11<script setup>
22import { onMounted , onBeforeUnmount , ref } from ' vue'
33
4- // Floating isometric voxel blocks — the unmistakable Minecraft cube.
5- // Grass / dirt / stone / oak / diamond / gold drift slowly upward and wrap.
4+ // Underwater ambiance: a field of bubbles rising and gently wobbling.
65// Pure canvas, no deps.
76const canvas = ref (null )
87let ctx = null
98let rafId = 0
10- let blocks = []
9+ let bubbles = []
1110let width = 0
1211let height = 0
1312let dpr = 1
1413let running = false
1514
16- // top / left / right face shades per block type (Minecraft-ish palette)
17- const PALETTES = [
18- { top: ' #7bc043' , left: ' #9c6b43' , right: ' #7d5436' }, // grass
19- { top: ' #9c6b43' , left: ' #825736' , right: ' #69472f' }, // dirt
20- { top: ' #a8a8a8' , left: ' #8a8a8a' , right: ' #6f6f6f' }, // stone
21- { top: ' #c2954f' , left: ' #a87c3d' , right: ' #8a6530' }, // oak planks
22- { top: ' #5fd6cb' , left: ' #48b3ab' , right: ' #369089' }, // diamond
23- { top: ' #f2cb4d' , left: ' #d4ab33' , right: ' #b08c24' }, // gold
24- ]
25-
2615let seed = 7
2716function rnd () {
2817 seed = (seed * 1664525 + 1013904223 ) % 4294967296
2918 return seed / 4294967296
3019}
3120
32- function makeBlock (initial ) {
33- const depth = 0.35 + rnd () * 0.65 // 0..1, drives size/speed/opacity (parallax )
21+ function makeBubble (initial ) {
22+ const depth = rnd ()
3423 return {
3524 x: rnd () * width,
36- y: initial ? rnd () * height : height + 60 ,
37- size : 12 + depth * 30 ,
38- speed: 8 + depth * 22 , // px per second, upward
39- drift: (rnd () - 0.5 ) * 0.6 , // gentle horizontal sway amount
25+ y: initial ? rnd () * height : height + 20 ,
26+ r : 1.5 + depth * 5 ,
27+ speed: 25 + depth * 55 , // px per second, rising
28+ drift: (rnd () - 0.5 ) * 0.8 ,
4029 phase: rnd () * Math .PI * 2 ,
41- spin: 0.3 + rnd () * 0.5 , // sway frequency
42- opacity: 0.35 + depth * 0.45 ,
43- palette: PALETTES [Math .floor (rnd () * PALETTES .length )],
30+ spin: 0.8 + rnd () * 1.2 ,
31+ opacity: 0.12 + depth * 0.28 ,
4432 }
4533}
4634
@@ -54,59 +42,28 @@ function resize() {
5442 canvas .value .height = height * dpr
5543 ctx .setTransform (dpr, 0 , 0 , dpr, 0 , 0 )
5644
57- const target = Math .min (20 , Math .max (10 , Math .round ((width * height) / 42000 )))
58- if (blocks .length < target) {
59- for (let i = blocks .length ; i < target; i++ ) blocks .push (makeBlock (true ))
45+ const target = Math .min (46 , Math .max (16 , Math .round ((width * height) / 20000 )))
46+ if (bubbles .length < target) {
47+ for (let i = bubbles .length ; i < target; i++ ) bubbles .push (makeBubble (true ))
6048 } else {
61- blocks .length = target
49+ bubbles .length = target
6250 }
63- // keep painter's order: far (small) drawn first
64- blocks .sort ((a , b ) => a .size - b .size )
6551}
6652
67- function drawCube (cx , cy , s , pal , opacity ) {
68- const a = s // horizontal half-width
69- const b = s / 2 // vertical half of top diamond
70- const vh = s // vertical edge length
71-
53+ function drawBubble (cx , cy , r , opacity ) {
7254 ctx .globalAlpha = opacity
73- ctx .lineJoin = ' round'
74- ctx .strokeStyle = ' rgba(0,0,0,0.25)'
75- ctx .lineWidth = 1
76-
77- // top face (diamond)
78- ctx .beginPath ()
79- ctx .moveTo (cx, cy)
80- ctx .lineTo (cx + a, cy + b)
81- ctx .lineTo (cx, cy + 2 * b)
82- ctx .lineTo (cx - a, cy + b)
83- ctx .closePath ()
84- ctx .fillStyle = pal .top
85- ctx .fill ()
86- ctx .stroke ()
87-
88- // left face
8955 ctx .beginPath ()
90- ctx .moveTo (cx - a, cy + b)
91- ctx .lineTo (cx, cy + 2 * b)
92- ctx .lineTo (cx, cy + 2 * b + vh)
93- ctx .lineTo (cx - a, cy + b + vh)
94- ctx .closePath ()
95- ctx .fillStyle = pal .left
56+ ctx .arc (cx, cy, r, 0 , Math .PI * 2 )
57+ ctx .fillStyle = ' rgba(220, 245, 255, 0.35)'
9658 ctx .fill ()
59+ ctx .lineWidth = 1
60+ ctx .strokeStyle = ' rgba(190, 235, 255, 0.7)'
9761 ctx .stroke ()
98-
99- // right face
62+ // little highlight
10063 ctx .beginPath ()
101- ctx .moveTo (cx + a, cy + b)
102- ctx .lineTo (cx, cy + 2 * b)
103- ctx .lineTo (cx, cy + 2 * b + vh)
104- ctx .lineTo (cx + a, cy + b + vh)
105- ctx .closePath ()
106- ctx .fillStyle = pal .right
64+ ctx .arc (cx - r * 0.3 , cy - r * 0.3 , Math .max (0.6 , r * 0.28 ), 0 , Math .PI * 2 )
65+ ctx .fillStyle = ' rgba(255, 255, 255, 0.8)'
10766 ctx .fill ()
108- ctx .stroke ()
109-
11067 ctx .globalAlpha = 1
11168}
11269
@@ -118,15 +75,12 @@ function step(ts) {
11875
11976 ctx .clearRect (0 , 0 , width, height)
12077
121- for (const blk of blocks) {
122- blk .y -= blk .speed * dt
123- blk .phase += blk .spin * dt
124- const sway = Math .sin (blk .phase ) * blk .drift * 20
125- if (blk .y + blk .size * 2 < - 20 ) {
126- // recycle from the bottom
127- Object .assign (blk, makeBlock (false ))
128- }
129- drawCube (blk .x + sway, blk .y , blk .size , blk .palette , blk .opacity )
78+ for (const bub of bubbles) {
79+ bub .y -= bub .speed * dt
80+ bub .phase += bub .spin * dt
81+ if (bub .y + bub .r * 2 < - 20 ) Object .assign (bub, makeBubble (false ))
82+ const sway = Math .sin (bub .phase ) * bub .drift * 14
83+ drawBubble (bub .x + sway, bub .y , bub .r , bub .opacity )
13084 }
13185
13286 rafId = requestAnimationFrame (step)
@@ -146,7 +100,7 @@ function stop() {
146100
147101function drawStatic () {
148102 ctx .clearRect (0 , 0 , width, height)
149- for (const blk of blocks) drawCube ( blk .x , blk .y , blk . size , blk . palette , blk .opacity )
103+ for (const bub of bubbles) drawBubble ( bub .x , bub .y , bub . r , bub .opacity )
150104}
151105
152106let onResize = null
0 commit comments