Skip to content

Commit d4ae4db

Browse files
vinwa
1 parent bcdfd68 commit d4ae4db

13 files changed

Lines changed: 90 additions & 18 deletions

‎ARCHITECTURE.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ separate, later phase (never mixed into structural moves).
2525
|---|---|
2626
| a creature (new or existing) | `animals/animals.js` + that creature's file + `core/scheduler.js` + `css/animals.css` |
2727
| deer | `animals/deer.js` + garden host API in `garden/garden.js` |
28+
| page snails (tap, peek, crawl, trail) | `animals/snails.js` (+ `weather.onRain` hook) |
2829
| birds / nest / seed feeding | `animals/birds.js` (guide bird: `animals/guide-bird.js`) |
2930
| weather, rain, sun, wind | `environment/weather.js` or `breeze.js` + `css/environment.css` |
3031
| flowers on the page (turning, touch, pollen) | `plants/flowers.js` |
@@ -238,6 +239,7 @@ bouquet and ribbon, ways-in links, "seed of curiosity". Not: the footer garden,
238239
- `birds.js`: simple variations of "a bird visits something": vine bird, flyby birds, the world bird (seed feeding, petal theft) and the nest they build.
239240
- `guide-bird.js`: the red banner bird (own spot-finding, scroll re-check, bubble). Independent logic, own file.
240241
- `vine-caterpillar.js`: crawls along vine paths (own physics/rAF/eating). `caterpillar.js`: the story twig caterpillar → chrysalis → butterfly life cycle.
242+
- `snails.js`: a few tappable snails resting in open margins (hide/peek/emerge, crawl to a nearby plant with a fading dew trail); reacts to rain through `weather.onRain`.
241243
- `butterflies.js`: drifting butterfly, vine butterflies, visitor butterfly/bee, tiny touch-butterfly. `deer.js`: the garden deer.
242244

