Upload index.html
Browse files- index.html +92 -12
index.html
CHANGED
|
@@ -22,10 +22,11 @@
|
|
| 22 |
<script>
|
| 23 |
(() => {
|
| 24 |
"use strict";
|
| 25 |
-
// ============ Aquarium
|
| 26 |
-
// Based on v10
|
| 27 |
-
//
|
| 28 |
-
//
|
|
|
|
| 29 |
const canvas = document.getElementById("tank");
|
| 30 |
const ctx = canvas.getContext("2d");
|
| 31 |
let W = 0, H = 0;
|
|
@@ -41,7 +42,8 @@ function lerpAngle(a, b, t) {
|
|
| 41 |
}
|
| 42 |
|
| 43 |
const HUES = [8, 35, 46, 172, 195, 215, 262, 320];
|
| 44 |
-
|
|
|
|
| 45 |
let curNight = 0; // v7: shared night factor (dims the top-light + sheen)
|
| 46 |
let plantPoints = []; // collision points along every stalk/branch, rebuilt each frame
|
| 47 |
let sandHeights = [];
|
|
@@ -54,6 +56,7 @@ class Fish {
|
|
| 54 |
this.y = rand(H * 0.15, H * 0.72);
|
| 55 |
this.angle = Math.random() < 0.5 ? rand(-0.4, 0.4) : Math.PI + rand(-0.4, 0.4);
|
| 56 |
this.hue = pick(HUES);
|
|
|
|
| 57 |
this.phase = rand(0, Math.PI * 2);
|
| 58 |
// v9: after dark, most fish bed down low in the grass β only a small share still
|
| 59 |
// hovers by the seaweed, and each of those keeps to its own stalk (no pile-ups)
|
|
@@ -286,15 +289,33 @@ class Fish {
|
|
| 286 |
// tail fin follows the body wave at its base, amplified (same phase β no fighting)
|
| 287 |
const sway = Math.sin(-s * waveK + this.phase) * s * 0.34;
|
| 288 |
|
| 289 |
-
// tail fin (v7
|
| 290 |
-
//
|
|
|
|
| 291 |
c.fillStyle = `hsla(${this.hue},75%,52%,0.92)`;
|
| 292 |
c.beginPath();
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 298 |
c.fill();
|
| 299 |
|
| 300 |
// body β undulates while swimming: the ellipse flexes along a traveling wave (v3)
|
|
@@ -471,6 +492,8 @@ function buildScene() {
|
|
| 471 |
rays = [];
|
| 472 |
rocks = [];
|
| 473 |
sandHeights = [];
|
|
|
|
|
|
|
| 474 |
|
| 475 |
// ALT: rolling hills instead of a flat bed β three octaves of random sine waves
|
| 476 |
// (broad swells + mid undulations + fine ripples), re-rolled on every refresh.
|
|
@@ -566,6 +589,30 @@ function buildScene() {
|
|
| 566 |
}
|
| 567 |
}
|
| 568 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 569 |
const nFish = Math.max(7, Math.floor(W / 130));
|
| 570 |
for (let i = 0; i < nFish; i++) fishes.push(new Fish());
|
| 571 |
|
|
@@ -944,6 +991,37 @@ function drawGrass(t) {
|
|
| 944 |
}
|
| 945 |
}
|
| 946 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 947 |
function drawVignette() {
|
| 948 |
const g = ctx.createRadialGradient(W / 2, H * 0.45, Math.min(W, H) * 0.35, W / 2, H * 0.55, Math.max(W, H) * 0.78);
|
| 949 |
g.addColorStop(0, "rgba(0,0,0,0)");
|
|
@@ -1064,6 +1142,8 @@ function frame(now) {
|
|
| 1064 |
b.draw(ctx);
|
| 1065 |
}
|
| 1066 |
|
|
|
|
|
|
|
| 1067 |
// ambient day/night wash over the whole scene
|
| 1068 |
if (light.washA > 0.005) {
|
| 1069 |
ctx.fillStyle = `rgba(${light.washRGB[0]},${light.washRGB[1]},${light.washRGB[2]},${light.washA})`;
|
|
|
|
| 22 |
<script>
|
| 23 |
(() => {
|
| 24 |
"use strict";
|
| 25 |
+
// ============ Aquarium v12 ALT-FG β hilly terrain + foreground grass (50% size) =====
|
| 26 |
+
// Based on v10 ALT (rolling-hills sand bed), plus front-glass foreground grass planted
|
| 27 |
+
// in the low band below the sand line and drawn on top of everything. The fg clumps are
|
| 28 |
+
// 50% smaller than the original fg version: half height, half width, half spread β
|
| 29 |
+
// so they read as near-glass flora without swallowing the hilly scene.
|
| 30 |
const canvas = document.getElementById("tank");
|
| 31 |
const ctx = canvas.getContext("2d");
|
| 32 |
let W = 0, H = 0;
|
|
|
|
| 42 |
}
|
| 43 |
|
| 44 |
const HUES = [8, 35, 46, 172, 195, 215, 262, 320];
|
| 45 |
+
const TAIL_TYPES = ['classic', 'fan', 'streamer']; // v11: three tail-fin styles, one per fish
|
| 46 |
+
let fishes = [], bubbles = [], seaweeds = [], motes = [], rays = [], rocks = [], grass = [], fgGrass = [];
|
| 47 |
let curNight = 0; // v7: shared night factor (dims the top-light + sheen)
|
| 48 |
let plantPoints = []; // collision points along every stalk/branch, rebuilt each frame
|
| 49 |
let sandHeights = [];
|
|
|
|
| 56 |
this.y = rand(H * 0.15, H * 0.72);
|
| 57 |
this.angle = Math.random() < 0.5 ? rand(-0.4, 0.4) : Math.PI + rand(-0.4, 0.4);
|
| 58 |
this.hue = pick(HUES);
|
| 59 |
+
this.tailType = pick(TAIL_TYPES); // v11: random tail-fin style for variety
|
| 60 |
this.phase = rand(0, Math.PI * 2);
|
| 61 |
// v9: after dark, most fish bed down low in the grass β only a small share still
|
| 62 |
// hovers by the seaweed, and each of those keeps to its own stalk (no pile-ups)
|
|
|
|
| 289 |
// tail fin follows the body wave at its base, amplified (same phase β no fighting)
|
| 290 |
const sway = Math.sin(-s * waveK + this.phase) * s * 0.34;
|
| 291 |
|
| 292 |
+
// tail fin (v7 rounded / v11 three styles): each tip is swept around by a curve
|
| 293 |
+
// whose control point sits just past where the old spike was. The style is fixed
|
| 294 |
+
// per fish at spawn so the school has variety in its tails.
|
| 295 |
c.fillStyle = `hsla(${this.hue},75%,52%,0.92)`;
|
| 296 |
c.beginPath();
|
| 297 |
+
if (this.tailType === 'fan') {
|
| 298 |
+
// Broad, rounded fan (betta/goldfish-style): wide spread, tips high and far out,
|
| 299 |
+
// with only a shallow dip between the two lobes.
|
| 300 |
+
c.moveTo(-s * 0.5, 0);
|
| 301 |
+
c.quadraticCurveTo(-s * 1.3, -s * 0.28 + sway, -s * 1.42, -s * 0.5 + sway); // up to upper tip
|
| 302 |
+
c.quadraticCurveTo(-s * 1.12, sway * 0.5, -s * 1.42, s * 0.5 + sway); // shallow notch β lower tip
|
| 303 |
+
c.quadraticCurveTo(-s * 1.3, s * 0.28 + sway, -s * 0.5, 0); // back to the base
|
| 304 |
+
} else if (this.tailType === 'streamer') {
|
| 305 |
+
// Long forked streamers (angelfish/fancy-guppy-style): two flowing lobes that
|
| 306 |
+
// reach further back with a deeper notch between them.
|
| 307 |
+
c.moveTo(-s * 0.5, 0);
|
| 308 |
+
c.quadraticCurveTo(-s * 1.35, -s * 0.24 + sway, -s * 1.68, -s * 0.56 + sway); // long upper lobe
|
| 309 |
+
c.quadraticCurveTo(-s * 0.98, sway * 0.35, -s * 1.68, s * 0.56 + sway); // deep fork β lower tip
|
| 310 |
+
c.quadraticCurveTo(-s * 1.35, s * 0.24 + sway, -s * 0.5, 0); // long lower lobe back to base
|
| 311 |
+
} else {
|
| 312 |
+
// classic (v7): rounded lobes with a soft notch β the original look
|
| 313 |
+
c.moveTo(-s * 0.5, 0);
|
| 314 |
+
c.quadraticCurveTo(-s * 1.04, -s * 0.36 + sway, -s * 1.22, -s * 0.44 + sway); // upper edge out toward the tip
|
| 315 |
+
c.quadraticCurveTo(-s * 1.34, -s * 0.52 + sway, -s * 0.86, sway * 0.5); // rounded upper lobe β notch
|
| 316 |
+
c.quadraticCurveTo(-s * 1.34, s * 0.52 + sway, -s * 1.22, s * 0.44 + sway); // notch β rounded lower lobe
|
| 317 |
+
c.quadraticCurveTo(-s * 1.04, s * 0.36 + sway, -s * 0.5, 0); // lower edge back to the base
|
| 318 |
+
}
|
| 319 |
c.fill();
|
| 320 |
|
| 321 |
// body β undulates while swimming: the ellipse flexes along a traveling wave (v3)
|
|
|
|
| 492 |
rays = [];
|
| 493 |
rocks = [];
|
| 494 |
sandHeights = [];
|
| 495 |
+
grass = []; // v10.1: clear on rebuild so resize/F11 doesn't stack a second row
|
| 496 |
+
fgGrass = []; // v12: same reset for the foreground layer (resize/F11 safety)
|
| 497 |
|
| 498 |
// ALT: rolling hills instead of a flat bed β three octaves of random sine waves
|
| 499 |
// (broad swells + mid undulations + fine ripples), re-rolled on every refresh.
|
|
|
|
| 589 |
}
|
| 590 |
}
|
| 591 |
|
| 592 |
+
// v12: foreground grass β planted on the FRONT glass (closest to the viewer), so it is
|
| 593 |
+
// drawn last and foreshortened. It grows in the low band below the sand line, i.e. the
|
| 594 |
+
// zone fish never reach, and sways with the water like the other flora but has no
|
| 595 |
+
// fish-push physics (purely decorative). ALT-FG: all sizes halved vs the original fg.
|
| 596 |
+
const nFgClusters = Math.max(8, Math.floor(W / 120));
|
| 597 |
+
for (let i = 0; i < nFgClusters; i++) {
|
| 598 |
+
const cx = rand(-10, W + 10);
|
| 599 |
+
const clusterBaseY = sandAt(cx) + rand(4, H * 0.05); // rooted just in front of the sand bed
|
| 600 |
+
const baseHue = rand(46, 76); // v12.1: yellowish-green β pops against the mid-ground green grass
|
| 601 |
+
const nBlades = 3 + ((Math.random() * 4) | 0); // 3..6 blades per clump
|
| 602 |
+
for (let b = 0; b < nBlades; b++) {
|
| 603 |
+
fgGrass.push({
|
| 604 |
+
x: cx + rand(-7.5, 7.5), // 50%: tighter clumps (was -15..15)
|
| 605 |
+
y: clusterBaseY + rand(-2, 4), // 50%: finer vertical jitter (was -4..8)
|
| 606 |
+
h: rand(H * 0.08, H * 0.18), // 50%: half height (was H*0.16..H*0.36)
|
| 607 |
+
lean: rand(-0.25, 0.25), // base tilt from vertical
|
| 608 |
+
phase: rand(0, Math.PI * 2),
|
| 609 |
+
speed: rand(0.8, 1.6), // slower, heavier sway than the tiny mid-ground grass
|
| 610 |
+
hue: baseHue + rand(-10, 10),
|
| 611 |
+
width: rand(1.75, 3.5) // 50%: half thickness (was 3.5..7)
|
| 612 |
+
});
|
| 613 |
+
}
|
| 614 |
+
}
|
| 615 |
+
|
| 616 |
const nFish = Math.max(7, Math.floor(W / 130));
|
| 617 |
for (let i = 0; i < nFish; i++) fishes.push(new Fish());
|
| 618 |
|
|
|
|
| 991 |
}
|
| 992 |
}
|
| 993 |
|
| 994 |
+
// v12: foreground grass sways with the water β decorative only, no physics.
|
| 995 |
+
// Drawn on top of everything else (it is closest to the viewer). Each blade gets a
|
| 996 |
+
// thin lighter inner stroke over a darker body for a lush, voluminous "closer" look.
|
| 997 |
+
function drawFgGrass(t) {
|
| 998 |
+
ctx.lineCap = "round";
|
| 999 |
+
for (const g of fgGrass) {
|
| 1000 |
+
const amp = 4 + g.h * 0.03; // taller blades swing wider at the tip
|
| 1001 |
+
const sway = Math.sin(t * g.speed + g.phase) * amp;
|
| 1002 |
+
const tipX = g.x + g.lean * g.h + sway;
|
| 1003 |
+
const tipY = g.y - g.h;
|
| 1004 |
+
const cpx = (g.x + tipX) / 2 + sway * 0.5;
|
| 1005 |
+
const cpy = (g.y + tipY) / 2;
|
| 1006 |
+
|
| 1007 |
+
// dark body of the blade (in front)
|
| 1008 |
+
ctx.strokeStyle = `hsla(${g.hue},60%,31%,0.96)`;
|
| 1009 |
+
ctx.lineWidth = g.width;
|
| 1010 |
+
ctx.beginPath();
|
| 1011 |
+
ctx.moveTo(g.x, g.y);
|
| 1012 |
+
ctx.quadraticCurveTo(cpx, cpy, tipX, tipY);
|
| 1013 |
+
ctx.stroke();
|
| 1014 |
+
|
| 1015 |
+
// lighter inner highlight for depth (cheap: one extra thin stroke)
|
| 1016 |
+
ctx.strokeStyle = `hsla(${g.hue + 6},64%,45%,0.5)`;
|
| 1017 |
+
ctx.lineWidth = g.width * 0.45;
|
| 1018 |
+
ctx.beginPath();
|
| 1019 |
+
ctx.moveTo(g.x, g.y);
|
| 1020 |
+
ctx.quadraticCurveTo(cpx, cpy, tipX, tipY);
|
| 1021 |
+
ctx.stroke();
|
| 1022 |
+
}
|
| 1023 |
+
}
|
| 1024 |
+
|
| 1025 |
function drawVignette() {
|
| 1026 |
const g = ctx.createRadialGradient(W / 2, H * 0.45, Math.min(W, H) * 0.35, W / 2, H * 0.55, Math.max(W, H) * 0.78);
|
| 1027 |
g.addColorStop(0, "rgba(0,0,0,0)");
|
|
|
|
| 1142 |
b.draw(ctx);
|
| 1143 |
}
|
| 1144 |
|
| 1145 |
+
drawFgGrass(t); // v12: foreground grass β closest to the viewer, drawn on top
|
| 1146 |
+
|
| 1147 |
// ambient day/night wash over the whole scene
|
| 1148 |
if (light.washA > 0.005) {
|
| 1149 |
ctx.fillStyle = `rgba(${light.washRGB[0]},${light.washRGB[1]},${light.washRGB[2]},${light.washA})`;
|