the vercel triangle over a mirror of salt, the sun rising behind its tip
scenes 200×100 15 fps 31 colours, on a canvas 14.9 kB [report a bug]
· ·· · · · ··· · · · ··· · · · · ·•· · ·•· · · · · · · · · · · ··· · · · ··· ··· · · ··· · ·· ····· · · · · · • · · · · · · · · · · · · · · · · · · · ··· · · · ·· ··· · ··· · · · · · · · · · · · · · · · · · · · · · · · · · · · ····· · · · · · ··· · ·· ··• · · · · · ····· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·· · ·· ··· · · · · · · • · · · · • · · · · · · · · · · · · ····· · · · • · · · · ··· · · · ····· · · · · · · · · · · · · · · · · · · · · · · · · · · ··· · · ··· · ··· · ··· · · · · · · · · · · · · · · · · · · · · ······· · · · · · ··•·· · · · · ··· · · · •·· · · · ····· · · · • · · · ··· ··· · · · · · · · · · · · · · · · · · ··· · • · ··· ··· · · · · · · · · · · · · · ······· · · · ··· · · · ··· · · ··· · · ····· · · · · ······· · · · · · · · · · · · · · · ··· · ··· · · · · ···· · · · · · · · • · · · · · · · · · · · ····· · · · · · ··•·· · · · • · · ··· · · · ··· · · · · · · ······· · · · · · · · · • · · ·• · · · ··· ·· · · · · ··· · · ··· · · • · • · · · · · · · · ······· · · · ··· · · ··· · · · · • •·· · · · ····· · · · · · · · · ● · ● · · ····· ● ··· · · · ··· · · ·• · ····· · • ● · · · · · · ··•·· · · · · •·· ···••···· ····· · · ······· · · · · · · • · ····· · · · ·●●●●·· · · ··· · · · • · · · · ··•·· · ···●●●●●●···· · •·· · · ·· · · ··· ·●●●●●●●●·· · · · · • ● · · · · · · · · · · · · · ··· · · · · · · · ····●●●●●●●●•······ · · · · · · · · · · · · · ·· · · · · · · · · · · · · · · · · · · · · ··········•●●●· ●●●·•·········· · · · · · · · · · · · · · • · · · · · · · · · ····•·•·····●●● •●●•···•·•···· · · · · · · · · · · · · · ······•···•···· · · •●● ·●●•· · ····•···•···· · · · • · · · · · · · · · · ··· · · ········•···•·•·•·•···· · ·····•●• ••●·· · · ····•·•·•···•·········· · · · · · · · · · · · · · · · · · ··· ··· ··········· · · · · ··•● ●•• · · · · · ··········· ··· · · · · · · · ● · · · · · • · · · · · · ··· ··························· · · · · · · · ··●●• ·●•· · · · · · · · ··························· ··· · · · · · · · · · · · · · · · · · · · · · · · · · · · ··· ··· ··· · · ····· · · · ••● ••·· · · · · ··· ··· ··· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ··· ··· ··· ······················· · · · · · · · · · · · · ••●· ·●•·· · · · · · · · · · · · ··························· ··· ··· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·••• ••• · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ··· ··· ··· ··· · · · · · · · · · · · · · · ··•●· ●•• · · · · · · · · · · · · • · ····· ··· ··· ··· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ••• ·••· · · · · · · · · · · · · · ··· · · · · · · · · · · · · · · · · · · · · · · ·•· · · ·•· ··· ··· ··· ··· · · · · · · · · · · · · · · · · · · · · ••● ·●•·· · · · · · ·· · · · · · · · · · ··· · ··· ··· ··· ··· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · • · · · ·•●· ·•• · • ·· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · • · · · · · · · · · · · · · · · · ·· · · · · ·••• ●·• · · · · · · · · • · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·••· ●·· • · · · · · · · · · · · · · · · · · · · · · · · · · · ·•· · · · · · · · · · · · · · · · · · · · · · · · · · · ••• •●•· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · •·● ●•· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·••· ••• · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ··•• ●·• • • · · · · · · · · · · · · · · · ·•· · · · · · · · · · · · · · · · · · · · · · ··••· ·●•· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·•· · · · •·• ••·· · · · · · · · · · · · · · · · · · · · · · ·· · · · • · · ··● ·●•·· · · · · · · · · · · · · · · · · · · · · · · · · · · · ··•· ••• · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ··•●· ●·• · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ••· ·•·· · · · · · · · · · · · · · · · · · · · · · · · · · · · · •·● ·••·· · · · · · · · · · · · · · · · · · · · · · · · · · · · · ··●· ·•• · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ··••· ••• · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ••· ●·· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ••• ·••· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ··● ·••·· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·•●· ·••·· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ··•• •·• · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ····●●· ·●••·· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ··●●●·· ··· ··· ··· ··· ··· ··· ··· ··· ··· ·●●•··· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ············································································•••·········································•••·•··········································································· •••●•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••●•●●●•••••••••••••••••••••••••••••••••••••••●•●●●•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••●•••• ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••●•●•••••••••••••••••••••••••••••••••••••••••●•●••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••• · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ··•·· · · · · · · · · · · · · · · · · · · · ··•···· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ···•●•··· ··· ··· ··· ··· ··· ··· ··· ··· ····●●•·· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ··• ·••·· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · •·• ··•·· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ··· •·· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ··••· •·· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ···· ··· · · · · · · · · · · · · · · · · · · · · · ··• ·••· · · · · · · · · · · · · · · · · · · · · · ··· ··· · · · · · · · · · · · · · · · · · · · · · · · · ···•· ·•·· · · · · · · · · · · · · · · · · · · · · · · · · · ··· •·· · · · · · · · · · · · · · · · ··•· ·•· · · · · · · · · · · · · · · · · ··• ··· · · · · · · · · · · · · · · · · · · · •·• ·•·· · · · · · · · · · · · · · · · · · · ··· •·· · · · · · · · · · · · · · ···· •·· · · · · · · · · · · · · · ··• ··· · · · · · · · · · · · · · · · · · · · · · · · •·• ·•··· · · · · · · · · · · · · · · · · · · · · · · · ··· •·· · · · · · · · · · · · · · · · · · · · · · · · · · · ···•· •·· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ··· ··· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ··•· ·•· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ··• ···· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ··· · · •·· ·•·· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ···· •·· · · · · · · · · · · · · · · · · · ··· · · · · · · · ····· · · · · · · · · ··· · · · · · · · ··· · · · ··•· •···· · · · · · · ··· · · · · · · · · · · · · · · · · ······· · · · · · · · ··· · · · · · · · · · · · · ··· •·· ···· ··· · · · · · · · · ······· ············· · · · · · · · · · · · · ··•···•···•········ · · · · · · •·· · · · · ··•·• · · · · · · · ············•···•·· · · · · · · · · · · · · ··· · · ··· ······· · ·· · · · · · · · ·· · · · ····· · · · · ······ · •···· •·•·· · • ······· · · ····· · · · ····· ··· · · · · · · ··· · · · · · · · · · ··•·· · · · · · · · ··• · · · · · · · · · · ··•······ ··· ·•• · · · · · · · · · · ··· · · · · · · · ··•·•·· · · · · · · · · ·· ·· ····· · · · ··· · · · ··• ·· ··· ·•· · · · · · • · · · ··· ·· ··· ····· · · · · · · · ··• · · · · · · · · · · · ••· · · ··· ··· · · •·· · · · · · · · · · · ··· · · · · · · · •·· · ··· ·· ····· ·· · · · · · · · · ··•· · ··· ···· ··• · · · · · · · · • ··· ··· ··· ··•·· ··• · · · · · · · · ···••·· · · · ··•·· ••· · · · ····•·· · · · · · · · ·· •··· ··· ······················· ·· · ······•·•·•·•·· · · · ·••· ··• · · · •···················· ········· · · · · · · · ··· · · · · · · · · · • · · · · · · · · ··•·•·•·•·•··········•· · · · · · ··••• ··●•· · · · · · · • ··•···•···········• · · · · · · · · ·• ····················· ···· · •·· ·· · ··•···••• · • ·•·· · ····· · • ··• • · · · · · •••·•••·· · · · · ·• •·• ·• ·· · •· ·· · · · ··•·•·· · ·· ·•· · • ··• · · ·•· · · · · · •••·· · · · ··• · · · ··• • · ··· · •· · · · · · • · · · ···· · • ··• · · · · · · · · · ·• · · · · · · · · · · · · · •·· · · · · · · · · · ··• • · ·· · · · ··· · · · ··· · · · · ·•· · ·•· · · · · · · · · · · ··· · · · ··· ··· · · ··· · ·· ····· · · · · · • · · · · · · · · · · · · · · · · · · · ··· · · · ·· ··· · ··· · · · · · · · · · · · · · · · · · · · · · · · · · · · ····· · · · · · ··· · ·· ··• · · · · · ····· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·· · ·· ··· · · · · · · • · · · · • · · · · · · · · · · · · ····· · · · • · · · · ··· · · · ····· · · · · · · · · · · · · · · · · · · · · · · · · · · ··· · · ··· · ··· · ··· · · · · · · · · · · · · · · · · · · · · ······· · · · · · ··•·· · · · · ··· · · · •·· · · · ····· · · · • · · · ··· ··· · · · · · · · · · · · · · · · · · ··· · • · ··· ··· · · · · · · · · · · · · · ······· · · · ··· · · · ··· · · ··· · · ····· · · · · ······· · · · · · · · · · · · · · · ··· · ··· · · · · ···· · · · · · · · • · · · · · · · · · · · ····· · · · · · ··•·· · · · • · · ··· · · · ··· · · · · · · ······· · · · · · · · · • · · ·• · · · ··· ·· · · · · ··· · · ··· · · • · • · · · · · · · · ······· · · · ··· · · ··· · · · · • •·· · · · ····· · · · · · · · · ● · ● · · ····· ● ··· · · · ··· · · ·• · ····· · • ● · · · · · · ··•·· · · · · •·· ···••···· ····· · · ······· · · · · · · • · ····· · · · ·●●●●·· · · ··· · · · • · · · · ··•·· · ···●●●●●●···· · •·· · · ·· · · ··· ·●●●●●●●●·· · · · · • ● · · · · · · · · · · · · · ··· · · · · · · · ····●●●●●●●●•······ · · · · · · · · · · · · · ·· · · · · · · · · · · · · · · · · · · · · ··········•●●●· ●●●·•·········· · · · · · · · · · · · · · • · · · · · · · · · ····•·•·····●●● •●●•···•·•···· · · · · · · · · · · · · · ······•···•···· · · •●● ·●●•· · ····•···•···· · · · • · · · · · · · · · · ··· · · ········•···•·•·•·•···· · ·····•●• ••●·· · · ····•·•·•···•·········· · · · · · · · · · · · · · · · · · ··· ··· ··········· · · · · ··•● ●•• · · · · · ··········· ··· · · · · · · · ● · · · · · • · · · · · · ··· ··························· · · · · · · · ··●●• ·●•· · · · · · · · ··························· ··· · · · · · · · · · · · · · · · · · · · · · · · · · · · ··· ··· ··· · · ····· · · · ••● ••·· · · · · ··· ··· ··· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ··· ··· ··· ······················· · · · · · · · · · · · · ••●· ·●•·· · · · · · · · · · · · ··························· ··· ··· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·••• ••• · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ··· ··· ··· ··· · · · · · · · · · · · · · · ··•●· ●•• · · · · · · · · · · · · • · ····· ··· ··· ··· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ••• ·••· · · · · · · · · · · · · · ··· · · · · · · · · · · · · · · · · · · · · · · ·•· · · ·•· ··· ··· ··· ··· · · · · · · · · · · · · · · · · · · · · ••● ·●•·· · · · · · ·· · · · · · · · · · ··· · ··· ··· ··· ··· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · • · · · ·•●· ·•• · • ·· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · • · · · · · · · · · · · · · · · · ·· · · · · ·••• ●·• · · · · · · · · • · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·••· ●·· • · · · · · · · · · · · · · · · · · · · · · · · · · · ·•· · · · · · · · · · · · · · · · · · · · · · · · · · · ••• •●•· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · •·● ●•· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·••· ••• · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ··•• ●·• • • · · · · · · · · · · · · · · · ·•· · · · · · · · · · · · · · · · · · · · · · ··••· ·●•· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·•· · · · •·• ••·· · · · · · · · · · · · · · · · · · · · · · ·· · · · • · · ··● ·●•·· · · · · · · · · · · · · · · · · · · · · · · · · · · · ··•· ••• · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ··•●· ●·• · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ••· ·•·· · · · · · · · · · · · · · · · · · · · · · · · · · · · · •·● ·••·· · · · · · · · · · · · · · · · · · · · · · · · · · · · · ··●· ·•• · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ··••· ••• · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ••· ●·· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ••• ·••· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ··● ·••·· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·•●· ·••·· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ··•• •·• · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ····●●· ·●••·· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ··●●●·· ··· ··· ··· ··· ··· ··· ··· ··· ··· ·●●•··· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ············································································•••·········································•••·•··········································································· •••●•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••●•●●●•••••••••••••••••••••••••••••••••••••••●•●●●•••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••●•••• ••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••●•●•••••••••••••••••••••••••••••••••••••••••●•●••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••• · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ··•·· · · · · · · · · · · · · · · · · · · · ··•···· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ···•●•··· ··· ··· ··· ··· ··· ··· ··· ··· ····●●•·· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ··• ·••·· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · •·• ··•·· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ··· •·· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ··••· •·· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ···· ··· · · · · · · · · · · · · · · · · · · · · · ··• ·••· · · · · · · · · · · · · · · · · · · · · · ··· ··· · · · · · · · · · · · · · · · · · · · · · · · · ···•· ·•·· · · · · · · · · · · · · · · · · · · · · · · · · · ··· •·· · · · · · · · · · · · · · · · ··•· ·•· · · · · · · · · · · · · · · · · ··• ··· · · · · · · · · · · · · · · · · · · · •·• ·•·· · · · · · · · · · · · · · · · · · · ··· •·· · · · · · · · · · · · · · ···· •·· · · · · · · · · · · · · · ··• ··· · · · · · · · · · · · · · · · · · · · · · · · •·• ·•··· · · · · · · · · · · · · · · · · · · · · · · · ··· •·· · · · · · · · · · · · · · · · · · · · · · · · · · · ···•· •·· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ··· ··· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ··•· ·•· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ··• ···· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ··· · · •·· ·•·· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ···· •·· · · · · · · · · · · · · · · · · · ··· · · · · · · · ····· · · · · · · · · ··· · · · · · · · ··· · · · ··•· •···· · · · · · · ··· · · · · · · · · · · · · · · · · ······· · · · · · · · ··· · · · · · · · · · · · · ··· •·· ···· ··· · · · · · · · · ······· ············· · · · · · · · · · · · · ··•···•···•········ · · · · · · •·· · · · · ··•·• · · · · · · · ············•···•·· · · · · · · · · · · · · ··· · · ··· ······· · ·· · · · · · · · ·· · · · ····· · · · · ······ · •···· •·•·· · • ······· · · ····· · · · ····· ··· · · · · · · ··· · · · · · · · · · ··•·· · · · · · · · ··• · · · · · · · · · · ··•······ ··· ·•• · · · · · · · · · · ··· · · · · · · · ··•·•·· · · · · · · · · ·· ·· ····· · · · ··· · · · ··• ·· ··· ·•· · · · · · • · · · ··· ·· ··· ····· · · · · · · · ··• · · · · · · · · · · · ••· · · ··· ··· · · •·· · · · · · · · · · · ··· · · · · · · · •·· · ··· ·· ····· ·· · · · · · · · · ··•· · ··· ···· ··• · · · · · · · · • ··· ··· ··· ··•·· ··• · · · · · · · · ···••·· · · · ··•·· ••· · · · ····•·· · · · · · · · ·· •··· ··· ······················· ·· · ······•·•·•·•·· · · · ·••· ··• · · · •···················· ········· · · · · · · · ··· · · · · · · · · · • · · · · · · · · ··•·•·•·•·•··········•· · · · · · ··••• ··●•· · · · · · · • ··•···•···········• · · · · · · · · ·• ····················· ···· · •·· ·· · ··•···••• · • ·•·· · ····· · • ··• • · · · · · •••·•••·· · · · · ·• •·• ·• ·· · •· ·· · · · ··•·•·· · ·· ·•· · • ··• · · ·•· · · · · · •••·· · · · ··• · · · ··• • · ··· · •· · · · · · • · · · ···· · • ··• · · · · · · · · · ·• · · · · · · · · · · · · · •·· · · · · · · · · · ··• •
download
use it
[docs][copy it in]html
<script type="module" src="https://ascii.rest/ascii.js"></script> <ascii-art piece="salt-flat"></ascii-art>
No install and no build step. ascii.js defines the <ascii-art> tag, loads the piece from ascii.rest, plays it while it is on screen, and holds the first frame for anyone who prefers reduced motion.
react
// npm install ascii.rest
import { Ascii } from "ascii.rest/react";
import { saltFlat } from "ascii.rest/pieces";
<Ascii piece={saltFlat} /> A client component, so it works in the Next.js app router as it is. Pass the name instead, piece="salt-flat", to fetch the piece only when it mounts.
astro
--- // npm install ascii.rest import Ascii from "ascii.rest/astro"; --- <Ascii piece="salt-flat" />
Renders the first frame on the server, so the page is whole before any script runs, then plays the piece once it loads.
typescript
// npm install ascii.rest
import { mount } from "ascii.rest";
import { saltFlat } from "ascii.rest/pieces";
const el = document.querySelector<HTMLCanvasElement>("canvas")!;
const stop = mount(el, saltFlat); mount plays a piece in a <pre>, or on a <canvas> for the coloured ones, and returns a function that stops it. Every piece, option and frame is typed.
terminal
npx ascii.rest salt-flat
Plays it in your terminal, in colour, until you press a key. Add --seconds 5 to stop it on its own; npx ascii.rest list shows every piece. In a CLI of your own, play("salt-flat", { seconds: 2 }) from ascii.rest/terminal shows it as a splash screen.
/*
* salt flat: the Vercel triangle, black and exactly equilateral, hangs just
* above the horizon of a salt flat that holds it upside down like a mirror.
* The sun rises exactly behind its apex, so rays fan out from the tip across
* a black sky, both slanted edges burn as a razor white rim with a faint
* spectral fringe, and the horizon runs as a thin line of light through the
* gap under it. The crust toward us is cracked into hexagons that recede to
* the horizon. The rays turn slowly, the colours drift along the rim, a glint
* climbs both edges to the tip every six seconds and the water barely moves.
*
* Each frame is shaded in colour cell by cell on a square grid, then drawn as
* a halftone: every cell is a dot whose size is its brightness, ordered-
* dithered so gradients read as texture, in the palette colour nearest its hue.
*/
import type { Frame, Meta } from "../types.ts";
export const meta = {
name: "salt flat",
category: "scenes",
note: "the vercel triangle over a mirror of salt, the sun rising behind its tip",
cols: 200,
rows: 100,
cell: 1,
fps: 15,
ground: "#000000",
palette: [
"#0d0d0e", "#161617", "#1f1f21", "#2a2a2c", "#363638", "#444446", "#555557",
"#69696b", "#808082", "#9a9a9c", "#b8b8ba", "#d8d8da", "#ffffff",
// Vercel's spectrum, at full strength, pale and dim
"#50e3c2", "#0070f3", "#7928ca", "#ff0080", "#ff4d4d", "#f9cb28",
"#b0f2e3", "#8cbcf9", "#bb94e4", "#ff8cc0", "#ffa6a6", "#fce59a",
"#24665a", "#00326d", "#36125b", "#73003a", "#732323", "#705b12",
],
} satisfies Meta;
const W = 200, H = 100;
const CX = 100; // the triangle's centre line
const HW = 22; // half its base
const PH = HW * Math.sqrt(3); // equilateral, so this tall
const HORIZON = 57;
const LIFT = 1.7; // how far it hangs above the horizon, and its reflection below
const BASE = HORIZON - LIFT;
const APEX = BASE - PH;
const SUNY = APEX + 3.2; // the sun sits just behind the tip
const S3 = Math.sqrt(3) / 2;
const LOOP = 24; // every motion repeats in this many seconds
const TAU = Math.PI * 2;
const DOTS = " ·•●";
const COVER = [0, 0.3, 0.6, 1]; // how much each dot fills, against the largest
const BAYER = [0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5].map((v) => v / 16 - 0.47);
// Vercel's gradient stops, round a wheel: cyan, blue, violet, magenta, red, amber.
const SPECTRUM = [
[0.31, 0.89, 0.76], [0.0, 0.44, 0.95], [0.47, 0.16, 0.79],
[1.0, 0.0, 0.5], [1.0, 0.3, 0.3], [0.98, 0.8, 0.16],
];
function hash(x: number, y: number): number {
let h = Math.imul(x | 0, 374761393) + Math.imul(y | 0, 668265263);
h = Math.imul(h ^ (h >>> 13), 1274126177);
return ((h ^ (h >>> 16)) >>> 0) / 4294967296;
}
function noise(x: number, y: number): number {
const xi = Math.floor(x), yi = Math.floor(y);
const fx = x - xi, fy = y - yi;
const u = fx * fx * (3 - 2 * fx), v = fy * fy * (3 - 2 * fy);
const a = hash(xi, yi), b = hash(xi + 1, yi), c = hash(xi, yi + 1), d = hash(xi + 1, yi + 1);
return a + (b - a) * u + (c - a) * v + (a - b - c + d) * u * v;
}
const clamp = (v: number) => (v < 0 ? 0 : v > 1 ? 1 : v);
const smooth = (a: number, b: number, v: number) => {
const k = clamp((v - a) / (b - a));
return k * k * (3 - 2 * k);
};
const hex = (s: string) => [1, 3, 5].map((i) => parseInt(s.slice(i, i + 2), 16) / 255);
// A colour off the spectrum wheel, h wrapping at 1.
const spec = [0, 0, 0];
function spectrum(h: number): void {
const p = (((h % 1) + 1) % 1) * 6;
const i = Math.floor(p), f = p - i;
const a = SPECTRUM[i], b = SPECTRUM[(i + 1) % 6];
for (let c = 0; c < 3; c++) spec[c] = a[c] + (b[c] - a[c]) * f;
}
// Signed distance to each side of the triangle, positive outside: the two
// slanted edges, then the base.
function sideL(x: number, y: number): number {
return -(x - (CX - HW)) * S3 - (y - BASE) * 0.5;
}
function sideR(x: number, y: number): number {
return (x - (CX + HW)) * S3 - (y - BASE) * 0.5;
}
export default function saltFlat(): Frame {
const P = meta.palette.map(hex);
const N = W * H;
const out: string[] = new Array(N);
// Nearest palette colour, cached on a 32-step cube.
const lut = new Uint8Array(32768).fill(255);
const nearest = (r: number, g: number, b: number): number => {
const k = (Math.min(31, (r * 31.99) | 0) << 10) | (Math.min(31, (g * 31.99) | 0) << 5) | Math.min(31, (b * 31.99) | 0);
if (lut[k] !== 255) return lut[k];
let best = 0, bd = 1e9;
for (let i = 0; i < P.length; i++) {
const dr = P[i][0] - r, dg = P[i][1] - g, db = P[i][2] - b;
const d = 0.3 * dr * dr + 0.5 * dg * dg + 0.2 * db * db;
if (d < bd) (bd = d), (best = i);
}
return (lut[k] = best);
};
// --- the crust, built once -----------------------------------------------
// Each row below the horizon is a strip of the flat at some distance. The
// crust is a lattice of flat-topped hexagons on the ground; the cracks are
// drawn about one cell wide on screen at any distance, using how fast the
// distance to the nearest edge changes from cell to cell, and fade out
// where the cells shrink to a few rows near the horizon.
const CAM = 250, F = 64, SIZE = 2; // camera height, lens, hexagon radius
const ridge = new Float32Array(N);
const stars = new Float32Array(N);
for (let r = 0; r < H; r++) {
for (let x = 0; x < W; x++) {
const k = r * W + x;
const y = r + 0.5;
if (y < HORIZON) {
if (y < HORIZON - 14 && hash(x, r * 5 + 3) > 0.988) stars[k] = 0.4 + 0.6 * hash(r, x);
continue;
}
const d = y - HORIZON;
const Z = CAM / d, X = ((x + 0.5 - CX) * Z) / F;
// a slight warp, so the plates are not machined
const wx = X + 0.16 * (noise(X * 0.5, Z * 0.5) - 0.5);
const wz = Z + 0.16 * (noise(X * 0.5 + 7.3, Z * 0.5 + 1.9) - 0.5);
// axial hex coordinates, rounded to the nearest centre
const q = ((2 / 3) * wx) / SIZE, rr = ((-1 / 3) * wx + (Math.sqrt(3) / 3) * wz) / SIZE;
let cx = Math.round(q), cz = Math.round(rr), cy = Math.round(-q - rr);
const dx = Math.abs(cx - q), dz = Math.abs(cz - rr), dy = Math.abs(cy + q + rr);
if (dx > dz && dx > dy) cx = -cz - cy;
else if (dz > dy) cz = -cx - cy;
const vx = wx - SIZE * 1.5 * cx, vz = wz - SIZE * Math.sqrt(3) * (cz + cx / 2);
// the nearest of the three edge directions, and how far in from it
const a0 = Math.abs(vz), a1 = Math.abs(S3 * vx + 0.5 * vz), a2 = Math.abs(S3 * vx - 0.5 * vz);
let nx = 0, nz = Math.sign(vz), h = a0;
if (a1 > h) (h = a1), (nx = S3 * Math.sign(S3 * vx + 0.5 * vz)), (nz = 0.5 * Math.sign(S3 * vx + 0.5 * vz));
if (a2 > h) (h = a2), (nx = S3 * Math.sign(S3 * vx - 0.5 * vz)), (nz = -0.5 * Math.sign(S3 * vx - 0.5 * vz));
const e = SIZE * S3 - h;
// the same distance in screen cells
const dZy = -(Z * Z) / CAM, dXx = Z / F, dXy = ((x + 0.5 - CX) / F) * dZy;
const gx = nx * dXx, gy = nx * dXy + nz * dZy;
const px = e / Math.sqrt(gx * gx + gy * gy + 1e-9);
// how tall a plate is on screen: below about three rows the cracks would alias
const tall = (SIZE * 2 * S3) / Math.abs(dZy);
ridge[k] = smooth(0.95, 0.25, px) * smooth(5, 15, tall);
}
}
// --- the sky, built once ------------------------------------------------
// Everything about the sky that does not move, for each cell above the
// water and, below it, for the point it mirrors. The ripples barely shift
// that point, so only the triangle's reflection follows them.
const FANS = 1024;
const fans = new Float32Array(FANS);
const still = new Float32Array(N), corona = new Float32Array(N), fall = new Float32Array(N);
const angle = new Uint16Array(N);
const shine = new Float32Array(N);
for (let r = 0; r < H; r++) {
for (let x = 0; x < W; x++) {
const k = r * W + x;
const xc = x + 0.5;
const y = r + 0.5 < HORIZON ? r + 0.5 : 2 * HORIZON - r - 0.5;
const dx = xc - CX, ds = y - SUNY;
const dist = Math.sqrt(dx * dx + ds * ds);
const up = HORIZON - y;
// black overhead, a band of dawn low down, a white line at the edge,
// and a faint lens streak through the sun
still[k] = 0.22 * Math.exp(-up / 3) + 0.025 * Math.exp(-up / 10) + 0.8 * Math.exp(-(up * up) / 0.7) +
0.35 * Math.exp(-(ds * ds) / 0.5) * Math.exp(-Math.abs(dx) / 32) + (dist < 4.5 ? 1 : 0);
// the sun: a white core behind the tip and a soft corona
corona[k] = 0.9 * Math.exp(-dist / 2.5) + 0.25 * Math.exp(-dist / 8);
// the rays fade with distance and are kept off the triangle's flanks,
// so the rim stands against black
const th = Math.atan2(dx, -ds);
fall[k] = smooth(4, 12, dist) * Math.exp(-dist / 50) * smooth(2.35, 1.75, Math.abs(th));
angle[k] = Math.min(FANS - 1, Math.floor(((th + Math.PI) / TAU) * FANS));
// the salt ridges are lit brighter toward the sun's line
shine[k] = (0.3 + 0.25 * Math.exp(-(dx * dx) / 2500)) * smooth(HORIZON + 2, HORIZON + 12, r + 0.5);
}
}
return (t, { color } = {}) => {
const w = TAU / LOOP;
const breathe = 0.92 + 0.08 * Math.sin(t * w * 2);
const spin = t * w; // the rays turn one notch each loop
// The glint: climbs both edges from the base corners to the tip in
// 1.4 s, every six seconds, then flares at the top.
const gp = (t % 6) / 1.4;
const ge = Math.min(1, gp);
const gI = gp < 1 ? 0.5 + 0.5 * gp : gp < 1.5 ? 1 - (gp - 1) * 2 : 0;
const gy = BASE - PH * ge * ge;
const flare = gp > 0.8 && gp < 1.5 ? Math.sin(((gp - 0.8) / 0.7) * Math.PI) : 0;
// What a point of the world above the water looks like, into R G B: the
// sky and sun, then the triangle laid over them with its rim and fringe.
// The rays, round the sun by angle: irregular narrow beams with true
// black between them, turning slowly.
for (let i = 0; i < FANS; i++) {
const th = (i / FANS) * TAU - Math.PI;
let a = 0.5 + 0.5 * Math.cos(14 * th + spin + 1.4 * Math.sin(5 * th - spin * 2));
let b = 0.5 + 0.5 * Math.cos(23 * th - spin * 3 + 0.9 * Math.sin(3 * th + spin));
a *= a, a *= a, a *= a; // to the 8th
b *= b, b *= b, b *= b, b *= b; // to the 16th
fans[i] = (a * 0.55 + b * 0.18) * breathe;
}
// What a point of the world above the water looks like, into R G B: the
// sky and sun, then the triangle laid over them with its rim and fringe.
// The sky's still parts come from cell k, worked out once.
let R = 0, G = 0, B = 0, COV = 0;
const world = (x: number, y: number, k: number, near: boolean) => {
const dx = x - CX;
let l = still[k] + corona[k] * breathe + fall[k] * fans[angle[k]];
// the glint's halo, one on each edge as it climbs
if (gI > 0) {
const gh = HW * (1 - (BASE - gy) / PH), gdy = y - gy;
const gl = Math.min((x - CX + gh) ** 2, (x - CX - gh) ** 2) + gdy * gdy;
l += Math.exp(-gl / 5) * gI * 0.7;
}
// when the glint reaches the tip, a four-pointed star there
if (flare > 0) {
const da = y - APEX;
l += flare * (Math.exp(-(da * da) / 0.35) * Math.exp(-Math.abs(dx) / 16) + Math.exp(-(dx * dx) / 0.35) * Math.exp(-Math.abs(da) / 12)) * 0.9;
}
if (near) {
const s = stars[Math.floor(y) * W + Math.floor(x)];
if (s) l = Math.max(l, s * (0.65 + 0.35 * Math.sin(t * w * (2 + ((s * 10) | 0)) + s * 40)) * smooth(0.25, 0.04, l));
}
R = l, G = l, B = l;
COV = 0;
// the triangle
const sl = sideL(x, y), sr = sideR(x, y), sb = y - BASE;
const edge = Math.max(sl, sr);
const sd = Math.max(edge, sb);
if (sd > 4) return;
const cov = clamp(0.5 - sd * 1.4);
COV = clamp(0.5 - sd * 0.5);
R *= 1 - cov, G *= 1 - cov, B *= 1 - cov;
// how high up the edge, 0 at the base and 1 at the tip
const hgt = clamp((BASE - y) / PH);
if (sb < 0.4) {
// the razor rim, brightest toward the sun, and the glint on it
let rim = Math.exp(-((edge + 0.55) * (edge + 0.55)) / 0.3) * (0.8 + 0.4 * hgt);
if (gI > 0) rim += Math.exp(-((y - gy) * (y - gy)) / 3) * gI * Math.exp(-((edge + 0.5) * (edge + 0.5)) / 0.6) * 1.2;
rim *= smooth(0.4, -0.6, sb);
R += rim, G += rim, B += rim;
// the fringe just outside it, dispersing into Vercel's spectrum
if (edge > -0.5) {
const fr = Math.exp(-((edge - 0.7) * (edge - 0.7)) / 0.5) * (0.45 + 0.3 * hgt) * smooth(0.4, -1.2, sb);
spectrum(edge * 0.18 + hgt * 0.9 - t / 6 + (sl > sr ? 0 : 0.5));
R += fr * spec[0], G += fr * spec[1], B += fr * spec[2];
}
}
// the underside, lit faintly by the water
const under = Math.exp(-((sb + 0.5) * (sb + 0.5)) / 0.2) * (edge < 0 ? 1 : 0) * 0.32;
R += under, G += under, B += under;
};
for (let r = 0; r < H; r++) {
for (let x = 0; x < W; x++) {
const k = r * W + x;
const y = r + 0.5;
const xc = x + 0.5;
let floor = 0;
if (y < HORIZON) {
world(xc, y, k, true);
floor = 0.05 * smooth(10, HORIZON, y);
} else {
// the mirror: the world flipped about the horizon, shaken a little
// more as the water comes toward us
const d = y - HORIZON;
const amp = 0.06 + d * 0.011;
const ph = t * w;
const sx = xc + amp * (Math.sin(y * 0.8 - ph * 6 + Math.sin(xc * 0.05) * 2) * 0.7 + Math.sin(xc * 0.11 + y * 1.3 - ph * 4) * 0.35);
const sy = 2 * HORIZON - y + amp * 0.4 * Math.sin(xc * 0.07 - ph * 3 + y * 0.6);
world(sx, sy, k, false);
// like any water it mirrors most at a grazing angle, less toward us
const film = 0.8 - 0.5 * smooth(HORIZON, H, y);
R *= film, G *= film, B *= film;
// the ridges of salt, lit pale, hardly at all where they cross the
// black of the reflection so it stays crisp
const rg = ridge[k] * (1 - 0.85 * COV);
if (rg > 0) {
const lit = shine[k];
const sh = 1 - 0.55 * rg;
R = R * sh + rg * lit, G = G * sh + rg * lit, B = B * sh + rg * lit;
}
floor = 0.02;
}
// Dot size from brightness, dithered. Then the colour makes up what the
// dot size could not: a small dot is drawn brighter and a large one
// dimmer, so a gradient stays smooth across the dither's steps.
const cr = R, cg = G, cbl = B;
const peak = Math.max(cr, cg, cbl, 1e-4);
const level = peak < 0.008 ? 0 : clamp(floor + (1 - floor) * Math.pow(peak, 0.85) * 0.95);
const step = Math.max(0, Math.min(3, Math.round(level * 3 + BAYER[(r & 3) * 4 + (x & 3)])));
out[k] = DOTS[step];
if (color) {
const want = step ? Math.min(1, (level + 0.06) / COVER[step]) : 0;
const s = (0.3 + 0.7 * want) / peak;
color[k] = nearest(clamp(cr * s), clamp(cg * s), clamp(cbl * s));
}
}
}
const lines: string[] = [];
for (let r = 0; r < H; r++) lines.push(out.slice(r * W, (r + 1) * W).join(""));
return lines.join("\n");
};
}