One commited on
Commit
149084f
·
verified ·
1 Parent(s): ec46511

Upload index.html

Browse files
Files changed (1) hide show
  1. index.html +59 -21
index.html CHANGED
@@ -514,6 +514,36 @@ class Fish {
514
  }
515
  c.fill();
516
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
517
  // body — undulates while swimming: the ellipse flexes along a traveling wave (v3)
518
  const g = this.bodyGrad; // precomputed in constructor (static colors, local coords)
519
  const NSEG = 26; // outline samples for a smooth curve
@@ -655,33 +685,29 @@ class Fish {
655
  c.arc(s * 0.61, -s * 0.1, s * 0.055, 0, Math.PI * 2);
656
  c.fill();
657
 
658
- // v16/v17: whiskers (barbels) — a pair from the snout on ~10% of fish, in one of
659
- // three lengths, drawn in fish-local space so they track the heading.
660
- // v18: loose ribbons flowing in the current — they trail back opposite the motion
661
- // and a traveling wave runs down their length as the fish swims. The faster it
662
- // swims, the further back they stream and the livelier they ripple; at rest they
663
- // droop softly. Drawn at a fixed 50% opacity.
664
  if (this.whiskerLen) {
665
- const L = this.whiskerLen * s; // chosen length tier at spawn
666
- const spd = Math.min(1, this.speedFactor); // current speed multiplier
667
- const NPTS = 7; // sample points per whisker
668
  c.save();
669
  c.lineCap = "round";
670
  c.strokeStyle = this.whiskerStyle;
671
  c.lineWidth = Math.max(1, s * 0.045);
672
- for (const side of [-1, 1]) { // one whisker each side
 
673
  const rootX = s * 0.6;
674
- const rootY = side * s * 0.09; // just off the midline at the snout
675
  c.beginPath();
676
  c.moveTo(rootX, rootY);
677
  for (let i = 1; i <= NPTS; i++) {
678
- const t = i / NPTS; // 0 at root → 1 at tip
679
- // base flow: streams back with the current as the fish moves forward,
680
- // drooping gently outward, easing out toward the loose tip
681
  const bx = rootX - L * (0.25 + 0.65 * spd) * t;
682
  const by = rootY + side * L * 0.42 * t * t;
683
- // traveling wave: ripples flow down the whisker (root → tip), loosest at the
684
- // free end; this.phase advances with swim speed so they live up as it moves
685
  const amp = s * 0.17 * t * (0.35 + 0.65 * spd);
686
  const off = Math.sin(t * 5.5 - this.phase * 1.4 + this.whiskPh) * amp;
687
  c.lineTo(bx, by + off);
@@ -772,6 +798,15 @@ function strokeArc(c, pts) {
772
  c.stroke();
773
  }
774
 
 
 
 
 
 
 
 
 
 
775
  class Bubble {
776
  constructor(init, x, y, size) {
777
  if (x !== undefined) { // spawned from a fish's mouth: tiny, one-shot
@@ -780,6 +815,7 @@ class Bubble {
780
  this.r = rand(1.4, 3.2) * s;
781
  this.speed = rand(45, 85) * (0.7 + 0.3 * s);
782
  this.p = rand(0, Math.PI * 2);
 
783
  this.isMouth = true;
784
  } else {
785
  this.reset(init);
@@ -791,6 +827,7 @@ class Bubble {
791
  this.r = rand(2, 7);
792
  this.speed = rand(35, 90);
793
  this.p = rand(0, Math.PI * 2);
 
794
  this.isMouth = false;
795
  }
796
  update(dt, t) {
@@ -799,14 +836,15 @@ class Bubble {
799
  if (this.y < -12) { if (this.isMouth) this.dead = true; else this.reset(false); }
800
  }
801
  draw(c) {
 
802
  c.lineWidth = 1;
803
- c.strokeStyle = "rgba(255,255,255,0.35)";
804
- c.fillStyle = "rgba(255,255,255,0.08)";
805
  c.beginPath();
806
  c.arc(this.x, this.y, this.r, 0, Math.PI * 2);
807
  c.fill();
808
  c.stroke();
809
- c.fillStyle = "rgba(255,255,255,0.45)";
810
  c.beginPath();
811
  c.arc(this.x - this.r * 0.3, this.y - this.r * 0.3, this.r * 0.25, 0, Math.PI * 2);
812
  c.fill();
@@ -1000,9 +1038,9 @@ function buildScene() {
1000
  }
1001
 
1002
  for (let i = 0; i < 5; i++) {
1003
- // random blur state: 0px (as-is), ~3px (semi-blurry), or ~7px (blurry) for visual variety
1004
  const blurRoll = Math.random();
1005
- const blur = blurRoll < 0.34 ? 0 : (blurRoll < 0.67 ? 3 : 7);
1006
  rays.push({
1007
  x: rand(0, W), w: rand(80, 210), tilt: rand(-0.35, 0.35),
1008
  speed: rand(0.06, 0.14) * (Math.random() < 0.5 ? 1 : -1), alpha: rand(0.04, 0.09), blur,
 
514
  }
515
  c.fill();
516
 
517
+ // v21: far-side whisker — drawn BEFORE the body so it sits behind the fish and is
518
+ // partially obscured by the head (only the part that pokes out from behind the snout
519
+ // shows). The near-side whisker is drawn after the eye, below.
520
+ if (this.whiskerLen) {
521
+ const L = this.whiskerLen * s;
522
+ const spd = Math.min(1, this.speedFactor);
523
+ const NPTS = 7;
524
+ c.save();
525
+ c.lineCap = "round";
526
+ c.strokeStyle = this.whiskerStyle;
527
+ c.lineWidth = Math.max(1, s * 0.045);
528
+ {
529
+ const side = -1; // far side (behind the body)
530
+ const rootX = s * 0.6;
531
+ const rootY = side * s * 0.09;
532
+ c.beginPath();
533
+ c.moveTo(rootX, rootY);
534
+ for (let i = 1; i <= NPTS; i++) {
535
+ const t = i / NPTS;
536
+ const bx = rootX - L * (0.25 + 0.65 * spd) * t;
537
+ const by = rootY + side * L * 0.42 * t * t;
538
+ const amp = s * 0.17 * t * (0.35 + 0.65 * spd);
539
+ const off = Math.sin(t * 5.5 - this.phase * 1.4 + this.whiskPh) * amp;
540
+ c.lineTo(bx, by + off);
541
+ }
542
+ c.stroke();
543
+ }
544
+ c.restore();
545
+ }
546
+
547
  // body — undulates while swimming: the ellipse flexes along a traveling wave (v3)
548
  const g = this.bodyGrad; // precomputed in constructor (static colors, local coords)
549
  const NSEG = 26; // outline samples for a smooth curve
 
685
  c.arc(s * 0.61, -s * 0.1, s * 0.055, 0, Math.PI * 2);
686
  c.fill();
687
 
688
+ // v21: near-side whisker — drawn AFTER the body so it sits in front of the fish.
689
+ // (The far-side whisker is drawn before the body, above.) Together they form a
690
+ // pair from the snout on ~10% of fish, in one of three lengths, with loose ribbons
691
+ // flowing in the current and a traveling wave rippling down their length. Drawn at
692
+ // a fixed 50% opacity.
 
693
  if (this.whiskerLen) {
694
+ const L = this.whiskerLen * s;
695
+ const spd = Math.min(1, this.speedFactor);
696
+ const NPTS = 7;
697
  c.save();
698
  c.lineCap = "round";
699
  c.strokeStyle = this.whiskerStyle;
700
  c.lineWidth = Math.max(1, s * 0.045);
701
+ {
702
+ const side = 1; // near side (in front of the body)
703
  const rootX = s * 0.6;
704
+ const rootY = side * s * 0.09;
705
  c.beginPath();
706
  c.moveTo(rootX, rootY);
707
  for (let i = 1; i <= NPTS; i++) {
708
+ const t = i / NPTS;
 
 
709
  const bx = rootX - L * (0.25 + 0.65 * spd) * t;
710
  const by = rootY + side * L * 0.42 * t * t;
 
 
711
  const amp = s * 0.17 * t * (0.35 + 0.65 * spd);
712
  const off = Math.sin(t * 5.5 - this.phase * 1.4 + this.whiskPh) * amp;
713
  c.lineTo(bx, by + off);
 
798
  c.stroke();
799
  }
800
 
801
+ // v19: each bubble gets a subtle random tint — pure white up to a soft grey-blue
802
+ function bubbleColor() {
803
+ const t = Math.random() * 0.85; // keep it subtle, most stay near-white
804
+ const r = Math.round(255 - (255 - 196) * t);
805
+ const g = Math.round(255 - (255 - 214) * t);
806
+ const b = Math.round(255 - (255 - 238) * t);
807
+ return [r, g, b];
808
+ }
809
+
810
  class Bubble {
811
  constructor(init, x, y, size) {
812
  if (x !== undefined) { // spawned from a fish's mouth: tiny, one-shot
 
815
  this.r = rand(1.4, 3.2) * s;
816
  this.speed = rand(45, 85) * (0.7 + 0.3 * s);
817
  this.p = rand(0, Math.PI * 2);
818
+ this.rgb = bubbleColor();
819
  this.isMouth = true;
820
  } else {
821
  this.reset(init);
 
827
  this.r = rand(2, 7);
828
  this.speed = rand(35, 90);
829
  this.p = rand(0, Math.PI * 2);
830
+ this.rgb = bubbleColor();
831
  this.isMouth = false;
832
  }
833
  update(dt, t) {
 
836
  if (this.y < -12) { if (this.isMouth) this.dead = true; else this.reset(false); }
837
  }
838
  draw(c) {
839
+ const [r, g, b] = this.rgb;
840
  c.lineWidth = 1;
841
+ c.strokeStyle = `rgba(${r},${g},${b},0.35)`;
842
+ c.fillStyle = `rgba(${r},${g},${b},0.08)`;
843
  c.beginPath();
844
  c.arc(this.x, this.y, this.r, 0, Math.PI * 2);
845
  c.fill();
846
  c.stroke();
847
+ c.fillStyle = `rgba(${r},${g},${b},0.45)`;
848
  c.beginPath();
849
  c.arc(this.x - this.r * 0.3, this.y - this.r * 0.3, this.r * 0.25, 0, Math.PI * 2);
850
  c.fill();
 
1038
  }
1039
 
1040
  for (let i = 0; i < 5; i++) {
1041
+ // random blur state: ~3px (semi-blurry), ~7px (blurry), or ~14px (very blurry) for visual variety
1042
  const blurRoll = Math.random();
1043
+ const blur = blurRoll < 0.34 ? 3 : (blurRoll < 0.67 ? 7 : 14);
1044
  rays.push({
1045
  x: rand(0, W), w: rand(80, 210), tilt: rand(-0.35, 0.35),
1046
  speed: rand(0.06, 0.14) * (Math.random() < 0.5 ? 1 : -1), alpha: rand(0.04, 0.09), blur,