a wall of lava lamps under the glowing orange cloud, watched by a camera
scenes 200×100 15 fps 30 colours, on a canvas 22.0 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="lava-wall"></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 { lavaWall } from "ascii.rest/pieces";
<Ascii piece={lavaWall} /> A client component, so it works in the Next.js app router as it is. Pass the name instead, piece="lava-wall", to fetch the piece only when it mounts.
astro
--- // npm install ascii.rest import Ascii from "ascii.rest/astro"; --- <Ascii piece="lava-wall" />
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 { lavaWall } from "ascii.rest/pieces";
const el = document.querySelector<HTMLCanvasElement>("canvas")!;
const stop = mount(el, lavaWall); 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 lava-wall
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("lava-wall", { seconds: 2 }) from ascii.rest/terminal shows it as a splash screen.
/*
* lava wall: the lobby wall of lava lamps that Cloudflare films for entropy.
* Shelves of lamps fill the dark room, a little larger toward us, each with
* its own wax rising, stretching, splitting and sinking. The orange cloud
* hangs over them as a backlit sign, and a camera on a tripod watches.
*
* 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: "lava wall",
category: "scenes",
note: "a wall of lava lamps under the glowing orange cloud, watched by a camera",
cols: 200,
rows: 100,
cell: 1,
fps: 15,
loop: 30,
ground: "#08070a",
palette: [
"#16121a", "#221c22", "#33282a", "#4a3a32", "#6e5442", "#9a7658",
"#404041", "#707074", "#a9a9ae", "#e8e4de",
"#5c1616", "#9e211d", "#dc362b", "#ff6a52",
"#7c3510", "#c4570f", "#f38020", "#faae40", "#ffd27c", "#fff1c8",
"#e6c02a",
"#0f3a3c", "#18786f", "#43d6c0",
"#2f1842", "#6b2f92", "#b45ddc", "#e2508c",
"#1d3768", "#3f80d2",
],
} satisfies Meta;
const W = 200, H = 100;
const LOOP = 30;
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);
const WALL = 0, GLASS = 1, METAL = 2, SHELF = 3, LIP = 4, SIGN = 5, CAM = 6, LENS = 7;
// The cloud from the library's own cloudflare piece. Its text cells are twice
// as tall as wide, so each is read as two square halves, top and bottom.
const LOGO = String.raw`
._ppp8888888qqq_,
_pp88888888888888888q_,
_p88888888888888888888888q,
.p888888888888888888888888888p
_888888888888888888888888888888q,
__ppqqqq__88888888888888888888888888888888q,
_p8888888888888888888888888888888888888888888p
q888888888888888888888888888888888888888888888P pqqqq__
.888888888888888888888888888888888888888888888P q88888888q_,
:888888888888888888888888888888888888888888888'.888888888888q,
_pppq8888888888888888888888888888888888888888888P '88888888888888_
_p88888888888888888888888888888888888888888888888"' "88888888888888p
.p88888888888888888P""""""""""""""""""""""""""""' '"""""""888888,
.p8888888888888888888888888888888888888888888qqqqq_ _pppq888888888888b
q88888888888888888888888888888888888888888888888888p _p888888888888888888
8888888888888888888888888888888888888888888888888888 .p88888888888888888888
888888888888888888888888888888888888888888888888888" d88888888888888888888P
`;
// How much of the top and the bottom half of a cell each character fills.
const HALVES: Record<string, [number, number]> = {
"8": [1, 1], b: [1, 1], d: [1, 1], p: [0.5, 1], q: [0.5, 1], P: [1, 0.5],
_: [0, 0.6], ",": [0, 0.5], ".": [0, 0.5], "'": [0.7, 0], '"': [1, 0], ":": [0.8, 0.8],
};
// The wax and the liquid of each kind of lamp: mostly warm, a few cool.
const KINDS: [number[], number[]][] = [
[[1, 0.2, 0.14], [0.55, 0.36, 0.08]], // red in amber
[[1, 0.52, 0.12], [0.5, 0.1, 0.12]], // orange in red
[[1, 0.82, 0.22], [0.55, 0.2, 0.05]], // yellow in orange
[[0.95, 0.16, 0.12], [0.6, 0.5, 0.12]], // red in yellow
[[1, 0.6, 0.18], [0.42, 0.14, 0.05]], // tangerine in brown
[[0.26, 0.86, 0.75], [0.07, 0.17, 0.38]], // teal in blue
[[0.75, 0.36, 0.9], [0.14, 0.07, 0.32]], // purple in indigo
[[1, 0.32, 0.55], [0.3, 0.08, 0.32]], // pink in plum
];
const WARM = [0, 1, 2, 3, 4, 0, 1, 2, 3, 4];
// Shelves, back to front: lamp scale and the row the lamp tops start on.
const SHELVES = [
{ s: 0.85, top: 9 },
{ s: 1.0, top: 30 },
{ s: 1.15, top: 50.5 },
{ s: 1.3, top: 74 },
];
const LAMP_H = 16; // a lamp's height in lamp units: base 0..4, glass 4..14, cap above
const GLASS_LO = 4, GLASS_HI = 14;
const SIGN_W = 62, SIGN_X = 100 - SIGN_W / 2, SIGN_Y = 1;
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;
}
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 mix = (a: number, b: number, k: number) => a + (b - a) * k;
const hex = (s: string) => [1, 3, 5].map((i) => parseInt(s.slice(i, i + 2), 16) / 255);
// A lamp's half width at height v, in lamp units: a cone of a base, the
// bottle swelling low and narrowing up, and a small cap.
function halfWidth(v: number): number {
if (v < 0) return -1;
if (v < GLASS_LO) return v < 0.5 ? 3.2 : 3.0 - 1.1 * (v / GLASS_LO);
if (v < GLASS_HI) {
const g = (v - GLASS_LO) / (GLASS_HI - GLASS_LO);
return 2.0 + 0.9 * smooth(0, 0.3, g) - 1.55 * smooth(0.3, 1, g);
}
if (v < LAMP_H) return 1.35 - 0.55 * ((v - GLASS_HI) / (LAMP_H - GLASS_HI)) - (v > LAMP_H - 0.6 ? 0.3 : 0);
return -1;
}
// Box blur in place, horizontally then vertically, radius r.
function blur(a: Float32Array, r: number): void {
const tmp = new Float32Array(W * H);
for (let y = 0; y < H; y++)
for (let x = 0; x < W; x++) {
let s = 0;
for (let d = -r; d <= r; d++) s += a[y * W + Math.min(W - 1, Math.max(0, x + d))];
tmp[y * W + x] = s / (2 * r + 1);
}
for (let y = 0; y < H; y++)
for (let x = 0; x < W; x++) {
let s = 0;
for (let d = -r; d <= r; d++) s += tmp[Math.min(H - 1, Math.max(0, y + d)) * W + x];
a[y * W + x] = s / (2 * r + 1);
}
}
export default function lavaWall(): 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 logo as a mask of square half cells ----------------------------
const rows = LOGO.slice(1, -1).split("\n");
const GW = 79, GH = rows.length * 2;
const cov = new Float32Array(GW * GH);
const back = new Uint8Array(GW * GH);
// The small cloud behind is everything right of the seam between the two,
// found row by row as the space inside the ink, and bridged where it closes.
const seam: number[] = rows.map((line) => {
const first = line.search(/\S/), last = line.trimEnd().length - 1;
let at = -1;
for (let x = first + 1; x < last; x++) if (line[x] === " ") at = x;
return at;
});
for (let y = 0; y < rows.length; y++) {
if (seam[y] >= 0 || y < 8) continue;
let a = y - 1, b = y + 1;
while (b < rows.length && seam[b] < 0) b++;
seam[y] = b < rows.length ? (seam[a] + seam[b]) / 2 : seam[a];
}
for (let y = 0; y < rows.length; y++)
for (let x = 0; x < GW; x++) {
const h = HALVES[rows[y][x] ?? " "] ?? [0, 0];
const isBack = y >= 8 && seam[y] >= 0 && x > seam[y] ? 1 : 0;
cov[2 * y * GW + x] = h[0];
cov[(2 * y + 1) * GW + x] = h[1];
back[2 * y * GW + x] = back[(2 * y + 1) * GW + x] = isBack;
}
const logoAt = (gx: number, gy: number) => {
const xi = Math.floor(gx), yi = Math.floor(gy);
const fx = gx - xi, fy = gy - yi;
const at = (x: number, y: number) => (x < 0 || y < 0 || x >= GW || y >= GH ? 0 : cov[y * GW + x]);
return mix(mix(at(xi, yi), at(xi + 1, yi), fx), mix(at(xi, yi + 1), at(xi + 1, yi + 1), fx), fy);
};
const LK = SIGN_W / 76; // cells for one half cell of the logo
const signCov = new Float32Array(N);
const signBack = new Uint8Array(N);
for (let r = 0; r < H; r++)
for (let x = 0; x < W; x++) {
const gx = 1.5 + (x + 0.5 - SIGN_X) / LK, gy = 2 + (r + 0.5 - SIGN_Y) / LK;
if (gx < -1 || gy < -1 || gx > GW || gy > GH) continue;
// four samples a cell, for a clean edge
let c = 0;
for (const [ox, oy] of [[-0.25, -0.25], [0.25, -0.25], [-0.25, 0.25], [0.25, 0.25]])
c += logoAt(gx - 0.5 + ox / LK, gy - 0.5 + oy / LK);
signCov[r * W + x] = smooth(0.35, 0.7, c / 4);
const bx = Math.round(gx - 0.5), by = Math.round(gy - 0.5);
if (bx >= 0 && by >= 0 && bx < GW && by < GH) signBack[r * W + x] = back[by * GW + bx];
}
// the sign's glow on the wall, close and wide
const haloNear = Float32Array.from(signCov), haloFar = Float32Array.from(signCov);
blur(haloNear, 2), blur(haloNear, 2);
blur(haloFar, 7), blur(haloFar, 7);
// --- the lamps --------------------------------------------------------
type Lamp = { cx: number; bottom: number; s: number; wax: number[]; liq: number[]; glow: number[]; blobs: number[][] };
const lamps: Lamp[] = [];
const mat = new Uint8Array(N);
const lampOf = new Int16Array(N).fill(-1);
const lu = new Float32Array(N), lv = new Float32Array(N);
const shelfOf = new Int8Array(N).fill(-1);
SHELVES.forEach(({ s, top }, j) => {
const bottom = top + LAMP_H * s;
const sp = 10.5 * s;
for (let i = -12; i <= 12; i++) {
const cx = 100 + (i + (j % 2) * 0.5 - 0.25) * sp;
if (cx < -5 || cx > W + 5) continue;
if (j === 0 && Math.abs(cx - 100) < SIGN_W / 2 + 5) continue; // the sign hangs here
const h = hash(i + 40, j + 7);
const kind = h < 0.78 ? WARM[Math.floor(hash(i, j * 13) * WARM.length)] : 5 + Math.floor(hash(j, i * 5) * 3);
const [wax, liq] = KINDS[kind];
// a resting pool at the bottom, a smaller one under the cap, and three
// blobs that rise and sink, each on a period that divides the loop
const blobs: number[][] = [[0, 4.5, 2.0, 0.5, 0, 0], [0, 13.7, 1.0, 0.6, 0, 0]];
for (let b = 0; b < 3; b++) {
const q = hash(i * 31 + b, j * 17 + 3);
blobs.push([1 + Math.floor(q * 3), hash(b, i * 7 + j), 0.8 + 0.45 * hash(i + b * 9, j), 1, hash(i * 3, b + j * 11), 1]);
}
const glow = wax.map((w, c) => 0.65 * w + 0.5 * liq[c]);
lamps.push({ cx, bottom, s, wax, liq, glow, blobs });
}
// the shelf under them
for (let r = Math.floor(bottom); r < Math.min(H, bottom + 2.3 * s + 1); r++)
for (let x = 0; x < W; x++) {
const k = r * W + x;
const y = r + 0.5;
if (y >= bottom && y < bottom + 2.3 * s) {
mat[k] = y < bottom + 0.7 ? LIP : SHELF;
shelfOf[k] = j;
}
}
});
lamps.forEach((L, id) => {
const { cx, bottom, s } = L;
for (let r = Math.floor(bottom - LAMP_H * s); r < Math.ceil(bottom); r++)
for (let x = Math.floor(cx - 3.5 * s); x <= Math.ceil(cx + 3.5 * s); x++) {
if (r < 0 || r >= H || x < 0 || x >= W) continue;
const k = r * W + x;
const v = (bottom - (r + 0.5)) / s, u = (x + 0.5 - cx) / s;
const hw = halfWidth(v);
if (hw < 0 || Math.abs(u) > hw) continue;
mat[k] = v >= GLASS_LO && v < GLASS_HI ? GLASS : METAL;
lampOf[k] = id;
lu[k] = u / hw;
lv[k] = v;
}
});
// Light spilled by every lamp onto the wall and shelves, from the length of
// its glass, kept out from under the shelf above.
const spill = new Float32Array(N * 3);
for (const L of lamps) {
const y0 = L.bottom - GLASS_HI * L.s, y1 = L.bottom - GLASS_LO * L.s;
const reach = 5 * L.s;
for (let r = Math.max(0, Math.floor(y0 - 26)); r < Math.min(H, y1 + 14); r++) {
const y = r + 0.5;
const dy = y < y0 ? y0 - y : y > y1 ? y - y1 : 0;
for (let x = Math.max(0, Math.floor(L.cx - 26)); x < Math.min(W, L.cx + 26); x++) {
const dx = x + 0.5 - L.cx;
const d = Math.sqrt(dx * dx + dy * dy * 1.6);
const e = Math.exp(-d / reach) * 0.6 + Math.exp(-d / (reach * 3)) * 0.04;
const k = r * W + x;
spill[k * 3] += L.glow[0] * e;
spill[k * 3 + 1] += L.glow[1] * e;
spill[k * 3 + 2] += L.glow[2] * e;
}
}
}
// --- the camera on its tripod, a silhouette in front ------------------
const camRim = new Float32Array(N);
const inCam = (x: number, y: number): number => {
// the body seen from behind, corners rounded, the screen on its back
const bx = Math.max(0, Math.abs(x - 168) - 16), by = Math.max(0, Math.abs(y - 73) - 6.5);
if (bx * bx + by * by < 4) return x >= 154 && x < 176 && y >= 67.5 && y < 79 ? LENS : CAM;
// the prism hump on top, and the dials either side of it
if (y >= 59.5 && y < 66 && Math.abs(x - 166) < 5.5 + (y - 59.5) * 0.45) return CAM;
if (y >= 63.5 && y < 66 && ((x >= 153 && x < 158) || (x >= 177 && x < 182))) return CAM;
if (x >= 162 && x < 174 && y >= 80 && y < 86) return CAM; // the tripod head
// the legs, splayed from the head to the floor
for (const fx of [138, 200]) {
const k = (y - 85) / 15;
if (k >= 0 && k <= 1.1 && Math.abs(x - mix(168, fx, k)) < 1.3 + 0.5 * k) return CAM;
}
if (y >= 85 && Math.abs(x - 168) < 1) return CAM; // the centre column
return 0;
};
for (let r = 0; r < H; r++)
for (let x = 0; x < W; x++) {
const c = inCam(x + 0.5, r + 0.5);
if (!c) continue;
mat[r * W + x] = c;
// a rim of lamplight on the edges facing the wall
const open =
(inCam(x + 0.5, r - 0.5) ? 0 : 1) + (inCam(x - 0.5, r + 0.5) ? 0 : 0.7) + (inCam(x + 1.5, r + 0.5) ? 0 : 0.7) + (inCam(x + 0.5, r + 1.5) ? 0 : 0.4);
camRim[r * W + x] = Math.min(1, open);
}
const SIGN_C = [0.953, 0.502, 0.125], BACK_C = [0.98, 0.682, 0.251];
// The screen on its back shows what it films, tiny: the cloud, and under
// it a row of lamps in the colours of the real ones on a shelf.
const screen = new Float32Array(N * 3);
const MINI = 10 / 76;
for (let r = 0; r < H; r++)
for (let x = 0; x < W; x++) {
if (mat[r * W + x] !== LENS) continue;
const k = r * W + x;
const row = r - 67, col = x - 154;
let c = [0.025, 0.035, 0.07];
const sc = smooth(0.3, 0.6, logoAt(1.5 + (col + 0.5 - 6) / MINI - 0.5, 2 + (row + 0.5 - 0.6) / MINI - 0.5));
if (sc > 0) c = (row > 2 && col > 11 ? BACK_C : SIGN_C).map((v) => v * sc + 0.02);
if (row >= 6 && row < 9 && col % 3 !== 2) {
const L = lamps[Math.floor(hash(Math.floor(col / 3), 3) * lamps.length)];
const g = row === 6 ? 0.5 : 1;
c = L.glow.map((v) => v * g);
} else if (row === 9) c = [0.2, 0.11, 0.07];
(screen[k * 3] = c[0]), (screen[k * 3 + 1] = c[1]), (screen[k * 3 + 2] = c[2]);
}
for (let k = 0; k < N; k++) if (signCov[k] > 0.02 && mat[k] !== CAM && mat[k] !== LENS) mat[k] = SIGN;
const NB = 5; // two pools and three blobs a lamp
const blobX = new Float32Array(lamps.length * NB), blobY = new Float32Array(lamps.length * NB);
const blobR = new Float32Array(lamps.length * NB), blobS = new Float32Array(lamps.length * NB);
return (t, { color } = {}) => {
const tt = ((t % LOOP) + LOOP) % LOOP;
// the glint crossing the sign every ten seconds
const glintAt = ((tt % 10) / 2.2) * (SIGN_W + 40) + SIGN_X - 20;
const hum = 0.96 + 0.04 * Math.sin((tt / LOOP) * Math.PI * 2 * 6);
// where every lamp's wax is now
lamps.forEach((L, id) => {
for (let b = 0; b < NB; b++) {
const B = L.blobs[b], i = id * NB + b;
if (B[5] === 0) {
(blobX[i] = 0), (blobY[i] = B[1]), (blobR[i] = B[2] * B[2]), (blobS[i] = 1 / B[3]);
continue;
}
const a = ((tt / LOOP) * B[0] + B[1]) * Math.PI * 2;
const by = 5.3 + (0.5 - 0.5 * Math.cos(a)) * 7.2;
const hw = halfWidth(by);
const br = B[2] * (0.8 + 0.2 * Math.sin(a * 0.5 + B[4]) ** 2) * Math.min(1, hw / 2.2);
blobX[i] = 0.45 * hw * Math.sin(a * 2 + B[4] * 6.28);
blobY[i] = by;
blobR[i] = br * br;
blobS[i] = 1 / (1 + 0.75 * Math.abs(Math.sin(a))); // drawn out while it moves fast
}
});
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 m = mat[k];
let cr = 0, cg = 0, cb = 0, floor = 0;
// the room: the dark wall, the lamps' light on it, and the sign's
const sr = spill[k * 3], sg = spill[k * 3 + 1], sb = spill[k * 3 + 2];
const hn = haloNear[k], hf = haloFar[k];
// a ring of glow round the sign, kept out of the seams inside it
const halo = hf * 0.3 * (1 - 0.85 * smooth(0.15, 0.6, hn)) * hum;
const wr = 0.006 + sr * 0.075 + halo * SIGN_C[0];
const wg = 0.005 + sg * 0.075 + halo * SIGN_C[1];
const wb = 0.008 + sb * 0.075 + halo * SIGN_C[2];
if (m === WALL || m === SIGN) {
// faint panel seams on the wall
const seamV = (x % 25 === 0 ? 0.6 : 1) * (0.92 + 0.08 * hash(x >> 2, r >> 3));
(cr = wr * seamV), (cg = wg * seamV), (cb = wb * seamV);
if (m === SIGN) {
const c = signCov[k];
const C = signBack[k] ? BACK_C : SIGN_C;
// backlit: lit full and even, as a lightbox is
let lum = 1.3 * hum;
const g = Math.abs(x + 0.5 + (y - SIGN_Y) * 0.6 - glintAt);
const glint = g < 5 ? 1 - smooth(0, 5, g) : 0;
lum += 0.35 * glint;
const fr = Math.min(1.2, C[0] * lum + glint * 0.25), fg = Math.min(1.2, C[1] * lum + glint * 0.3), fb = C[2] * lum + glint * 0.25;
(cr = mix(cr, fr, c)), (cg = mix(cg, fg, c)), (cb = mix(cb, fb, c));
floor = 0.2 * c;
}
} else if (m === GLASS || m === METAL) {
const id = lampOf[k];
const L = lamps[id];
const u = lu[k], v = lv[k];
if (m === GLASS) {
const g = (v - GLASS_LO) / (GLASS_HI - GLASS_LO);
const px = u * halfWidth(v);
let f = 0;
for (let i = id * NB; i < id * NB + NB; i++) {
const dx = px - blobX[i], dy = (v - blobY[i]) * blobS[i];
f += blobR[i] / (dx * dx + dy * dy + 0.05);
}
// the liquid, lit by the bulb below, darker toward the glass edge
const bulb = 0.3 + 0.6 * Math.exp(-g * 2.4);
const rim = 1 - 0.5 * smooth(0.55, 1, Math.abs(u));
(cr = L.liq[0] * bulb * rim), (cg = L.liq[1] * bulb * rim), (cb = L.liq[2] * bulb * rim);
// the wax, glowing hotter where it is thick and low
const wax = smooth(0.8, 1.25, f);
const wl = (0.62 + 0.45 * Math.exp(-g * 1.6) + 0.2 * clamp(f - 1.4)) * (0.75 + 0.25 * rim);
(cr = mix(cr, L.wax[0] * wl, wax)), (cg = mix(cg, L.wax[1] * wl, wax)), (cb = mix(cb, L.wax[2] * wl, wax));
// a sheen down the glass on the left
const sheen = Math.exp(-(((u + 0.5) / 0.14) ** 2)) * 0.28 * smooth(0.95, 0.1, g);
(cr += sheen), (cg += sheen), (cb += sheen * 1.05);
floor = 0.3;
} else {
// brushed metal, a round highlight, and the glass's colour where it
// meets the bottle
const hl = Math.exp(-(((u + 0.3) / 0.3) ** 2));
const near = v < GLASS_LO ? smooth(GLASS_LO - 2, GLASS_LO, v) : smooth(GLASS_HI + 1.6, GLASS_HI, v);
const base = 0.4 + 0.3 * (1 - u * u) + 0.45 * hl + 0.35 * near;
(cr = 0.12 * hl + L.glow[0] * base * 0.4), (cg = 0.12 * hl + L.glow[1] * base * 0.4), (cb = 0.13 * hl + L.glow[2] * base * 0.4);
floor = 0.25;
}
} else if (m === LIP || m === SHELF) {
// the shelf's top edge catches the lamps; its face is in their glow
const j = shelfOf[k];
const lit = m === LIP ? 0.95 : 0.32;
const grain = 0.9 + 0.1 * hash(x >> 1, r * 7 + j);
cr = (0.07 + sr * lit) * grain;
cg = (0.05 + sg * lit * 0.92) * grain;
cb = (0.04 + sb * lit * 0.85) * grain;
floor = 0.08;
} else if (m === CAM || m === LENS) {
const rim = camRim[k];
// black, with a cool edge of light where it faces the glow
const e = rim * (0.9 + 0.6 * smooth(85, 70, y));
cr = 0.012 + (sr * 0.5 + 0.05) * e;
cg = 0.012 + (sg * 0.5 + 0.05) * e;
cb = 0.016 + (sb * 0.5 + 0.06) * e;
if (m === CAM && (x === 179 || x === 181) && (r === 72 || r === 75)) (cr = 0.16), (cg = 0.15), (cb = 0.15); // buttons
if (m === LENS) {
const s = 0.9 + 0.1 * hum;
(cr = screen[k * 3] * s), (cg = screen[k * 3 + 1] * s), (cb = screen[k * 3 + 2] * s);
}
if (x === 180 && r === 68) {
// the tally light, recording
const on = Math.sin(tt * Math.PI * 2) > -0.2;
(cr = on ? 1 : 0.3), (cg = on ? 0.22 : 0.06), (cb = on ? 0.18 : 0.05);
}
}
// the room falls off toward the corners and the floor
const vx = (x - 100) / 100, vy = (y - 46) / 62;
const vig = 1 - 0.4 * (vx * vx + vy * vy);
if (m !== CAM && m !== LENS && m !== SIGN) (cr *= vig), (cg *= vig), (cb *= vig);
// 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 peak = Math.max(cr, cg, cb, 1e-4);
const level = 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(cb * 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");
};
}