243245
**environment/** `weather.js`: rain clouds (garden and page), sun toggle, rainbow after rain. `breeze.js`: cursor-driven forces on plants

‎css/08-garden-game-v7-v8.css‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
/* click the side vines: flowers grow on evenly spaced spots, then fade */
1313
.vine-hit { position: fixed; top: 0; width: 84px; height: 100vh; z-index: 4; display: block; cursor: pointer; -webkit-tap-highlight-color: transparent; }
1414
.vine-hit.left { left: 8px; } .vine-hit.right { right: 8px; }
15-
@media (max-width: 1239px) { .vine-hit { width: 20px; } .vine-hit.left { left: 0; } .vine-hit.right { right: 0; } }
15+
@media (max-width: 1239px) { .vine-hit { width: 38px; } .vine-hit.left { left: 0; } .vine-hit.right { right: 0; } }
1616
.vine-tip { position: fixed; left: 0; top: 0; z-index: 130; box-sizing: border-box; max-width: calc(100vw - 16px); overflow-wrap: anywhere; pointer-events: none; font-family: var(--font-hand); font-size: 1.1rem; color: var(--green); background: rgba(255, 255, 255, 0.9); padding: 0.1rem 0.75rem 0.2rem; border-radius: 20px; box-shadow: 0 4px 14px rgba(120, 72, 96, 0.12); opacity: 0; transition: opacity 0.3s; white-space: nowrap; }
1717
.vine-tip.show { opacity: 1; }
1818
.vine-sprout { transition: opacity 1.4s ease; }
@@ -242,7 +242,7 @@
242242
.g-badge .tiny-fl { width: 1.25em; height: 1.25em; animation: spinSlow 4s linear infinite; }
243243
.g-reset { font-family: var(--font-body); font-size: 0.72rem; color: var(--text-dim); background: none; border: 1px dashed rgba(140, 127, 146, 0.4); border-radius: 20px; padding: 0.15rem 0.7rem; cursor: pointer; transition: color 0.3s, border-color 0.3s; }
244244
.g-reset:hover { color: var(--cyan); border-color: var(--cyan); }
245-
.g-reset.armed { color: #fff; background: var(--cyan); border-color: var(--cyan); border-style: solid; }
245+
.g-reset.armed, .g-reset.armed:hover, .g-reset.armed:focus, .g-reset.armed:active { color: #fff; background: var(--cyan); border-color: var(--cyan); border-style: solid; }
246246
.g-badges { display: inline-flex; align-items: center; gap: 3px; }
247247
.g-bdg { display: inline-block; width: 17px; height: 17px; color: #cfc6d4; opacity: 0.5; filter: grayscale(1); cursor: help; transition: opacity 0.4s, filter 0.4s, transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); }
248248
.g-bdg svg { display: block; width: 100%; height: 100%; overflow: visible; --center: #fff; }

‎css/11-little-world.css‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,3 +194,46 @@
194194
.env-magic.is-still .env-glow { opacity: .24; transform: translate(-50%, -2px) scale(.95); }
195195
}
196196
@keyframes envFade { from { opacity: 0; } to { opacity: 1; } }
197+
198+
/* ---- snails (js/animals/snails.js) ---- */
199+
.snail { position: absolute; z-index: 3; width: 34px; height: 25px; cursor: pointer; touch-action: manipulation; -webkit-tap-highlight-color: transparent; --sh1: #f4b9a8; --sh2: #d98b7b; --bd: #f0e0cc; --pt: #f6a9c4; --sn-in: .45s; }
200+
.snail[hidden] { display: none; }
201+
.snail::after { content: ''; position: absolute; inset: -10px; } /* a forgiving tap area */
202+
.snail:focus-visible { outline: 2.5px solid var(--focus); outline-offset: 4px; border-radius: 8px; }
203+
.sn-flip { display: block; width: 100%; height: 100%; }
204+
.snail.is-left .sn-flip { transform: scaleX(-1); }
205+
.snail svg { display: block; width: 100%; height: 100%; overflow: visible; filter: drop-shadow(0 1px 1px rgba(110, 80, 50, .25)); }
206+
.sn-foot { fill: var(--bd); }
207+
.sn-stalk { fill: none; stroke: var(--bd); stroke-width: 1.6; stroke-linecap: round; }
208+
.sn-eye { fill: #5a4366; }
209+
.sn-shell { fill: var(--sh1); stroke: var(--sh2); stroke-width: .8; }
210+
.sn-spiral { fill: none; stroke: var(--sh2); stroke-width: 1.3; stroke-linecap: round; }
211+
.sn-hi { fill: #fff; opacity: .45; }
212+
.sn-dec { display: none; fill: var(--sh2); opacity: .8; }
213+
.sn-petal { display: none; fill: var(--pt); stroke: rgba(255, 255, 255, .6); stroke-width: .4; }
214+
.snail.is-deco .sn-dec, .snail.has-petal .sn-petal { display: block; }
215+
.snail.is-wet .sn-hi { opacity: .8; }
216+
.sn-body { transform-origin: 20px 26px; transition: transform var(--sn-in) ease-in-out, opacity var(--sn-in) ease-in-out; }
217+
.snail.is-hidden .sn-body { transform: translateX(4px) scale(.15, .4); opacity: 0; }
218+
.snail.is-peek .sn-body { transform: translateX(8px) scale(.6, .9); }
219+
.snail.is-peek .sn-eyes { transform: translateY(2px); }
220+
@media (prefers-reduced-motion: no-preference) {
221+
.snail.is-crawl .sn-body { animation: snStretch 1.7s ease-in-out infinite; }
222+
.snail:not(.is-hidden):not(.is-peek):not(.is-crawl) .sn-eyes { transform-origin: 38px 17px; animation: snLook 7s ease-in-out infinite; }
223+
.snail.is-new svg { animation: snNew 1.3s ease; }
224+
.sn-trail { animation: snTrail 3.6s ease-out forwards; }
225+
.sn-spark { animation: snSpark 2.4s ease-out forwards; }
226+
.sn-flour { animation: snFlour 6s ease-in-out forwards; }
227+
}
228+
@media (prefers-reduced-motion: reduce) { .sn-body { transition: none; } }
229+
.sn-trail { position: absolute; z-index: 2; width: 10px; height: 3px; opacity: 0; border-radius: 50%; pointer-events: none; background: radial-gradient(ellipse, rgba(255, 255, 255, .95), rgba(190, 215, 240, .6) 55%, transparent 78%); transform: rotate(var(--r, 0deg)); opacity: 0; }
230+
.sn-spark { position: absolute; z-index: 2; width: 7px; height: 7px; pointer-events: none; opacity: 0; clip-path: polygon(50% 0, 60% 40%, 100% 50%, 60% 60%, 50% 100%, 40% 60%, 0 50%, 40% 40%); }
231+
.sn-flour { position: absolute; z-index: 2; width: 16px; height: 16px; pointer-events: none; opacity: 0; }
232+
.sn-flour svg { display: block; width: 100%; height: 100%; overflow: visible; }
233+
@keyframes snStretch { 0%, 100% { transform: scaleX(1); } 50% { transform: scaleX(1.07); } }
234+
@keyframes snLook { 0%, 40%, 100% { transform: rotate(0); } 55% { transform: rotate(-5deg); } 75% { transform: rotate(4deg); } }
235+
@keyframes snNew { 0%, 100% { filter: drop-shadow(0 1px 1px rgba(110, 80, 50, .25)); } 40% { filter: drop-shadow(0 0 6px rgba(255, 230, 150, .95)) brightness(1.15); } }
236+
@keyframes snTrail { 0% { opacity: 0; scale: .6; } 12% { opacity: .85; scale: 1; } 100% { opacity: 0; scale: 1.25; } }
237+
@keyframes snSpark { 0% { opacity: 0; scale: .2; } 20% { opacity: 1; scale: 1; } 100% { opacity: 0; scale: .4; rotate: 50deg; } }
238+
@keyframes snFlour { 0% { opacity: 0; scale: .3; } 15% { opacity: .9; scale: 1; } 70% { opacity: .8; } 100% { opacity: 0; scale: 1.1; } }
239+
@media (max-width: 699px) { .snail { width: 28px; height: 21px; } }

‎css/14-side-vines-plants.css‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@
6767
.g-cat.is-bloom .g-art, .h-spec.is-bloom .h-art { --hv: 2; transform: translateY(-8px) rotate(calc(var(--sun, 0deg) - 1deg)) scale(1.06); transition: max-width 0.5s var(--glide), transform 0.28s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.4s ease, --hv 0.28s ease-out, --wd 1.8s ease-out; }
6868

6969
.pt { --k2: calc(var(--k, 1) * var(--a, 1)); transform-box: view-box; transform-origin: var(--ox, 50%) var(--oy, 50%); animation-duration: var(--t, 6s); animation-delay: var(--d, 0s); animation-iteration-count: infinite; animation-timing-function: ease-in-out; animation-play-state: var(--ps, running); }
70+
/* The hidden SVG sprite's <symbol> originals are never drawn (only their <use> copies are), yet their parts kept ticking these
71+
infinite animations forever (~120 of them), costing continuous style recalculation. Stopping the originals leaves the copies
72+
(which render in their own shadow trees and are not matched here) animating exactly as before. */
73+
symbol, symbol * { animation: none !important; }
7074
/* small flowers bob and tip (Research, Exploration, bud heads) */
7175
.hd.bob { animation-name: hdBob; scale: calc(1 + var(--hv) * 0.05); }
7276
@keyframes hdBob {

‎images/madisonbutchko_flower.jpg‎

-104 KB
Loading

‎images/madisonbutchko_yellow.jpg‎

-61.1 KB
Loading

‎images/madisonbutchko_yellow2.jpg‎

-39.9 KB
Loading

‎index.html‎

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@
1616
<link rel="stylesheet" href="css/05-fx-titles-gallery-cursor.css?v=2">
1717
<link rel="stylesheet" href="css/06-backgrounds-rainbow-gallery.css?v=2">
1818
<link rel="stylesheet" href="css/07-living-background-v4-v6.css?v=2">
19-
<link rel="stylesheet" href="css/08-garden-game-v7-v8.css?v=5">
19+
<link rel="stylesheet" href="css/08-garden-game-v7-v8.css?v=6">
2020
<link rel="stylesheet" href="css/09-v9-refinements.css?v=3">
2121
<link rel="stylesheet" href="css/10-botanical.css?v=3">
22-
<link rel="stylesheet" href="css/11-little-world.css?v=14">
22+
<link rel="stylesheet" href="css/11-little-world.css?v=15">
2323
<link rel="stylesheet" href="css/12-polish-pass.css?v=2">
2424
<link rel="stylesheet" href="css/13-ecosystem.css?v=2">
25-
<link rel="stylesheet" href="css/14-side-vines-plants.css?v=3">
25+
<link rel="stylesheet" href="css/14-side-vines-plants.css?v=4">
2626
</head>
2727
<body>
2828
<svg width="0" height="0" style="position:absolute" aria-hidden="true">
@@ -97,9 +97,9 @@ <h1>Madison <em>Butchko</em></h1>
9797
<div class="collage" id="collage">
9898
<div class="fan-halo" aria-hidden="true"></div>
9999
<div class="fan">
100-
<div class="profile-photo fan-card" data-slot="0"><img fetchpriority="high" src="images/madisonbutchko_flower.jpg" alt="Madison holding a bouquet of spring flowers"></div>
101-
<div class="profile-photo fan-card" data-slot="1"><img src="images/madisonbutchko_yellow.jpg" alt="Madison smiling in front of blooming forsythia"></div>
102-
<div class="profile-photo fan-card" data-slot="2"><img src="images/madisonbutchko_yellow2.jpg" alt="Madison on campus beside yellow forsythia"></div>
100+
<div class="profile-photo fan-card" data-slot="0"><img fetchpriority="high" decoding="async" src="images/madisonbutchko_flower.jpg" alt="Madison holding a bouquet of spring flowers"></div>
101+
<div class="profile-photo fan-card" data-slot="1"><img fetchpriority="low" decoding="async" src="images/madisonbutchko_yellow.jpg" alt="Madison smiling in front of blooming forsythia"></div>
102+
<div class="profile-photo fan-card" data-slot="2"><img fetchpriority="low" decoding="async" src="images/madisonbutchko_yellow2.jpg" alt="Madison on campus beside yellow forsythia"></div>
103103
</div>
104104
<div class="fan-dots" role="tablist" aria-label="Choose photo"><button class="on" aria-label="Photo 1"></button><button aria-label="Photo 2"></button><button aria-label="Photo 3"></button></div>
105105
</div>
@@ -732,21 +732,21 @@ <h2 class="contact-title">Let's <em>Connect</em></h2>
732732
<script src="js/navigation/navigation.js?v=2" defer></script>
733733
<script src="js/site/hero.js?v=1" defer></script>
734734
<script src="js/effects/petals.js?v=2" defer></script>
735-
<script src="js/plants/vines.js?v=4" defer></script>
735+
<script src="js/plants/vines.js?v=5" defer></script>
736736
<script src="js/effects/flower-fx.js?v=2" defer></script>
737737
<script src="js/navigation/scroll-sunflower.js?v=2" defer></script>
738738
<script src="js/plants/decor.js?v=3" defer></script>
739739
<script src="js/animals/art.js?v=2" defer></script>
740740
<script src="js/plants/plants.js?v=2" defer></script>
741-
<script src="js/plants/vine-sprigs.js?v=4" defer></script>
742-
<script src="js/environment/weather.js?v=4" defer></script>
741+
<script src="js/plants/vine-sprigs.js?v=5" defer></script>
742+
<script src="js/environment/weather.js?v=5" defer></script>
743743
<script src="js/garden/species.js?v=1" defer></script>
744744
<script src="js/garden/hud.js?v=1" defer></script>
745745
<script src="js/garden/critters.js?v=1" defer></script>
746746
<script src="js/animals/deer.js?v=2" defer></script>
747747
<script src="js/garden/garden.js?v=2" defer></script>
748748
<script src="js/animals/animals.js?v=1" defer></script>
749-
<script src="js/animals/guide-bird.js?v=1" defer></script>
749+
<script src="js/animals/guide-bird.js?v=2" defer></script>
750750
<script src="js/animals/birds.js?v=4" defer></script>
751751
<script src="js/animals/vine-caterpillar.js?v=1" defer></script>
752752
<script src="js/plants/seeds.js?v=2" defer></script>
@@ -763,6 +763,7 @@ <h2 class="contact-title">Let's <em>Connect</em></h2>
763763
<script src="js/plants/dandelions.js?v=2" defer></script>
764764
<script src="js/plants/flowers.js?v=4" defer></script>
765765
<script src="js/world/world.js?v=1" defer></script>
766-
<script src="js/main.js?v=5" defer></script>
766+
<script src="js/animals/snails.js?v=1" defer></script>
767+
<script src="js/main.js?v=6" defer></script>
767768
</body>
768769
</html>

‎js/animals/guide-bird.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ MB.define('animals.guide-bird', ['core.scheduler'], function (scheduler) {
4343
for (const px of xs) for (const py of ys){
4444
const hits = document.elementsFromPoint(Math.max(0, Math.min(innerWidth - 1, px)), Math.max(0, Math.min(innerHeight - 1, py)));
4545
const top = hits.find(h => !el.contains(h));
46-
if (top && top.closest(BLOCK)) n++;
46+
if (top && top.closest(BLOCK)) return 1; /* callers only ask "any block?" (n > 0, or n * 1000 where one block already rules a spot out), so stop at the first hit instead of hit-testing all 9 points */
4747
}
4848
return n;
4949
}

‎js/environment/weather.js‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
Purpose : the page's small rain cloud: rarely appears over a row of flowers; tap it for a gentle local shower (flowers bow and lift, sometimes a faint rainbow).
33
Owns : rainCloud() and its 5-minute cooldown; createGarden(ga): the garden bed's sun/rain cycle (spawnCloud, setSunny, weatherTick, CLOUD_SVG). (the cloud art is shared with the garden cloud through animals/art.js.)
44
Uses : plants.plants (FLI, tween: garden weather), core.utils ($, $$, rand, clamp, f1, reduce), core.scheduler (Life), core.safe-zones (inView, navBottom)
5+
Also : onRain(fn): hook called when a shower starts (animals/snails.js).
56
Used by : legacy/200-little-world.js (heartbeat's rare roll, ?worlddebug hook).
67
Mobile / reduced motion: nothing under prefers-reduced-motion (no cloud at all); the cloud is a click/Enter button, so touch works.
78
Moved verbatim from legacy/200 (Migration Step 10c); behaviour, order and timing unchanged. */
@@ -16,6 +17,10 @@ MB.define('environment.weather', ['core.utils', 'core.scheduler', 'core.safe-zon
1617
local shower, the flowers bow and lift; sometimes a faint rainbow.
1718
------------------------------------------------------------------ */
1819
let lastCloud = -1e9;
20+
/* creatures that like rain (snails) register here; called when a shower starts (page cloud or garden cloud) */
21+
const rainHooks = [];
22+
const onRain = fn => rainHooks.push(fn);
23+
const fireRain = () => rainHooks.forEach(fn => { try { fn(); } catch (e) { } });
1924
function rainCloud() {
2025
if (reduce || !CLOUD_SVG || performance.now() - lastCloud < 300000) return false;
2126
const row = [$('.g-row'), $('.h-row')].find(r => r && inView(r) && r.getBoundingClientRect().top > navBottom() + 60 && !r.classList.contains('compact'));
@@ -33,7 +38,7 @@ MB.define('environment.weather', ['core.utils', 'core.scheduler', 'core.safe-zon
3338
const leave = () => { cloud.classList.remove('on'); cloud.classList.add('away'); setTimeout(() => { cloud.remove(); Life.release('cloud'); }, 1600); };
3439
const idle = setTimeout(() => { if (!raining) leave(); }, 30000);
3540
const rain = () => {
36-
if (raining) return; raining = true; clearTimeout(idle);
41+
if (raining) return; raining = true; clearTimeout(idle); fireRain();
3742
const cr = cloud.getBoundingClientRect(), fall = Math.max(80, rr.bottom - cr.bottom - 30);
3843
const sheet = document.createElement('div'); sheet.className = 'w-rain'; sheet.style.height = f1(fall) + 'px';
3944
sheet.innerHTML = Array.from({ length: 14 }, (_, k) => '<i style="left:' + f1(8 + k * 6.2) + '%;--d:' + f1(rand(0, 0.9)) + 's;--s:' + f1(rand(0.7, 1)) + 's"></i>').join('');
@@ -143,7 +148,7 @@ MB.define('environment.weather', ['core.utils', 'core.scheduler', 'core.safe-zon
143148
const x0s = c.x, y0 = c.y, DUR = 5800; let perked = 0, started = false;
144149
const shower = tween(DUR, t => {
145150
const now = performance.now();
146-
if (!started && t > 0.1){ started = true; c.shower = true; c.raining = true; el.classList.add('raining'); }
151+
if (!started && t > 0.1){ started = true; fireRain(); c.shower = true; c.raining = true; el.classList.add('raining'); }
147152
if (perked === 0 && t > 0.3){ perked = 1; refreshArea(); }
148153
if (perked === 1 && t > 0.6){ perked = 2; refreshArea(); }
149154
if (t >= 0.9 && c.raining){ c.raining = false; c.shower = false; el.classList.remove('raining'); }
@@ -170,5 +175,5 @@ MB.define('environment.weather', ['core.utils', 'core.scheduler', 'core.safe-zon
170175
Object.assign(ga, { setSunny, spawnCloud, weatherTick });
171176
}
172177

173-
return { rainCloud, createGarden };
178+
return { rainCloud, createGarden, onRain };
174179
});

0 commit comments

Comments
 (0)