Skip to content

Commit 7271438

Browse files
vines
1 parent e8da469 commit 7271438

6 files changed

Lines changed: 125 additions & 46 deletions

File tree

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
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; }
1515
@media (max-width: 1239px) { .vine-hit { width: 20px; } .vine-hit.left { left: 0; } .vine-hit.right { right: 0; } }
16-
.vine-tip { position: fixed; left: 0; top: 0; z-index: 130; 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; }
16+
.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; }
1919
.vine-sprout .sprout { animation: sproutIn 0.85s cubic-bezier(0.34, 1.6, 0.64, 1) 0.3s both; }

‎css/10-botanical.css‎

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -78,17 +78,34 @@ main > section, footer { overflow: visible; }
7878
.nav.small { top: calc(0.55rem + var(--safe-t)); }
7979
.nav a { white-space: nowrap; min-height: 38px; padding: 0.5rem clamp(0.6rem, 1.1vw, 0.95rem) 0.5rem clamp(0.5rem, 0.8vw, 0.7rem) !important; font-size: clamp(0.76rem, 0.72rem + 0.12vw, 0.82rem) !important; }
8080
@media (max-width: 640px) {
81-
:root { --nav-h: calc(62px + var(--safe-t)); }
82-
.nav { left: calc(8px + var(--safe-l)); right: calc(8px + var(--safe-r)); transform: none; max-width: none; justify-content: space-between; gap: 0 !important; padding: 0.28rem !important; }
83-
.nav a { flex: 1 1 auto; justify-content: center; gap: 0.2rem !important; min-height: 40px; padding: 0.35rem 0.3rem !important; font-size: 0.8rem !important; }
84-
.nav a .nav-fl { display: none; }
85-
.nav a.active .nav-fl { display: block; width: 11px; height: 11px; }
86-
.nav-bug { display: none; }
81+
:root { --nav-h: calc(52px + var(--safe-t)); --mobile-menu-rainbow: linear-gradient(90deg, #c45178 0%, #b77924 25%, #488363 50%, #527cba 75%, #9256b4 100%); }
82+
.nav { display: none !important; }
8783
.hero { padding-top: calc(var(--nav-h) + 1.4rem) !important; }
8884
}
89-
@media (max-width: 400px) {
90-
.nav a { font-size: 0.75rem !important; padding: 0.35rem 0.2rem !important; }
91-
.nav a.active .nav-fl { display: none; }
85+
86+
/* Mobile header + dropdown (<=640px): replaces the pill nav; links are built from .nav by navigation.js */
87+
.m-header, .m-menu { display: none; }
88+
@media (max-width: 640px) {
89+
.m-header { display: flex; align-items: center; justify-content: space-between; position: fixed; top: 0; left: 0; right: 0; z-index: 140; box-sizing: border-box; height: var(--nav-h); padding: var(--safe-t) calc(14px + var(--safe-r)) 0 calc(16px + var(--safe-l)); background: linear-gradient(90deg, rgba(251, 231, 161, 0.92), rgba(253, 225, 234, 0.92) 35%, rgba(236, 228, 248, 0.92) 68%, rgba(223, 236, 210, 0.92)); border-bottom: 1px solid rgba(194, 69, 126, 0.14); box-shadow: 0 4px 14px rgba(120, 72, 96, 0.08); }
90+
.m-brand { font-family: var(--font-display); font-size: 1.15rem; font-weight: 500; color: var(--white); text-decoration: none; }
91+
.m-burger { position: relative; flex-shrink: 0; width: 44px; height: 44px; padding: 0; border: 0; background: transparent; cursor: pointer; -webkit-tap-highlight-color: transparent; }
92+
.m-burger span { position: absolute; left: 11px; width: 22px; height: 2px; border-radius: 2px; background: var(--mobile-menu-rainbow); transition: transform 0.22s ease, opacity 0.15s ease; }
93+
.m-burger span:nth-child(1) { top: 15px; }
94+
.m-burger span:nth-child(2) { top: 21px; }
95+
.m-burger span:nth-child(3) { top: 27px; }
96+
.m-burger.open span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
97+
.m-burger.open span:nth-child(2) { opacity: 0; }
98+
.m-burger.open span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }
99+
.m-menu { display: flex; flex-direction: column; position: fixed; top: var(--nav-h); left: 0; right: 0; z-index: 139; box-sizing: border-box; max-height: calc(100dvh - var(--nav-h)); overflow-y: auto; padding: 0.4rem calc(1rem + var(--safe-r)) calc(0.8rem + env(safe-area-inset-bottom, 0px)) calc(1rem + var(--safe-l)); background: rgba(255, 250, 242, 0.97); border-bottom: 1px solid rgba(194, 69, 126, 0.14); box-shadow: 0 12px 24px rgba(120, 72, 96, 0.14); opacity: 0; visibility: hidden; pointer-events: none; translate: 0 -8px; transition: opacity 0.2s ease, translate 0.2s ease, visibility 0s 0.2s; }
100+
.m-menu.open { opacity: 1; visibility: visible; pointer-events: auto; translate: 0 0; transition: opacity 0.2s ease, translate 0.2s ease; }
101+
.m-menu a { display: flex; align-items: center; gap: 0.7rem; padding: 0.85rem 0.5rem; font-size: 1rem; color: var(--text-mid); text-decoration: none; border-bottom: 1px solid var(--border); }
102+
.m-menu a:last-child { border-bottom: 0; }
103+
.m-menu a.active { color: var(--cyan); font-weight: 500; }
104+
.m-menu .nav-fl { width: 22px; height: 22px; scale: 1; rotate: 0deg; opacity: 1; }
105+
.m-burger:focus-visible, .m-brand:focus-visible, .m-menu a:focus-visible { outline: 2px solid #7441b3; outline-offset: 3px; }
106+
}
107+
@media (prefers-reduced-motion: reduce) {
108+
.m-burger span, .m-menu, .m-menu.open { transition: none; }
92109
}
93110

94111
/* ---------------------------------------------------------------------

‎index.html‎

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
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=2">
19+
<link rel="stylesheet" href="css/08-garden-game-v7-v8.css?v=3">
2020
<link rel="stylesheet" href="css/09-v9-refinements.css?v=2">
21-
<link rel="stylesheet" href="css/10-botanical.css?v=2">
21+
<link rel="stylesheet" href="css/10-botanical.css?v=3">
2222
<link rel="stylesheet" href="css/11-little-world.css?v=2">
2323
<link rel="stylesheet" href="css/12-polish-pass.css?v=2">
2424
<link rel="stylesheet" href="css/13-ecosystem.css?v=2">
@@ -57,6 +57,11 @@
5757
<a href="#gallery">Artwork</a>
5858
<a href="#contact">Connect</a>
5959
</nav>
60+
<header class="m-header">
61+
<a class="m-brand" href="#home">Madison Butchko</a>
62+
<button class="m-burger" type="button" aria-label="Open navigation menu" aria-expanded="false" aria-controls="mMenu"><span></span><span></span><span></span></button>
63+
</header>
64+
<nav class="m-menu" id="mMenu" aria-label="Mobile navigation" inert></nav>
6065
<main>
6166
<section class="hero" id="home">
6267
<div class="hero-blobs" aria-hidden="true"><span class="blob b1"></span><span class="blob b2"></span><span class="blob b3"></span><span class="blob b4"></span></div>
@@ -719,7 +724,7 @@ <h2 class="contact-title">Let's <em>Connect</em></h2>
719724
<script src="js/effects/scroll-effects.js?v=2" defer></script>
720725
<script src="js/effects/text-effects.js?v=2" defer></script>
721726
<script src="js/effects/cursor.js?v=1" defer></script>
722-
<script src="js/navigation/navigation.js?v=1" defer></script>
727+
<script src="js/navigation/navigation.js?v=2" defer></script>
723728
<script src="js/site/hero.js?v=1" defer></script>
724729
<script src="js/effects/petals.js?v=2" defer></script>
725730
<script src="js/plants/vines.js?v=4" defer></script>
@@ -728,7 +733,7 @@ <h2 class="contact-title">Let's <em>Connect</em></h2>
728733
<script src="js/plants/decor.js?v=2" defer></script>
729734
<script src="js/animals/art.js?v=2" defer></script>
730735
<script src="js/plants/plants.js?v=2" defer></script>
731-
<script src="js/plants/vine-sprigs.js?v=3" defer></script>
736+
<script src="js/plants/vine-sprigs.js?v=4" defer></script>
732737
<script src="js/environment/weather.js?v=3" defer></script>
733738
<script src="js/garden/species.js?v=1" defer></script>
734739
<script src="js/garden/hud.js?v=1" defer></script>
@@ -752,6 +757,6 @@ <h2 class="contact-title">Let's <em>Connect</em></h2>
752757
<script src="js/plants/dandelions.js?v=2" defer></script>
753758
<script src="js/plants/flowers.js?v=3" defer></script>
754759
<script src="js/world/world.js?v=1" defer></script>
755-
<script src="js/main.js?v=3" defer></script>
760+
<script src="js/main.js?v=4" defer></script>
756761
</body>
757762
</html>

‎js/main.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
const nav = use('navigation.navigation'), hero = use('site.hero'), vines = use('plants.vines'), flowers = use('plants.flowers');
1616
const birds = use('animals.birds'), bouquet = use('botanical.bouquet');
1717

18+
nav.mobileMenu(); /* mobile header + dropdown (links cloned from .nav, before scroll-spy collects them) */
1819
scroll.reveal(); nav.scrollSpy(); /* reveal-on-scroll, scroll-spy */
1920
await boundary();
2021
use('effects.petals').init(); /* drifting petals + click bursts */

‎js/navigation/navigation.js‎

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,43 @@
11
/* js/navigation/navigation.js
22
Purpose : the top navigation: scroll-spy (which link is active), the sliding nav pill, and the flower nav (a flower per link and a butterfly that flies to your section).
3-
Owns : scrollSpy(), pill(), flowerNav(). Each is called by a legacy file at the spot its code used to run (scroll-spy first, then the pill, then the flower nav: the flower nav and pill both watch the .nav classes that scroll-spy sets).
3+
Owns : mobileMenu(), scrollSpy(), pill(), flowerNav(). Each is called by a legacy file at the spot its code used to run (scroll-spy first, then the pill, then the flower nav: the flower nav and pill both watch the .nav classes that scroll-spy sets).
44
Uses : nothing. Used by: js/legacy/020, 110, 120 (until Phase D wiring).
5-
Mobile / reduced motion: there is no hamburger menu (the pill nav is used at every width); the nav shrinks after 80px of scroll; the flower nav butterfly is part of the page decoration and respects the existing CSS reduced-motion rules.
5+
Mobile / reduced motion: at <=640px the pill nav is hidden and the .m-header + .m-menu dropdown (hamburger) show instead; mobileMenu() builds its links from the .nav links so labels, order and hrefs have one source; the nav shrinks after 80px of scroll; the flower nav butterfly is part of the page decoration and respects the existing CSS reduced-motion rules.
66
Moved verbatim from the legacy files (Migration Step 7); behaviour, order and timing unchanged. */
77
MB.define('navigation.navigation', [], function () {
88
'use strict';
99

10+
const NS = 'http://www.w3.org/2000/svg';
11+
const NAVFL = [['fl-bloom','#f4a7bf','#f2c230'],['fl-daisy','#b9a2de','#fbe7a1'],['fl-forsythia','#f2c230','#d99a12'],['fl-bloom','#8db36a','#fff1cc'],['fl-daisy','#e9789f','#f2c230']];
12+
function navFlower(k){ const f = NAVFL[k % NAVFL.length]; const s = document.createElementNS(NS, 'svg'); s.setAttribute('class', 'nav-fl'); s.setAttribute('aria-hidden', 'true'); s.setAttribute('focusable', 'false'); s.setAttribute('viewBox', '-50 -50 100 100'); s.style.color = f[1]; s.style.setProperty('--center', f[2]); s.innerHTML = '<use href="#' + f[0] + '" x="-50" y="-50" width="100" height="100"/>'; return s; }
13+
14+
/* Mobile header + dropdown. Links are copied from the desktop .nav so both menus share one label/href/order source;
15+
the single open/closed state drives the menu class, burger class, aria-label, aria-expanded and `inert` (closed links leave the tab order at once). */
16+
function mobileMenu() {
17+
const burger = document.querySelector('.m-burger'), menu = document.getElementById('mMenu'), nav = document.querySelector('.nav');
18+
if (!burger || !menu || !nav) return;
19+
nav.querySelectorAll('a').forEach((src, idx) => {
20+
const a = document.createElement('a'); a.href = src.getAttribute('href'); a.textContent = src.textContent.trim();
21+
if (src.classList.contains('active')) a.className = 'active';
22+
a.prepend(navFlower(idx)); menu.appendChild(a);
23+
});
24+
const mq = matchMedia('(max-width: 640px)');
25+
let open = false;
26+
/* outside-click and Escape listeners exist only while the menu is open */
27+
const onOutside = e => { if (!menu.contains(e.target) && !burger.contains(e.target)) set(false); };
28+
const onKey = e => { if (e.key === 'Escape') set(false, true); };
29+
const set = (on, refocus) => {
30+
if (on === open) return; open = on;
31+
menu.classList.toggle('open', on); burger.classList.toggle('open', on); menu.inert = !on;
32+
burger.setAttribute('aria-expanded', String(on)); burger.setAttribute('aria-label', on ? 'Close navigation menu' : 'Open navigation menu');
33+
if (on) { document.addEventListener('pointerdown', onOutside); addEventListener('keydown', onKey); }
34+
else { document.removeEventListener('pointerdown', onOutside); removeEventListener('keydown', onKey); if (refocus) burger.focus(); }
35+
};
36+
burger.addEventListener('click', () => set(!open));
37+
menu.addEventListener('click', e => { if (e.target.closest('a')) set(false); });
38+
mq.addEventListener('change', e => { if (!e.matches) set(false); });
39+
}
40+
1041
function scrollSpy() {
1142
(()=>{const secs=[...document.querySelectorAll('section[id]')],links=[...document.querySelectorAll('.nav a, .m-menu a')];let tk=false,cur='';addEventListener('scroll',()=>{if(tk)return;tk=true;requestAnimationFrame(()=>{tk=false;let c='about';for(const s of secs){if(s.id!=='home'&&scrollY>=s.offsetTop-200)c=s.id;}if(c===cur)return;cur=c;links.forEach(l=>{const on=l.getAttribute('href')==='#'+c;if(l.classList.contains('active')!==on)l.classList.toggle('active',on);});});},{passive:true});})();
1243
}
@@ -22,11 +53,9 @@ MB.define('navigation.navigation', [], function () {
2253
}
2354

2455
function flowerNav() {
25-
const NS = 'http://www.w3.org/2000/svg', reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
56+
const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
2657

2758
/* ---------- flower nav ---------- */
28-
const NAVFL = [['fl-bloom','#f4a7bf','#f2c230'],['fl-daisy','#b9a2de','#fbe7a1'],['fl-forsythia','#f2c230','#d99a12'],['fl-bloom','#8db36a','#fff1cc'],['fl-daisy','#e9789f','#f2c230']];
29-
function navFlower(k){ const f = NAVFL[k % NAVFL.length]; const s = document.createElementNS(NS, 'svg'); s.setAttribute('class', 'nav-fl'); s.setAttribute('aria-hidden', 'true'); s.setAttribute('focusable', 'false'); s.setAttribute('viewBox', '-50 -50 100 100'); s.style.color = f[1]; s.style.setProperty('--center', f[2]); s.innerHTML = '<use href="#' + f[0] + '" x="-50" y="-50" width="100" height="100"/>'; return s; }
3059
document.querySelectorAll('.nav a').forEach((a, idx) => {
3160
a.prepend(navFlower(idx));
3261
});
@@ -51,5 +80,5 @@ MB.define('navigation.navigation', [], function () {
5180
}
5281
}
5382

54-
return { scrollSpy, pill, flowerNav };
83+
return { mobileMenu, scrollSpy, pill, flowerNav };
5584
});

0 commit comments

Comments
 (0)