Worked examples: feature Bubble for Hum and trim the custom showcase

Swap the Hum carousel card from Sprout (hum-02) to Bubble (hum-07), the
guided sourdough Narrative Workflow build. Keep only one custom example in
the showcase, the Mend Assembly repair-cafe poster; the other bespoke pages
stay in the repo but off the landing. Untrack hum-02 so only featured builds
ship, refresh the scroll-demos manifest, and bump the cache version.
This commit is contained in:
Youssef
2026-06-02 18:54:33 +01:00
parent a5593bbca1
commit 455c2654bd
12 changed files with 735 additions and 524 deletions
+4
View File
@@ -61,5 +61,9 @@ site/examples/gallery-0[12]/
site/examples/carnival-0[2-9]/
site/examples/cobalt-02/
site/examples/hum-01/
site/examples/hum-02/
site/examples/hum-0[3-8]/
!site/examples/hum-07/
site/_tests/_thumbs/hum-02.jpg
site/examples/lumen-0[2-4]/
Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

-204
View File
@@ -1,204 +0,0 @@
<!doctype html>
<html lang="en" data-theme="hum">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<title>Sprout — Reading, one happy step at a time.</title>
<meta name="description" content="Sprout teaches kids aged 47 to read with ten-minute phonics lessons, a friendly guide, and no ads, ever." />
<meta name="theme-color" content="#faf6ee" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="tokens.css" />
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<a class="sr-only" href="#main">Skip to content</a>
<!-- ─── Nav · N1b canonical three-section (Hum-styled) ─── -->
<header class="nav" id="nav">
<div class="nav__inner">
<a class="nav__brand" href="#" aria-label="Sprout — home">
<span class="sprout-mark" aria-hidden="true"><span class="sprout-mark__face"></span></span>
Sprout
</a>
<nav class="nav__center" aria-label="Primary">
<div class="nav__item nav__item--menu">
<button class="nav__link" aria-expanded="false">How it works <span class="nav__caret"></span></button>
<div class="nav__dropdown" role="menu">
<a class="nav__dropitem" href="#"><b>The lessons</b><i>Ten minutes, every day</i></a>
<a class="nav__dropitem" href="#"><b>Phonics path</b><i>From letters to little books</i></a>
<a class="nav__dropitem" href="#"><b>Meet Sprout</b><i>The friendly guide</i></a>
</div>
</div>
<a class="nav__link" href="#families">For families</a>
<a class="nav__link" href="#schools">For schools</a>
<a class="nav__link" href="#stories">Stories</a>
</nav>
<div class="nav__right">
<a class="btn btn--text" href="#">Sign in</a>
<a class="btn btn--pear btn--small" href="#">Try free</a>
</div>
</div>
</header>
<main id="main">
<!-- ─── Hero · centred + full-width bento ─── -->
<section class="hero">
<div class="hero__head">
<p class="eyebrow"><span class="eyebrow__dot eyebrow__dot--mint"></span> Ages 47 · no ads, ever</p>
<h1 class="hero__title">Reading, one happy <span class="hero__em">step</span> at a time.</h1>
<p class="hero__lede">
Sprout turns learning to read into a ten-minute habit kids ask for — friendly
phonics, a guide who cheers them on, and not a single advertisement.
</p>
<div class="hero__cta">
<a class="btn btn--pear btn--lg" href="#">Start free for 14 days</a>
<a class="btn btn--ghost btn--lg" href="#how">See a lesson →</a>
</div>
</div>
<!-- Bento grid — the product, shown abstractly -->
<div class="bento" aria-label="What's inside Sprout">
<article class="bento__tile bento__tile--word" data-accent="mint">
<p class="tile__kicker">Today's word</p>
<p class="tile__word">bloom</p>
<p class="tile__phonics"><span>b</span><span>l</span><span class="is-hot">oo</span><span>m</span></p>
<p class="tile__say">say it: <b>b — l — oo — m</b></p>
</article>
<article class="bento__tile bento__tile--guide" data-accent="lavender">
<div class="guide" aria-hidden="true">
<span class="guide__leaf"></span>
<span class="guide__body"><span class="guide__face"></span></span>
</div>
<p class="tile__guide-line">"Hi, I'm Sprout! Ready for today's word?"</p>
</article>
<article class="bento__tile bento__tile--streak" data-accent="pear">
<p class="tile__kicker">On a roll</p>
<p class="tile__big"><span class="count" data-to="12">12</span><span class="tile__unit">days</span></p>
<p class="tile__dots" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i><i></i><i></i></p>
</article>
<article class="bento__tile bento__tile--learned" data-accent="cyan">
<p class="tile__kicker">Words learned</p>
<p class="tile__big"><span class="count" data-to="47">47</span></p>
<p class="tile__sub">three new this week</p>
</article>
<article class="bento__tile bento__tile--game" data-accent="coral">
<p class="tile__kicker">Today's game</p>
<p class="tile__game-title">Catch the rhyme</p>
<div class="rhyme" aria-hidden="true">
<span class="rhyme__chip">cat</span>
<span class="rhyme__chip rhyme__chip--match">hat</span>
<span class="rhyme__chip">dog</span>
<span class="rhyme__chip rhyme__chip--match">bat</span>
</div>
</article>
<article class="bento__tile bento__tile--parent" data-accent="mint">
<p class="tile__kicker">For grown-ups</p>
<p class="tile__parent">A weekly note on what your reader is finding easy — and what to gently practise.</p>
<p class="tile__parent-meta">Sundays · 30-second read</p>
</article>
</div>
</section>
<!-- ─── How a lesson works · 3 steps ─── -->
<section class="section section--band" id="how" aria-labelledby="how-title">
<header class="section__head section__head--center">
<p class="eyebrow"><span class="eyebrow__dot eyebrow__dot--pear"></span> A lesson, start to finish</p>
<h2 class="section__title" id="how-title">Ten minutes. Three little moves.</h2>
</header>
<div class="steps">
<article class="step" data-accent="mint">
<span class="step__num">1</span>
<h3 class="step__title">Meet the word</h3>
<p class="step__body">Sprout introduces one word with a picture and a sound. No reading pressure — just hello.</p>
</article>
<article class="step" data-accent="cyan">
<span class="step__num">2</span>
<h3 class="step__title">Sound it out</h3>
<p class="step__body">Tap each part to hear it. The word lights up as your reader blends the sounds together.</p>
</article>
<article class="step" data-accent="coral">
<span class="step__num">3</span>
<h3 class="step__title">Play it back</h3>
<p class="step__body">A 60-second game locks it in — catch the rhyme, build the word, feed it to Sprout.</p>
</article>
</div>
</section>
<!-- ─── Parents ─── -->
<section class="section" id="families" aria-labelledby="fam-title">
<header class="section__head">
<p class="eyebrow"><span class="eyebrow__dot eyebrow__dot--lavender"></span> What families say</p>
<h2 class="section__title" id="fam-title">The first app they ask to use.</h2>
</header>
<ul class="quotes">
<li class="quote" data-accent="pear">
<p class="quote__text">"My four-year-old does her 'Sprout lesson' before breakfast. She thinks it's a game. It is, mostly."</p>
<p class="quote__by"><b>Hana</b> · parent of one · 60-day streak</p>
</li>
<li class="quote" data-accent="cyan">
<p class="quote__text">"No ads, no upsells aimed at a six-year-old. That's why I trust it on his tablet."</p>
<p class="quote__by"><b>Marcus</b> · parent of two</p>
</li>
<li class="quote" data-accent="mint">
<p class="quote__text">"The Sunday note tells me exactly what to read with her. I stopped guessing."</p>
<p class="quote__by"><b>Priya</b> · parent of one</p>
</li>
</ul>
</section>
<!-- ─── Pricing ─── -->
<section class="section section--band" id="schools" aria-labelledby="price-title">
<header class="section__head section__head--center">
<p class="eyebrow"><span class="eyebrow__dot eyebrow__dot--coral"></span> Pricing</p>
<h2 class="section__title" id="price-title">Free to start. Kind to families.</h2>
</header>
<div class="plans">
<article class="plan" data-accent="cyan">
<p class="plan__name">Family</p>
<p class="plan__price"><span class="plan__num">$7</span><span class="plan__unit">/month</span></p>
<p class="plan__note">Up to three little readers. Cancel anytime.</p>
<ul class="plan__list"><li>3 reader profiles</li><li>The full phonics path</li><li>Weekly parent note</li><li>Offline lessons</li></ul>
<a class="btn btn--ghost btn--block" href="#">Start free</a>
</article>
<article class="plan plan--feature" data-accent="pear">
<span class="plan__badge">Loved most</span>
<p class="plan__name">Family · yearly</p>
<p class="plan__price"><span class="plan__num">$60</span><span class="plan__unit">/year</span></p>
<p class="plan__note">Two months free. Same gentle everything.</p>
<ul class="plan__list"><li>Everything in Family</li><li>Two months on us</li><li>Printable little books</li><li>A real sticker pack, by post</li></ul>
<a class="btn btn--pear btn--block" href="#">Start free</a>
</article>
<article class="plan" data-accent="mint">
<p class="plan__name">Classroom</p>
<p class="plan__price"><span class="plan__num">Free</span><span class="plan__unit">for teachers</span></p>
<p class="plan__note">Up to 30 readers. Funded by the family plans.</p>
<ul class="plan__list"><li>30 reader profiles</li><li>Class progress board</li><li>No ads, no data resale</li><li>Email a human, same day</li></ul>
<a class="btn btn--ghost btn--block" href="#">Verify as a teacher</a>
</article>
</div>
</section>
</main>
<footer class="footer">
<div class="footer__inner">
<p class="footer__statement">Grow a reader.</p>
<div class="footer__meta">
<a class="footer__brand" href="#"><span class="sprout-mark sprout-mark--sm" aria-hidden="true"><span class="sprout-mark__face"></span></span> Sprout</a>
<p class="footer__col"><span>Sprout Learning, PBC</span><span>Made by parents + teachers</span><span>Utrecht &amp; Austin</span></p>
<p class="footer__col"><a href="#">For families</a><a href="#">For schools</a><a href="#">Our promise on ads</a><a href="#">hello@sprout.kids</a></p>
<p class="footer__col footer__copy">© 2024 — 2026 Sprout<br /><span class="footer__hash">B-Corp · COPPA-safe</span></p>
</div>
</div>
</footer>
<script src="script.js"></script>
</body>
</html>
-33
View File
@@ -1,33 +0,0 @@
/* Hum · Sprout — nav scroll-frost + counter tick-up on view-enter. */
(function () {
const nav = document.getElementById('nav');
if (nav) {
let ticking = false;
function onScroll() { nav.classList.toggle('is-scrolled', window.scrollY > 24); ticking = false; }
window.addEventListener('scroll', function () { if (!ticking) { requestAnimationFrame(onScroll); ticking = true; } }, { passive: true });
onScroll();
}
/* Count up to data-to once visible */
const counters = Array.from(document.querySelectorAll('.count'));
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
function run(el) {
const to = parseInt(el.dataset.to, 10) || 0;
if (reduce) { el.textContent = to; return; }
const dur = 1100, start = performance.now();
function tick(now) {
const p = Math.min((now - start) / dur, 1);
const eased = 1 - Math.pow(1 - p, 3);
el.textContent = Math.round(to * eased);
if (p < 1) requestAnimationFrame(tick);
else { el.textContent = to; el.animate([{ transform: 'scale(1)' }, { transform: 'scale(1.08)' }, { transform: 'scale(1)' }], { duration: 320, easing: 'ease-out' }); }
}
requestAnimationFrame(tick);
}
if ('IntersectionObserver' in window && counters.length) {
const io = new IntersectionObserver(function (entries) {
entries.forEach(function (e) { if (e.isIntersecting) { run(e.target); io.unobserve(e.target); } });
}, { threshold: 0.6 });
counters.forEach(function (c) { io.observe(c); });
} else { counters.forEach(run); }
})();
-237
View File
@@ -1,237 +0,0 @@
/* Hallmark · Hum · Sprout (kids learn-to-read) · Bento Grid · N1b nav · hum-02 */
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; }
html { overflow-x: clip; scroll-behavior: smooth; }
body {
background: var(--color-paper);
color: var(--color-ink);
font-family: var(--font-body);
font-size: 1rem; line-height: 1.55;
font-variant-numeric: tabular-nums;
font-feature-settings: "ss01" on;
-webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility;
}
img, svg { display: block; max-width: 100%; }
::selection { background: oklch(86% 0.18 95 / 0.4); color: var(--color-ink); }
a { color: inherit; text-decoration: none; }
a:focus-visible, button:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 3px; border-radius: 6px; }
button { font: inherit; background: none; border: 0; padding: 0; cursor: pointer; color: inherit; }
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }
.eyebrow {
font-family: var(--font-label); font-size: 11px; letter-spacing: 0.10em;
text-transform: uppercase; color: var(--color-ink-2); opacity: 0.75;
margin: 0 0 1rem; display: inline-flex; align-items: center; gap: 0.55em;
}
.eyebrow__dot { width: 8px; height: 8px; border-radius: 50%; }
.eyebrow__dot--pear { background: var(--color-accent); }
.eyebrow__dot--cyan { background: var(--color-accent-2); }
.eyebrow__dot--coral { background: var(--color-accent-3); }
.eyebrow__dot--mint { background: var(--color-mint); }
.eyebrow__dot--lavender { background: var(--color-lavender); }
/* ── Buttons ── */
/* v2 button system (push base + ghost/text) — see hum-buttons + hum.md */
.btn {
--btn-face: var(--color-accent); --btn-ink: var(--color-ink); --btn-edge: var(--color-accent-deep); --btn-cast: oklch(76% 0.20 95 / 0.45); --btn-line: var(--color-accent-deep);
display: inline-flex; align-items: center; justify-content: center; gap: 0.5em; padding: 0.8rem 1.35rem; font-weight: 600; font-size: 0.9375rem; letter-spacing: -0.005em; border: 0; border-radius: var(--radius-pill); color: var(--btn-ink); background: var(--btn-face); cursor: pointer; white-space: nowrap; user-select: none; position: relative; isolation: isolate;
box-shadow: 0 4px 0 0 var(--btn-edge), 0 6px 12px -3px var(--btn-cast); transform: translateY(0);
transition: transform 140ms cubic-bezier(0.2,0.7,0.3,1), box-shadow 140ms cubic-bezier(0.2,0.7,0.3,1), background-color 160ms, border-color 200ms, color 200ms;
}
.btn:hover { transform: translateY(-2px); box-shadow: 0 6px 0 0 var(--btn-edge), 0 12px 22px -4px var(--btn-cast); }
.btn:active { transform: translateY(3px); box-shadow: 0 1px 0 0 var(--btn-edge), 0 2px 6px -2px var(--btn-cast); transition-duration: 70ms; }
.btn:focus-visible { outline: 3px solid color-mix(in oklch, var(--btn-edge) 70%, var(--color-focus)); outline-offset: 3px; }
.btn[disabled] { opacity: 0.5; cursor: not-allowed; pointer-events: none; transform: none; box-shadow: 0 4px 0 0 var(--btn-edge); }
.btn__arrow { transition: transform 200ms cubic-bezier(0.2,0.7,0.3,1); }
.btn:hover .btn__arrow { transform: translateX(3px); }
.btn--small { padding: 0.55rem 1rem; font-size: 0.85rem; }
.btn--lg { padding: 1rem 1.7rem; font-size: 1.0625rem; }
.btn--block { width: 100%; }
.btn--pear { --btn-face: var(--color-accent); --btn-ink: var(--color-ink); --btn-edge: var(--color-accent-deep); --btn-cast: oklch(76% 0.20 95 / 0.45); --btn-line: var(--color-accent-deep); }
.btn--cyan { --btn-face: var(--color-accent-2); --btn-ink: var(--color-paper); --btn-edge: var(--color-accent-2-deep); --btn-cast: oklch(56% 0.20 235 / 0.4); --btn-line: var(--color-accent-2-deep); }
.btn--coral { --btn-face: var(--color-accent-3); --btn-ink: var(--color-paper); --btn-edge: var(--color-accent-3-deep); --btn-cast: oklch(58% 0.26 18 / 0.42); --btn-line: var(--color-accent-3-deep); }
.btn--ghost { background: transparent; color: var(--color-ink); border: 1.5px solid var(--color-rule-2); box-shadow: none; }
.btn--ghost:hover { transform: translateY(-2px); border-color: var(--color-ink); background: var(--color-paper-2); box-shadow: 0 8px 20px -10px oklch(20% 0.012 250 / 0.18); }
.btn--ghost:active { transform: translateY(0); transition-duration: 80ms; box-shadow: none; }
.btn--text { background: transparent; color: var(--color-ink-2); box-shadow: none; padding-inline: 0.5rem; }
.btn--text:hover { color: var(--color-ink); transform: none; box-shadow: none; }
.btn--text:active { transform: none; box-shadow: none; }
/* ── Sprout character mark ── */
.sprout-mark {
position: relative; display: inline-block;
width: 26px; height: 26px; border-radius: 50% 50% 50% 50% / 60% 60% 40% 40%;
background: linear-gradient(160deg, var(--color-mint) 0%, var(--color-mint-deep) 100%);
box-shadow: 0 3px 0 -1px var(--color-mint-deep);
vertical-align: middle;
animation: char-bob 4s ease-in-out infinite;
}
.sprout-mark--sm { width: 22px; height: 22px; }
.sprout-mark::before { /* leaf sprout */
content: ""; position: absolute; top: -6px; left: 50%; transform: translateX(-50%) rotate(-12deg);
width: 8px; height: 11px; background: var(--color-mint-deep);
border-radius: 0 100% 0 100%;
}
.sprout-mark__face { position: absolute; inset: 7px 5px; }
.sprout-mark__face::before { content: ""; position: absolute; top: 30%; left: 18%; width: 3px; height: 3px; border-radius: 50%; background: var(--color-ink); box-shadow: 8px 0 0 var(--color-ink); }
.sprout-mark__face::after { content: ""; position: absolute; bottom: 12%; left: 50%; transform: translateX(-50%); width: 8px; height: 4px; border-bottom: 1.5px solid var(--color-ink); border-radius: 0 0 8px 8px; }
@keyframes char-bob { 0%,100% { transform: translateY(0) rotate(0); } 50% { transform: translateY(-2px) rotate(2deg); } }
@media (prefers-reduced-motion: reduce) { .sprout-mark { animation: none; } }
/* ── Nav · N1b three-section ── */
.nav { position: fixed; top: 0; left: 0; right: 0; z-index: 500; background: transparent; border-bottom: 1px solid transparent; transition: background 240ms var(--ease-out), border-color 240ms, box-shadow 240ms; }
.nav.is-scrolled { background: oklch(97% 0.012 95 / 0.8); backdrop-filter: blur(18px) saturate(160%); -webkit-backdrop-filter: blur(18px) saturate(160%); border-bottom-color: var(--color-rule); box-shadow: 0 8px 28px -18px oklch(20% 0.012 250 / 0.3); }
.nav__inner { max-width: var(--page-max); margin: 0 auto; padding-inline: var(--page-gutter); height: 68px; display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 1.5rem; }
.nav__brand { display: inline-flex; align-items: center; gap: 0.55em; font-family: var(--font-display); font-weight: 700; font-size: 1.25rem; letter-spacing: -0.02em; color: var(--color-ink); justify-self: start; }
.nav__center { display: flex; align-items: center; gap: 0.25rem; justify-self: center; }
.nav__item { position: relative; }
.nav__link { display: inline-flex; align-items: center; gap: 0.3em; padding: 0.5rem 0.75rem; font-size: 0.9rem; font-weight: 500; color: var(--color-ink-2); border-radius: 10px; transition: color 160ms, background 160ms; }
.nav__link:hover { color: var(--color-ink); background: var(--color-paper-2); }
.nav__caret { width: 7px; height: 7px; border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor; transform: rotate(45deg) translateY(-1px); transition: transform 220ms var(--ease-out); opacity: 0.6; }
.nav__dropdown { position: absolute; top: calc(100% + 8px); left: 50%; transform: translateX(-50%) translateY(-6px) scale(0.98); min-width: 250px; padding: 0.4rem; background: oklch(99% 0.008 95 / 0.95); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); border: 1px solid var(--color-rule); border-radius: 16px; box-shadow: 0 24px 48px -20px oklch(20% 0.012 250 / 0.25); opacity: 0; visibility: hidden; pointer-events: none; transition: opacity 200ms var(--ease-out), transform 240ms var(--ease-spring), visibility 200ms; }
.nav__item--menu:hover .nav__dropdown, .nav__item--menu:focus-within .nav__dropdown { opacity: 1; visibility: visible; pointer-events: auto; transform: translateX(-50%) translateY(0) scale(1); }
.nav__item--menu:hover .nav__caret, .nav__item--menu:focus-within .nav__caret { transform: rotate(-135deg) translateY(2px); }
.nav__dropitem { display: flex; flex-direction: column; gap: 0.1rem; padding: 0.6rem 0.75rem; border-radius: 11px; transition: background 140ms; }
.nav__dropitem:hover { background: var(--tint-mint); }
.nav__dropitem b { font-weight: 600; font-size: 0.9rem; color: var(--color-ink); }
.nav__dropitem i { font-style: normal; font-size: 0.8125rem; color: var(--color-muted); }
.nav__right { display: flex; align-items: center; gap: 0.5rem; justify-self: end; }
@media (max-width: 920px) { .nav__center { display: none; } }
/* ── Hero ── */
.hero { padding-block: clamp(8rem, 14vh, 10.5rem) clamp(3rem, 7vh, 5rem); }
.hero__head { max-width: 60rem; margin: 0 auto clamp(2.5rem, 5vh, 3.5rem); padding-inline: var(--page-gutter); text-align: center; }
.hero__head .eyebrow { justify-content: center; }
.hero__title { font-family: var(--font-display); font-weight: 700; font-size: clamp(2.5rem, 5.5vw + 0.5rem, 4.75rem); line-height: 1.02; letter-spacing: -0.03em; color: var(--color-ink); margin: 0 0 1.25rem; text-wrap: balance; }
.hero__em { color: inherit; background-image: linear-gradient(oklch(80% 0.16 150 / 0.5) 0 0); background-repeat: no-repeat; background-size: 100% 0.14em; background-position: 0 100%; -webkit-box-decoration-break: clone; box-decoration-break: clone; padding-bottom: 0.24em; }
.hero__lede { font-size: 1.1875rem; line-height: 1.55; color: var(--color-ink-2); max-width: 50ch; margin: 0 auto 1.75rem; }
.hero__cta { display: flex; flex-wrap: wrap; gap: 0.75rem; justify-content: center; }
/* ── Bento ── */
.bento {
max-width: var(--page-max); margin: 0 auto; padding-inline: var(--page-gutter);
display: grid; gap: 1rem; grid-template-columns: 1fr;
}
@media (min-width: 760px) {
.bento {
grid-template-columns: repeat(4, 1fr);
grid-auto-rows: minmax(120px, auto);
grid-template-areas:
"word word guide guide"
"word word streak learned"
"game game parent parent";
}
.bento__tile--word { grid-area: word; }
.bento__tile--guide { grid-area: guide; }
.bento__tile--streak { grid-area: streak; }
.bento__tile--learned { grid-area: learned; }
.bento__tile--game { grid-area: game; }
.bento__tile--parent { grid-area: parent; }
}
.bento__tile {
border-radius: var(--radius-card); padding: 1.5rem;
border: 1.5px solid var(--color-rule); background: var(--color-paper);
display: flex; flex-direction: column; gap: 0.5rem;
transition: transform 320ms var(--ease-spring), box-shadow 320ms, border-color 320ms, background 320ms;
}
.bento__tile[data-accent="mint"] { background: var(--tint-mint); }
.bento__tile[data-accent="lavender"] { background: var(--tint-lavender); }
.bento__tile[data-accent="pear"] { background: var(--tint-pear); }
.bento__tile[data-accent="cyan"] { background: var(--tint-cyan); }
.bento__tile[data-accent="coral"] { background: var(--tint-coral); }
.bento__tile:hover { transform: translateY(-4px); box-shadow: var(--shadow-card-hover); }
.tile__kicker { font-family: var(--font-label); font-size: 10.5px; letter-spacing: 0.10em; text-transform: uppercase; color: var(--color-muted); margin: 0; }
.tile__word { font-family: var(--font-display); font-weight: 700; font-size: clamp(2.5rem, 6vw, 4rem); line-height: 1; letter-spacing: -0.03em; color: var(--color-ink); margin: 0.25rem 0; }
.tile__phonics { display: flex; gap: 0.4rem; margin: 0.25rem 0; }
.tile__phonics span { font-family: var(--font-display); font-weight: 600; font-size: 1.5rem; padding: 0.1em 0.45em; border-radius: 10px; background: var(--color-paper); border: 1.5px solid var(--color-rule); color: var(--color-ink-2); }
.tile__phonics span.is-hot { background: var(--color-mint); border-color: transparent; color: var(--color-ink); }
.tile__say { font-size: 0.9375rem; color: var(--color-ink-2); margin: 0.25rem 0 0; }
.tile__say b { color: var(--color-ink); }
.bento__tile--guide { align-items: center; text-align: center; justify-content: center; }
.guide { position: relative; width: 84px; height: 84px; margin-bottom: 0.25rem; }
.guide__body { position: absolute; inset: 14px 0 0; border-radius: 50% 50% 48% 48% / 56% 56% 44% 44%; background: linear-gradient(165deg, var(--color-mint) 0%, var(--color-mint-deep) 100%); box-shadow: 0 5px 0 -2px var(--color-mint-deep); display: block; animation: char-bob 3.6s ease-in-out infinite; }
.guide__face { position: absolute; inset: 22px 18px; }
.guide__face::before { content: ""; position: absolute; top: 26%; left: 20%; width: 5px; height: 5px; border-radius: 50%; background: var(--color-ink); box-shadow: 16px 0 0 var(--color-ink); }
.guide__face::after { content: ""; position: absolute; bottom: 14%; left: 50%; transform: translateX(-50%); width: 16px; height: 8px; border-bottom: 2.5px solid var(--color-ink); border-radius: 0 0 16px 16px; }
.guide__leaf { position: absolute; top: 0; left: 50%; transform: translateX(-50%) rotate(-14deg); width: 18px; height: 26px; background: var(--color-mint-deep); border-radius: 0 100% 0 100%; z-index: 1; }
.tile__guide-line { font-family: var(--font-display); font-weight: 600; font-size: 1.0625rem; line-height: 1.3; color: var(--color-ink); margin: 0; max-width: 22ch; }
.tile__big { font-family: var(--font-display); font-weight: 700; font-size: clamp(2.5rem, 5vw, 3.5rem); line-height: 1; letter-spacing: -0.03em; color: var(--color-ink); margin: 0.25rem 0; display: inline-flex; align-items: baseline; gap: 0.25em; }
.tile__unit { font-family: var(--font-label); font-size: 0.85rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--color-muted); }
.tile__dots { display: flex; gap: 0.3rem; margin: 0.25rem 0 0; }
.tile__dots i { width: 9px; height: 9px; border-radius: 50%; background: var(--color-accent); }
.tile__dots i:last-child { background: var(--color-rule-2); }
.tile__sub { font-size: 0.875rem; color: var(--color-ink-2); margin: 0; }
.tile__game-title { font-family: var(--font-display); font-weight: 600; font-size: 1.375rem; letter-spacing: -0.02em; color: var(--color-ink); margin: 0.25rem 0; }
.rhyme { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.rhyme__chip { font-family: var(--font-display); font-weight: 600; font-size: 1rem; padding: 0.35em 0.8em; border-radius: var(--radius-pill); background: var(--color-paper); border: 1.5px solid var(--color-rule); color: var(--color-ink-2); }
.rhyme__chip--match { background: var(--color-accent-3); border-color: transparent; color: var(--color-paper); }
.tile__parent { font-size: 1rem; line-height: 1.5; color: var(--color-ink-2); margin: 0.25rem 0; }
.tile__parent-meta { font-family: var(--font-label); font-size: 10.5px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--color-muted); margin: auto 0 0; }
/* ── Sections ── */
.section { max-width: var(--page-max); margin: 0 auto; padding: var(--section-gap) var(--page-gutter); }
.section--band { max-width: none; padding-inline: 0; background: var(--color-paper-2); border-block: 1px solid var(--color-rule); }
.section--band > * { max-width: var(--page-max); margin-inline: auto; padding-inline: var(--page-gutter); }
.section__head { margin-bottom: 2.5rem; max-width: 56rem; }
.section__head--center { text-align: center; margin-inline: auto; }
.section__head--center .eyebrow { justify-content: center; }
.section__title { font-family: var(--font-display); font-weight: 700; font-size: clamp(2rem, 3.5vw + 0.5rem, 3.25rem); line-height: 1.08; letter-spacing: -0.025em; color: var(--color-ink); margin: 0; max-width: 22ch; text-wrap: balance; }
.section__head--center .section__title { margin-inline: auto; }
.steps { display: grid; grid-template-columns: 1fr; gap: 1rem; }
@media (min-width: 760px) { .steps { grid-template-columns: repeat(3, 1fr); } }
.step { padding: 1.75rem; border-radius: var(--radius-card); background: var(--color-paper); border: 1.5px solid var(--color-rule); display: flex; flex-direction: column; gap: 0.6rem; box-shadow: var(--shadow-card); transition: transform 320ms var(--ease-spring); }
.step:hover { transform: translateY(-4px); }
.step__num { width: 38px; height: 38px; border-radius: 50%; display: grid; place-items: center; font-family: var(--font-display); font-weight: 700; font-size: 1.125rem; color: var(--color-ink); }
.step[data-accent="mint"] .step__num { background: var(--color-mint); }
.step[data-accent="cyan"] .step__num { background: var(--color-accent-2); color: var(--color-paper); }
.step[data-accent="coral"] .step__num { background: var(--color-accent-3); color: var(--color-paper); }
.step__title { font-family: var(--font-display); font-weight: 600; font-size: 1.25rem; letter-spacing: -0.02em; color: var(--color-ink); margin: 0; }
.step__body { font-size: 0.9375rem; line-height: 1.6; color: var(--color-ink-2); margin: 0; }
.quotes { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: 1fr; gap: 1rem; }
@media (min-width: 820px) { .quotes { grid-template-columns: repeat(3, 1fr); } }
.quote { padding: 1.75rem; border-radius: var(--radius-card); border: 1.5px solid var(--color-rule); display: flex; flex-direction: column; gap: 1rem; }
.quote[data-accent="pear"] { background: var(--tint-pear); }
.quote[data-accent="cyan"] { background: var(--tint-cyan); }
.quote[data-accent="mint"] { background: var(--tint-mint); }
.quote__text { font-family: var(--font-display); font-weight: 500; font-size: 1.125rem; line-height: 1.4; letter-spacing: -0.012em; color: var(--color-ink); margin: 0; }
.quote__by { font-size: 0.875rem; color: var(--color-ink-2); margin: 0; padding-top: 0.85rem; border-top: 1px solid var(--color-rule); }
.quote__by b { font-weight: 600; color: var(--color-ink); }
.plans { display: grid; grid-template-columns: 1fr; gap: 1rem; }
@media (min-width: 880px) { .plans { grid-template-columns: repeat(3, 1fr); } }
.plan { display: flex; flex-direction: column; gap: 0.85rem; padding: 1.75rem; border-radius: var(--radius-card); border: 1.5px solid var(--color-rule); background: var(--color-paper); box-shadow: var(--shadow-card); transition: transform 320ms var(--ease-spring); position: relative; }
.plan:hover { transform: translateY(-4px); }
.plan--feature { background: var(--tint-pear); border-color: var(--color-accent-deep); }
.plan__badge { position: absolute; top: -0.7rem; left: 50%; transform: translateX(-50%); font-family: var(--font-label); font-size: 10.5px; letter-spacing: 0.10em; text-transform: uppercase; padding: 0.3rem 0.7rem; background: var(--color-ink); color: var(--color-paper); border-radius: 999px; }
.plan__name { font-family: var(--font-label); font-size: 11px; letter-spacing: 0.10em; text-transform: uppercase; color: var(--color-muted); margin: 0; }
.plan__price { font-family: var(--font-display); font-weight: 700; font-size: 2.5rem; line-height: 1; letter-spacing: -0.03em; color: var(--color-ink); margin: 0; }
.plan__unit { font-family: var(--font-label); font-size: 0.3em; letter-spacing: 0.08em; text-transform: uppercase; color: var(--color-muted); margin-inline-start: 0.3em; vertical-align: 0.7em; }
.plan__note { font-size: 0.875rem; color: var(--color-ink-2); margin: 0; padding-bottom: 0.85rem; border-bottom: 1px solid var(--color-rule); line-height: 1.5; }
.plan__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.6rem; flex: 1; }
.plan__list li { font-size: 0.9375rem; color: var(--color-ink-2); padding-left: 1.4rem; position: relative; }
.plan__list li::before { content: ""; position: absolute; left: 0; top: 0.45em; width: 12px; height: 12px; border-radius: 50%; background: var(--color-mint); box-shadow: inset 0 0 0 3.5px var(--color-paper); }
.plan[data-accent="cyan"] .plan__list li::before { background: var(--color-accent-2); }
.plan__list { margin-bottom: 0.5rem; }
/* ── Footer ── */
.footer { background: var(--color-paper-2); border-top: 1px solid var(--color-rule); }
.footer__inner { max-width: var(--page-max); margin: 0 auto; padding: clamp(4rem, 8vh, 6rem) var(--page-gutter) clamp(3rem, 6vh, 4.5rem); display: flex; flex-direction: column; gap: clamp(3rem, 6vh, 4.5rem); }
.footer__statement { font-family: var(--font-display); font-weight: 700; font-size: clamp(2.5rem, 5vw + 1rem, 5rem); line-height: 1.02; letter-spacing: -0.03em; color: var(--color-ink); margin: 0; }
.footer__meta { display: grid; grid-template-columns: auto 1fr 1fr 1fr; gap: 2rem; align-items: start; padding-top: 2rem; border-top: 1px solid var(--color-rule); }
@media (max-width: 880px) { .footer__meta { grid-template-columns: 1fr 1fr; } .footer__brand { grid-column: span 2; } }
@media (max-width: 540px) { .footer__meta { grid-template-columns: 1fr; } .footer__brand { grid-column: span 1; } }
.footer__brand { display: inline-flex; align-items: center; gap: 0.5em; font-family: var(--font-display); font-weight: 700; font-size: 1.25rem; color: var(--color-ink); align-self: start; }
.footer__col { margin: 0; font-family: var(--font-label); font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--color-muted); display: flex; flex-direction: column; gap: 0.4rem; }
.footer__col a { color: var(--color-muted); transition: color 180ms; }
.footer__col a:hover { color: var(--color-ink); }
.footer__hash { color: var(--color-rule-2); }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } * { animation-duration: 0.01ms !important; } }
+305
View File
@@ -0,0 +1,305 @@
<!doctype html>
<html lang="en" data-theme="hum">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<title>Bubble — Guided sourdough, one stage at a time.</title>
<meta name="description" content="Bubble walks you through one loaf of real sourdough — feed, mix, prove, bake — and pings you the moment the dough needs you. About 45 minutes of actual work, spread across two unhurried days." />
<meta name="theme-color" content="#faf6ee" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="tokens.css" />
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<a class="sr-only" href="#main">Skip to content</a>
<!-- ─── Nav · N10 floating-on-scroll morph ─── -->
<header class="nav" id="nav">
<div class="nav__inner">
<a class="nav__brand" href="#" aria-label="Bubble — home">
<span class="bub-mark" aria-hidden="true"></span>
Bubble
</a>
<nav class="nav__links" aria-label="Primary">
<a href="#stages">The method</a>
<a href="#numbers">The math</a>
<a href="#voices">Kitchen</a>
<a href="#pricing">Pricing</a>
</nav>
<div class="nav__actions">
<a class="btn btn--text" href="#">Sign in</a>
<a class="btn btn--mint btn--sm" href="#" data-burst>Start a starter</a>
</div>
</div>
</header>
<main id="main">
<!-- ─── Hero · Narrative Workflow opening ─── -->
<section class="hero">
<div class="hero__grid">
<div class="hero__lead">
<div class="process" aria-label="The four stages">
<span class="process__step"><span class="process__dot" style="background:var(--color-mint)"></span><span class="num">01</span> feed</span>
<span class="process__link"></span>
<span class="process__step"><span class="process__dot" style="background:var(--color-accent-2)"></span><span class="num">02</span> mix</span>
<span class="process__link"></span>
<span class="process__step"><span class="process__dot" style="background:var(--color-accent)"></span><span class="num">03</span> prove</span>
<span class="process__link"></span>
<span class="process__step"><span class="process__dot" style="background:var(--color-accent-3)"></span><span class="num">04</span> bake</span>
</div>
<h1 class="hero__title">Real sourdough, in about <em>forty-five minutes</em> of actual work.</h1>
<p class="hero__lede">Bubble walks you through one loaf, one stage at a time, and pings you the moment the dough needs you. The other thirty-six hours it just sits there getting better, while you get on with your life.</p>
<div class="hero__cta">
<a class="btn btn--mint btn--lg" href="#" data-burst>Start your first loaf</a>
<a class="btn btn--outline btn--lg btn--mint" href="#stages">See the four stages <span class="btn__arrow" aria-hidden="true"></span></a>
</div>
<p class="hero__note">Free to read · no account for the recipe · works at the counter, offline</p>
</div>
<div class="hero__stage">
<div class="starter" id="starter" aria-hidden="true">
<span class="starter__pulse"></span>
<span class="starter__jar"><span class="starter__fill"></span></span>
<span class="starter__lid"></span>
<span class="starter__bubble"></span>
<span class="starter__bubble"></span>
<span class="starter__bubble"></span>
<span class="starter__bubble"></span>
</div>
<div class="remind" role="status">
<span class="remind__icon" aria-hidden="true"></span>
<div>
<p class="remind__head"><b>Bubble</b> · in 35 min</p>
<p class="remind__body">First stretch &amp; fold — <span class="remind__time">then 45 minutes to yourself.</span></p>
</div>
</div>
</div>
</div>
</section>
<!-- ─── The timeline spine · numbered stages ─── -->
<section class="section" id="stages" aria-labelledby="stages-title">
<header class="section__head">
<p class="eyebrow"><span class="eyebrow__dot eyebrow__dot--mint"></span> The method</p>
<h2 class="section__title" id="stages-title">Four stages, start to slice.</h2>
<p class="section__lede">The same four every loaf. You only ever do one at a time — Bubble holds the timeline so you don't have to keep it in your head.</p>
</header>
<div class="stages">
<!-- 1.0 FEED -->
<article class="stage stage--mint">
<div class="stage__rail" aria-hidden="true">
<div class="stage__node"><b>1<small>.0</small></b></div>
<div class="stage__line"></div>
</div>
<div class="stage__panel">
<div class="stage__copy">
<p class="stage__label">1.0 · Feed · The night before</p>
<h3 class="stage__title">Wake the starter</h3>
<p class="stage__text">Stir a spoonful of starter into fresh flour and water before bed. By morning it's doubled, domed, and smells like yogurt and green apples — that's ripe. <b>Bubble times the feed to your kitchen's temperature</b>, so it peaks when you wake up, not at 3 a.m.</p>
<div class="stage__chips">
<span class="chip"><span class="chip__dot"></span> 1012 hrs · overnight</span>
<span class="chip chip--hands"><span class="chip__dot"></span> Hands-on · 5 min</span>
</div>
</div>
<div class="stage__art" aria-hidden="true">
<div class="art-jar">
<span class="art-jar__lid"></span>
<span class="art-jar__body"><span class="art-jar__fill"></span></span>
<span class="b"></span><span class="b"></span><span class="b"></span>
</div>
</div>
</div>
</article>
<!-- 2.0 MIX -->
<article class="stage stage--cyan">
<div class="stage__rail" aria-hidden="true">
<div class="stage__node"><b>2<small>.0</small></b></div>
<div class="stage__line"></div>
</div>
<div class="stage__panel">
<div class="stage__copy">
<p class="stage__label">2.0 · Mix · Morning</p>
<h3 class="stage__title">Mix, then let it rest</h3>
<p class="stage__text">Fold flour, water, the ripe levain and salt into a shaggy dough, then walk away for an hour while the flour drinks and the gluten knits itself — no kneading required. <b>One rest timer, one nudge</b> when it's ready for the first fold.</p>
<div class="stage__chips">
<span class="chip"><span class="chip__dot"></span> 1 hr autolyse</span>
<span class="chip chip--hands"><span class="chip__dot"></span> Hands-on · 10 min</span>
</div>
</div>
<div class="stage__art" aria-hidden="true">
<div class="art-bowl">
<span class="art-bowl__bowl"></span>
<span class="art-bowl__rim"></span>
<span class="art-bowl__dough"></span>
<span class="fl"></span><span class="fl"></span><span class="fl"></span>
</div>
</div>
</div>
</article>
<!-- 3.0 PROVE -->
<article class="stage stage--pear">
<div class="stage__rail" aria-hidden="true">
<div class="stage__node"><b>3<small>.0</small></b></div>
<div class="stage__line"></div>
</div>
<div class="stage__panel">
<div class="stage__copy">
<p class="stage__label">3.0 · Prove · Day, then overnight</p>
<h3 class="stage__title">Fold, shape, and wait</h3>
<p class="stage__text">Four gentle folds over the first couple of hours build the structure; then the dough bulks for four or five, shapes into a taut round, and rests overnight in the fridge. <b>Bubble paces the folds</b> and tells you when the bulk is genuinely done — by how the dough looks, not just the clock.</p>
<div class="stage__chips">
<span class="chip"><span class="chip__dot"></span> 45 hrs bulk</span>
<span class="chip"><span class="chip__dot"></span> + overnight cold proof</span>
<span class="chip chip--hands"><span class="chip__dot"></span> Hands-on · 20 min</span>
</div>
</div>
<div class="stage__art" aria-hidden="true">
<div class="art-banneton">
<span class="art-banneton__dome"></span>
<span class="art-banneton__rim"></span>
<span class="art-banneton__basket"></span>
<span class="art-banneton__tag">+40%</span>
</div>
</div>
</div>
</article>
<!-- 4.0 BAKE -->
<article class="stage stage--coral">
<div class="stage__rail" aria-hidden="true">
<div class="stage__node"><b>4<small>.0</small></b></div>
<div class="stage__line"></div>
</div>
<div class="stage__panel">
<div class="stage__copy">
<p class="stage__label">4.0 · Bake · Dawn</p>
<h3 class="stage__title">Score it, bake it, slice it</h3>
<p class="stage__text">Straight from the fridge, slash the top and lower it into a screaming-hot pot — twenty minutes lidded for the rise, twenty more open for the crust. <b>Pull it at the colour you like</b>, then wait the hardest hour of all: letting it cool before you cut.</p>
<div class="stage__chips">
<span class="chip"><span class="chip__dot"></span> 45 min bake</span>
<span class="chip chip--hands"><span class="chip__dot"></span> Hands-on · 10 min</span>
</div>
</div>
<div class="stage__art" aria-hidden="true">
<div class="art-loaf">
<span class="art-loaf__boule"></span>
<span class="art-loaf__score"></span>
<span class="art-loaf__ear"></span>
<span class="st"></span><span class="st"></span><span class="st"></span>
</div>
</div>
</div>
</article>
</div>
</section>
<!-- ─── The honest math · counters ─── -->
<section class="section section--band section--mint" id="numbers" aria-labelledby="num-title">
<header class="section__head section__head--center">
<p class="eyebrow"><span class="eyebrow__dot eyebrow__dot--mint"></span> The honest math</p>
<h2 class="section__title" id="num-title">Mostly, you wait. That's the whole trick.</h2>
</header>
<dl class="numbers">
<div class="bignum">
<dd class="bignum__v"><span class="bignum__pre"></span><span class="count" data-to="45">45</span><span class="bignum__u">min</span></dd>
<dt class="bignum__k">your hands are actually busy — spread across two unhurried days.</dt>
</div>
<div class="bignum">
<dd class="bignum__v"><span class="count" data-to="36">36</span><span class="bignum__u">hrs</span></dd>
<dt class="bignum__k">the dough ferments on its own while you do literally anything else.</dt>
</div>
<div class="bignum">
<dd class="bignum__v"><span class="count" data-to="4">4</span></dd>
<dt class="bignum__k">simple stages — the same four, in the same order, every single loaf.</dt>
</div>
</dl>
</section>
<!-- ─── Voices · color-shift cards ─── -->
<section class="section" id="voices" aria-labelledby="vo-title">
<header class="section__head">
<p class="eyebrow"><span class="eyebrow__dot eyebrow__dot--cyan"></span> From the kitchen</p>
<h2 class="section__title" id="vo-title">People who used to buy their bread.</h2>
</header>
<ul class="voices">
<li class="voice" data-accent="mint">
<p class="voice__text">"My first three loaves came out like frisbees. Bubble was the first thing to tell me my starter was just hungry, not dead."</p>
<p class="voice__by"><span class="voice__chip"></span><b>Priya</b> · baking since March</p>
</li>
<li class="voice" data-accent="cyan">
<p class="voice__text">"The fold reminders are the whole product, honestly. I'd always miss the second one and end up with a brick."</p>
<p class="voice__by"><span class="voice__chip"></span><b>Marcus</b> · Saturday baker</p>
</li>
<li class="voice" data-accent="pear">
<p class="voice__text">"I feed it Thursday night, mix Friday, bake to a podcast on Saturday morning. It finally just fits around my week."</p>
<p class="voice__by"><span class="voice__chip"></span><b>Lena</b> · 60 loaves in</p>
</li>
</ul>
</section>
<!-- ─── Pricing ─── -->
<section class="section section--band" id="pricing" aria-labelledby="price-title">
<header class="section__head section__head--center">
<p class="eyebrow"><span class="eyebrow__dot eyebrow__dot--pear"></span> Pricing</p>
<h2 class="section__title" id="price-title">Free to bake. Cheap to get good.</h2>
</header>
<div class="plans">
<article class="plan">
<p class="plan__name">Free</p>
<p class="plan__price"><span class="plan__num">$0</span></p>
<p class="plan__note">The recipe, the four stages, and manual timers. Forever.</p>
<ul class="plan__list">
<li>The full guided method</li>
<li>Manual stage timers</li>
<li>One starter to track</li>
</ul>
<a class="btn btn--outline btn--mint btn--block" href="#" data-burst>Read the recipe</a>
</article>
<article class="plan plan--feature">
<span class="plan__badge">Most baked</span>
<p class="plan__name">Plus</p>
<p class="plan__price"><span class="plan__num">$4</span><span class="plan__unit">/ month</span></p>
<p class="plan__note">Reminders that read your kitchen, and every loaf you'll ever bake. Or $30 a year.</p>
<ul class="plan__list">
<li>Smart reminders, timed to your kitchen's temperature</li>
<li>Unlimited bakes &amp; saved recipes</li>
<li>Hydration &amp; timing calculator</li>
<li>Apple Watch &amp; home-screen widgets</li>
</ul>
<a class="btn btn--mint btn--block" href="#" data-burst>Start a free month</a>
</article>
</div>
</section>
<!-- ─── Final CTA · Narrative Workflow foot ─── -->
<section class="section closer" aria-labelledby="closer-title">
<h2 class="closer__title" id="closer-title">Start at stage one.</h2>
<p class="closer__lede">Feed a starter tonight and you'll have something worth slicing by the weekend. The recipe's free — read the whole thing before you commit to anything.</p>
<div class="hero__cta" style="justify-content:center">
<a class="btn btn--mint btn--lg" href="#" data-burst>Feed your starter <span class="btn__arrow" aria-hidden="true"></span></a>
<a class="btn btn--outline btn--lg btn--mint" href="#pricing">See pricing</a>
</div>
</section>
</main>
<footer class="footer">
<div class="footer__inner">
<p class="footer__statement">Bread takes its time.</p>
<div class="footer__meta">
<a class="footer__brand" href="#"><span class="bub-mark" aria-hidden="true"></span> Bubble</a>
<p class="footer__col"><span>Bubble Bakes, Inc.</span><span>Remote · flour everywhere</span></p>
<p class="footer__col"><a href="#stages">The method</a><a href="#numbers">The math</a><a href="#pricing">Pricing</a><a href="#">hello@bubble.bake</a></p>
<p class="footer__col footer__copy">© 2024 — 2026 Bubble<br /><span class="footer__hash">feed · mix · prove · bake</span></p>
</div>
</div>
</footer>
<script src="script.js"></script>
</body>
</html>
+81
View File
@@ -0,0 +1,81 @@
/* Hum · Bubble — N10 floating-on-scroll morph · counter tick-up · stage reveal · star-burst. */
(function () {
var reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
/* ── N10 · morph the bar into a floating pill past a threshold (single class toggle) ── */
var nav = document.getElementById('nav');
if (nav) {
var ticking = false;
function onScroll() {
var floating = window.scrollY > 24;
nav.classList.toggle('is-floating', floating);
ticking = false;
}
window.addEventListener('scroll', function () {
if (!ticking) { requestAnimationFrame(onScroll); ticking = true; }
}, { passive: true });
onScroll();
}
/* ── Counter tick-up on view-enter ── */
var counters = Array.prototype.slice.call(document.querySelectorAll('.count'));
function runCount(el) {
var raw = el.dataset.to || el.textContent;
var to = parseInt(String(raw).replace(/[^0-9]/g, ''), 10) || 0;
function fmt(n) { return n.toLocaleString('en-US'); }
if (reduce) { el.textContent = fmt(to); return; }
var dur = 1200, start = performance.now();
function tick(now) {
var p = Math.min((now - start) / dur, 1);
var eased = 1 - Math.pow(1 - p, 3);
el.textContent = fmt(Math.round(to * eased));
if (p < 1) { requestAnimationFrame(tick); }
else {
el.textContent = fmt(to);
if (el.animate) { el.animate([{ transform: 'scale(1)' }, { transform: 'scale(1.07)' }, { transform: 'scale(1)' }], { duration: 320, easing: 'ease-out' }); }
}
}
requestAnimationFrame(tick);
}
/* ── Stage reveal · sweep in as each enters the viewport ── */
var stages = Array.prototype.slice.call(document.querySelectorAll('.stage'));
if ('IntersectionObserver' in window) {
if (counters.length) {
var ioCount = new IntersectionObserver(function (entries) {
entries.forEach(function (e) { if (e.isIntersecting) { runCount(e.target); ioCount.unobserve(e.target); } });
}, { threshold: 0.6 });
counters.forEach(function (c) { ioCount.observe(c); });
}
if (stages.length) {
var ioStage = new IntersectionObserver(function (entries) {
entries.forEach(function (e) { if (e.isIntersecting) { e.target.classList.add('is-in'); ioStage.unobserve(e.target); } });
}, { threshold: 0.25, rootMargin: '0px 0px -10% 0px' });
stages.forEach(function (s) { ioStage.observe(s); });
}
} else {
counters.forEach(runCount);
stages.forEach(function (s) { s.classList.add('is-in'); });
}
/* ── Star-burst micro-celebration + character reaction on primary actions ── */
var starter = document.getElementById('starter');
function burst(x, y) {
var s = document.createElement('span');
s.className = 'star-burst';
s.style.position = 'absolute';
s.style.left = (x - 12) + 'px';
s.style.top = (y - 12) + 'px';
document.body.appendChild(s);
setTimeout(function () { s.remove(); }, 460);
}
document.querySelectorAll('[data-burst]').forEach(function (el) {
el.addEventListener('click', function (ev) {
if (el.getAttribute('href') === '#') ev.preventDefault();
if (reduce) return;
burst(ev.pageX, ev.pageY);
if (starter) { starter.classList.remove('is-react'); void starter.offsetWidth; starter.classList.add('is-react'); }
});
});
})();
+325
View File
@@ -0,0 +1,325 @@
/* Hallmark · macrostructure: Narrative Workflow · numbered stage timeline (feed → mix → prove → bake)
* theme: Hum · nav: N10 floating-on-scroll morph · footer: Ft5 statement · lead accent: mint
* character: bubbling starter jar (pear) · brief: "Bubble — guided sourdough, one stage at a time." · hum-07
*/
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; }
html { overflow-x: clip; scroll-behavior: smooth; }
body {
background: var(--color-paper); color: var(--color-ink);
font-family: var(--font-body); font-size: 1rem; line-height: 1.55;
font-variant-numeric: tabular-nums; font-feature-settings: "ss01" on;
-webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility;
padding-top: var(--bar-h);
}
img, svg { display: block; max-width: 100%; }
::selection { background: oklch(80% 0.16 150 / 0.32); color: var(--color-ink); }
a { color: inherit; text-decoration: none; }
a:focus-visible, button:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 3px; border-radius: 6px; }
button { font: inherit; background: none; border: 0; padding: 0; cursor: pointer; color: inherit; }
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }
.eyebrow { font-family: var(--font-label); font-size: 11px; letter-spacing: 0.10em; text-transform: uppercase; color: var(--color-ink-2); opacity: 0.78; margin: 0 0 1rem; display: inline-flex; align-items: center; gap: 0.55em; }
.eyebrow__dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; }
.eyebrow__dot--pear { background: var(--color-accent); }
.eyebrow__dot--cyan { background: var(--color-accent-2); }
.eyebrow__dot--coral { background: var(--color-accent-3); }
.eyebrow__dot--mint { background: var(--color-mint); }
.eyebrow__dot--lavender { background: var(--color-lavender); }
/* Emphasis highlight — clipped background gradient on the text itself (tracks wraps). Mint default. */
em, .hl {
font-style: normal; color: inherit;
background-image: linear-gradient(var(--hl, oklch(80% 0.16 150 / 0.55)) 0 0);
background-repeat: no-repeat; background-size: 100% 0.14em; background-position: 0 100%;
-webkit-box-decoration-break: clone; box-decoration-break: clone; padding-bottom: 0.24em;
}
.hl--pear { --hl: oklch(86% 0.18 95 / 0.6); }
.hl--coral { --hl: oklch(68% 0.24 18 / 0.32); }
.hl--cyan { --hl: oklch(66% 0.18 235 / 0.30); }
/* ═══ THE CANONICAL HUM BUTTON SYSTEM (copied verbatim from hum-buttons) ═══ */
.btn {
--btn-face: var(--color-accent); --btn-ink: var(--color-ink); --btn-edge: var(--color-accent-deep);
--btn-cast: oklch(76% 0.20 95 / 0.45); --btn-line: var(--color-accent-deep);
display: inline-flex; align-items: center; justify-content: center; gap: 0.5em;
padding: 0.8rem 1.4rem;
font-family: var(--font-body); font-weight: 600; font-size: 0.9375rem; letter-spacing: -0.005em;
border: 0; border-radius: var(--radius-pill);
color: var(--btn-ink); background: var(--btn-face);
cursor: pointer; white-space: nowrap; user-select: none; position: relative; isolation: isolate;
box-shadow: 0 4px 0 0 var(--btn-edge), 0 6px 12px -3px var(--btn-cast);
transform: translateY(0);
transition: transform 140ms cubic-bezier(0.2, 0.7, 0.3, 1),
box-shadow 140ms cubic-bezier(0.2, 0.7, 0.3, 1), background-color 160ms ease;
}
.btn:hover, .btn.is-hover { transform: translateY(-2px); box-shadow: 0 6px 0 0 var(--btn-edge), 0 12px 22px -4px var(--btn-cast); }
.btn:active, .btn.is-active { transform: translateY(3px); box-shadow: 0 1px 0 0 var(--btn-edge), 0 2px 6px -2px var(--btn-cast); transition-duration: 70ms; }
.btn:focus-visible { outline: 3px solid color-mix(in oklch, var(--btn-edge) 70%, var(--color-focus)); outline-offset: 3px; }
.btn[disabled], .btn.is-disabled { opacity: 0.5; cursor: not-allowed; pointer-events: none; transform: none; box-shadow: 0 4px 0 0 var(--btn-edge); }
.btn__arrow { transition: transform 200ms cubic-bezier(0.2, 0.7, 0.3, 1); }
.btn:hover .btn__arrow, .btn.is-hover .btn__arrow { transform: translateX(3px); }
.btn--sm { padding: 0.5rem 0.95rem; font-size: 0.85rem; }
.btn--lg { padding: 1rem 1.7rem; font-size: 1.0625rem; }
.btn--block { width: 100%; }
.btn--pear { --btn-face: var(--color-accent); --btn-ink: var(--color-ink); --btn-edge: var(--color-accent-deep); --btn-cast: oklch(76% 0.20 95 / 0.45); --btn-line: var(--color-accent-deep); }
.btn--coral { --btn-face: var(--color-accent-3); --btn-ink: var(--color-paper); --btn-edge: var(--color-accent-3-deep); --btn-cast: oklch(58% 0.26 18 / 0.42); --btn-line: var(--color-accent-3-deep); }
.btn--cyan { --btn-face: var(--color-accent-2); --btn-ink: var(--color-paper); --btn-edge: var(--color-accent-2-deep); --btn-cast: oklch(56% 0.20 235 / 0.40); --btn-line: var(--color-accent-2-deep); }
.btn--lav { --btn-face: var(--color-lavender); --btn-ink: var(--color-ink); --btn-edge: var(--color-lavender-deep); --btn-cast: oklch(64% 0.18 305 / 0.42); --btn-line: var(--color-lavender-deep); }
.btn--mint { --btn-face: var(--color-mint); --btn-ink: var(--color-ink); --btn-edge: var(--color-mint-deep); --btn-cast: oklch(70% 0.18 150 / 0.42); --btn-line: var(--color-mint-deep); }
.btn--ink { --btn-face: var(--color-ink); --btn-ink: var(--color-paper); --btn-edge: oklch(8% 0.01 250); --btn-cast: oklch(20% 0.012 250 / 0.4); --btn-line: var(--color-ink); }
.btn--soft { box-shadow: 0 1px 2px oklch(20% 0.012 250 / 0.12), 0 1px 1px oklch(20% 0.012 250 / 0.05); }
.btn--soft:hover, .btn--soft.is-hover { transform: translateY(-2px); box-shadow: 0 12px 26px -8px var(--btn-cast), 0 2px 6px oklch(20% 0.012 250 / 0.08); }
.btn--soft:active, .btn--soft.is-active { transform: translateY(0); box-shadow: 0 1px 2px oklch(20% 0.012 250 / 0.12); transition-duration: 90ms; }
.btn--outline {
background: transparent; color: var(--btn-line);
border: 1.5px solid color-mix(in oklch, var(--btn-line) 42%, var(--color-rule));
box-shadow: none; overflow: hidden;
transition: color 240ms ease, border-color 240ms ease, transform 140ms cubic-bezier(0.2, 0.7, 0.3, 1);
}
.btn--outline::before { content: ""; position: absolute; inset: 0; z-index: -1; background: var(--btn-face); transform: scaleY(0); transform-origin: bottom; transition: transform 300ms cubic-bezier(0.2, 0.7, 0.3, 1); }
.btn--outline:hover, .btn--outline.is-hover { color: var(--btn-ink); border-color: var(--btn-face); }
.btn--outline:hover::before, .btn--outline.is-hover::before { transform: scaleY(1); }
.btn--outline:active, .btn--outline.is-active { transform: translateY(1px); }
.btn--text { background: transparent; color: var(--color-ink-2); box-shadow: none; padding-inline: 0.5rem; }
.btn--text:hover { color: var(--color-ink); transform: none; box-shadow: none; }
.btn--text:active { transform: none; box-shadow: none; }
/* ═══ NAV · N10 floating-on-scroll morph ═══
Four laws: height-constant (inner height + 1px border box never change) · transform-for-offset
(the float gap is translateY, never margin/top) · cross-fade-everything (max-width, radius,
shadow, border-color, padding, bg all transition together) · single-curve (one duration + ease). */
:root { --bar-h: 66px; }
.nav { position: fixed; top: 0; left: 0; right: 0; z-index: 500; display: flex; justify-content: center; height: var(--bar-h); }
.nav__inner {
width: 100%; max-width: 100%; height: var(--bar-h);
display: flex; align-items: center; gap: 1.25rem; padding-inline: var(--page-gutter);
background: oklch(97% 0.012 95 / 0.82);
backdrop-filter: blur(18px) saturate(160%); -webkit-backdrop-filter: blur(18px) saturate(160%);
border: 1px solid transparent; border-bottom-color: var(--color-rule); border-radius: 0;
box-shadow: 0 0 0 0 oklch(20% 0.012 250 / 0);
transform: translateY(0); will-change: transform, max-width;
transition: max-width 420ms var(--ease-out), border-radius 420ms var(--ease-out),
transform 420ms var(--ease-out), box-shadow 420ms var(--ease-out),
border-color 420ms var(--ease-out), padding 420ms var(--ease-out), background-color 420ms var(--ease-out);
}
.nav.is-floating .nav__inner {
max-width: min(64rem, calc(100% - 1.5rem));
transform: translateY(0.7rem);
border-color: var(--color-rule); border-radius: var(--radius-pill);
box-shadow: var(--shadow-card-hover);
padding-inline: 1.1rem;
background: oklch(97% 0.012 95 / 0.9);
}
.nav__brand { display: inline-flex; align-items: center; gap: 0.6em; font-family: var(--font-display); font-weight: 700; font-size: 1.25rem; letter-spacing: -0.02em; color: var(--color-ink); }
.nav__links { display: flex; align-items: center; gap: 0.3rem; margin-left: auto; }
.nav__links a { padding: 0.5rem 0.8rem; font-size: 0.9rem; font-weight: 500; color: var(--color-ink-2); border-radius: 10px; transition: color 160ms, background 160ms; }
.nav__links a:hover { color: var(--color-ink); background: var(--color-paper-2); }
.nav__actions { display: flex; align-items: center; gap: 0.5rem; margin-left: 0.4rem; }
@media (max-width: 880px) { .nav__links { display: none; } .nav__actions .btn--text { display: none; } }
/* the bubbling starter mark in the wordmark / footer */
.bub-mark { position: relative; display: inline-block; width: 22px; height: 26px; flex: 0 0 auto; vertical-align: middle; }
.bub-mark::before { content: ""; position: absolute; inset: 4px 1px 0; border: 2.5px solid var(--color-ink); border-radius: 5px 5px 8px 8px; background: linear-gradient(var(--color-paper) 46%, var(--color-accent) 46%); }
.bub-mark::after { content: ""; position: absolute; top: 0; left: 50%; transform: translateX(-50%); width: 13px; height: 4px; border-radius: 3px; background: var(--color-accent-deep); }
/* ═══ HERO · Narrative Workflow opening ═══ */
.hero { max-width: var(--page-max); margin: 0 auto; padding: clamp(2.5rem, 6vh, 5rem) var(--page-gutter) clamp(2rem, 4vh, 3.5rem); }
.hero__grid { display: grid; grid-template-columns: 1fr; gap: clamp(2.5rem, 5vw, 4rem); align-items: center; }
@media (min-width: 940px) { .hero__grid { grid-template-columns: 1.15fr 0.85fr; } }
/* process strip — the four stages at a glance, joined by a line */
.process { display: flex; align-items: center; gap: 0; margin: 0 0 2rem; flex-wrap: wrap; }
.process__step { display: inline-flex; align-items: center; gap: 0.5em; font-family: var(--font-label); font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--color-ink-2); padding: 0.4rem 0.7rem; border-radius: 999px; background: var(--color-paper-2); border: 1px solid var(--color-rule); }
.process__dot { width: 8px; height: 8px; border-radius: 50%; }
.process__link { width: clamp(0.75rem, 2vw, 1.6rem); height: 2px; background: var(--color-rule-2); border-radius: 2px; opacity: 0.7; }
.process__step .num { color: var(--color-muted); }
.hero__title { font-family: var(--font-display); font-weight: 700; font-size: clamp(2.4rem, 5vw + 0.5rem, 4.25rem); line-height: 1.02; letter-spacing: -0.03em; color: var(--color-ink); margin: 0 0 1.25rem; text-wrap: balance; }
.hero__lede { font-size: 1.1875rem; line-height: 1.55; color: var(--color-ink-2); max-width: 46ch; margin: 0 0 1.9rem; }
.hero__cta { display: flex; flex-wrap: wrap; gap: 0.75rem; align-items: center; }
.hero__note { font-family: var(--font-label); font-size: 11px; letter-spacing: 0.04em; text-transform: uppercase; color: var(--color-muted); margin: 1.4rem 0 0; }
/* hero right: the character jar + a reminder card */
.hero__stage { position: relative; display: flex; flex-direction: column; align-items: center; gap: 1.5rem; }
/* the starter character (pear, bubbling, pulses; bursts on CTA) */
.starter { position: relative; width: 150px; height: 184px; }
.starter.is-react { animation: starter-pop 480ms var(--ease-spring); }
@keyframes starter-pop { 0% { transform: scale(1); } 35% { transform: scale(1.08) rotate(-2deg); } 70% { transform: scale(0.97); } 100% { transform: scale(1); } }
.starter__pulse { position: absolute; inset: 18px 12px 0; border-radius: 16px 16px 26px 26px; background: var(--color-accent); opacity: 0.18; animation: starter-breathe 4s var(--ease-soft) infinite; }
@keyframes starter-breathe { 0%,100% { transform: scale(1); opacity: 0.16; } 50% { transform: scale(1.06); opacity: 0.26; } }
.starter__jar { position: absolute; inset: 18px 12px 0; border: 3.5px solid var(--color-ink); border-radius: 14px 14px 24px 24px; background: var(--color-paper); overflow: hidden; }
.starter__lid { position: absolute; top: 0; left: 50%; transform: translateX(-50%); width: 92px; height: 16px; border-radius: 7px; background: var(--color-accent-deep); box-shadow: 0 3px 0 -1px oklch(70% 0.20 95); }
.starter__lid::after { content: ""; position: absolute; inset: -7px 14px auto; height: 8px; border-radius: 6px 6px 0 0; background: var(--color-accent); }
.starter__fill { position: absolute; left: 0; right: 0; bottom: 0; height: 54%; background: linear-gradient(var(--color-accent), var(--color-accent-deep)); }
.starter__fill::before { content: ""; position: absolute; top: -9px; left: 0; right: 0; height: 16px; border-radius: 50%; background: var(--color-accent); }
.starter__bubble { position: absolute; bottom: 24%; border-radius: 50%; background: oklch(96% 0.05 95 / 0.85); animation: bubble-rise 2.8s var(--ease-soft) infinite; }
.starter__bubble:nth-child(1) { left: 32%; width: 9px; height: 9px; animation-delay: 0s; }
.starter__bubble:nth-child(2) { left: 54%; width: 6px; height: 6px; animation-delay: 0.8s; }
.starter__bubble:nth-child(3) { left: 44%; width: 11px; height: 11px; animation-delay: 1.5s; }
.starter__bubble:nth-child(4) { left: 66%; width: 7px; height: 7px; animation-delay: 2.1s; }
@keyframes bubble-rise { 0% { transform: translateY(0) scale(0.7); opacity: 0; } 18% { opacity: 0.9; } 80% { opacity: 0.7; } 100% { transform: translateY(-58px) scale(1); opacity: 0; } }
.remind { width: 100%; max-width: 320px; background: var(--color-paper); border: 1.5px solid var(--color-rule); border-radius: 18px; padding: 0.9rem 1.05rem; box-shadow: var(--shadow-card); display: flex; gap: 0.8rem; align-items: flex-start; }
.remind__icon { width: 34px; height: 34px; border-radius: 10px; background: var(--tint-mint); display: grid; place-items: center; flex: 0 0 auto; }
.remind__icon::after { content: ""; width: 13px; height: 13px; border-radius: 50%; border: 3px solid var(--color-mint-deep); }
.remind__head { font-family: var(--font-label); font-size: 10px; letter-spacing: 0.10em; text-transform: uppercase; color: var(--color-muted); margin: 0 0 0.25rem; display: flex; gap: 0.5em; align-items: center; }
.remind__head b { color: var(--color-mint-deep); }
.remind__body { font-size: 0.9375rem; font-weight: 500; line-height: 1.4; color: var(--color-ink); margin: 0; }
.remind__time { color: var(--color-ink-2); font-weight: 400; }
/* ═══ SECTIONS (one shell, identical edges — band carries only bg + block padding) ═══ */
.section { max-width: var(--page-max); margin: 0 auto; padding: var(--section-gap) var(--page-gutter); }
.section--band { max-width: none; padding-inline: 0; background: var(--color-paper-2); border-block: 1px solid var(--color-rule); }
.section--band > * { max-width: var(--page-max); margin-inline: auto; padding-inline: var(--page-gutter); }
.section--mint { background: var(--tint-mint); }
.section__head { margin-bottom: 3rem; max-width: 54rem; }
.section__head--center { text-align: center; margin-inline: auto; }
.section__head--center .eyebrow { justify-content: center; }
.section__title { font-family: var(--font-display); font-weight: 700; font-size: clamp(2rem, 3.5vw + 0.5rem, 3.25rem); line-height: 1.08; letter-spacing: -0.025em; color: var(--color-ink); margin: 0; max-width: 22ch; text-wrap: balance; }
.section__head--center .section__title { margin-inline: auto; }
.section__lede { font-size: 1.0625rem; line-height: 1.6; color: var(--color-ink-2); max-width: 52ch; margin: 1rem 0 0; }
.section__head--center .section__lede { margin-inline: auto; }
/* ═══ THE TIMELINE SPINE · numbered stages ═══ */
.stages { display: flex; flex-direction: column; }
.stage { display: grid; grid-template-columns: clamp(3.25rem, 4vw + 2rem, 4.75rem) 1fr; gap: clamp(1.1rem, 3vw, 2.5rem); }
.stage--mint { --st: var(--color-mint); --st-deep: var(--color-mint-deep); --st-ink: var(--color-ink); --st-tint: var(--tint-mint); }
.stage--cyan { --st: var(--color-accent-2); --st-deep: var(--color-accent-2-deep); --st-ink: var(--color-paper); --st-tint: var(--tint-cyan); }
.stage--pear { --st: var(--color-accent); --st-deep: var(--color-accent-deep); --st-ink: var(--color-ink); --st-tint: var(--tint-pear); }
.stage--coral { --st: var(--color-accent-3); --st-deep: var(--color-accent-3-deep); --st-ink: var(--color-paper); --st-tint: var(--tint-coral); }
.stage__rail { position: relative; display: flex; flex-direction: column; align-items: center; }
.stage__node { width: clamp(3.25rem, 4vw + 2rem, 4.75rem); aspect-ratio: 1; border-radius: 22px; display: grid; place-items: center; background: var(--st); color: var(--st-ink); box-shadow: 0 5px 0 -1px var(--st-deep), var(--shadow-card); font-family: var(--font-display); font-weight: 700; line-height: 1; transform: scale(0.55); opacity: 0; transition: transform 520ms var(--ease-spring), opacity 300ms ease; }
.stage__node b { font-size: clamp(1.4rem, 1.6vw + 0.7rem, 2rem); letter-spacing: -0.03em; }
.stage__node small { font-size: 0.5em; opacity: 0.72; }
.stage__line { flex: 1 1 auto; width: 3px; min-height: clamp(1.5rem, 4vh, 2.75rem); background: var(--st); border-radius: 3px; margin-block: 0.6rem; transform: scaleY(0); transform-origin: top; transition: transform 600ms var(--ease-out) 130ms; }
.stage:last-child .stage__line { display: none; }
.stage__panel { background: var(--color-paper); border: 1.5px solid var(--color-rule); border-radius: var(--radius-card); box-shadow: var(--shadow-card); padding: clamp(1.4rem, 3vw, 2.25rem); margin-bottom: clamp(2rem, 5vh, 3.75rem); display: grid; grid-template-columns: 1fr; gap: 1.5rem; align-items: center; opacity: 0; transform: translateX(28px); transition: opacity 620ms var(--ease-out), transform 620ms var(--ease-out); }
@media (min-width: 720px) { .stage__panel { grid-template-columns: 1fr clamp(120px, 22vw, 168px); } }
.stage.is-in .stage__node { transform: none; opacity: 1; }
.stage.is-in .stage__line { transform: scaleY(1); }
.stage.is-in .stage__panel { opacity: 1; transform: none; }
.stage__label { font-family: var(--font-label); font-size: 11px; letter-spacing: 0.10em; text-transform: uppercase; color: var(--st-deep); margin: 0 0 0.6rem; font-weight: 500; }
.stage__title { font-family: var(--font-display); font-weight: 700; font-size: clamp(1.5rem, 2vw + 0.6rem, 2.1rem); line-height: 1.1; letter-spacing: -0.02em; color: var(--color-ink); margin: 0 0 0.7rem; }
.stage__text { font-size: 0.9875rem; line-height: 1.6; color: var(--color-ink-2); margin: 0 0 1.15rem; max-width: 48ch; }
.stage__text b { font-weight: 600; color: var(--color-ink); }
.stage__chips { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.chip { font-family: var(--font-label); font-size: 10.5px; letter-spacing: 0.05em; text-transform: uppercase; padding: 0.35rem 0.7rem; border-radius: 999px; background: var(--st-tint); color: var(--color-ink); display: inline-flex; align-items: center; gap: 0.45em; white-space: nowrap; }
.chip__dot { width: 7px; height: 7px; border-radius: 50%; background: var(--st-deep); }
.chip--hands { background: var(--color-paper-2); color: var(--color-ink-2); }
.chip--hands .chip__dot { background: var(--color-rule-2); }
/* stage art frame */
.stage__art { position: relative; height: clamp(120px, 22vw, 160px); border-radius: 16px; background: var(--st-tint); display: grid; place-items: center; overflow: hidden; }
/* — FEED: a feeding jar with rising bubbles — */
.art-jar { position: relative; width: 78px; height: 100px; }
.art-jar__lid { position: absolute; top: 0; left: 50%; transform: translateX(-50%); width: 58px; height: 11px; border-radius: 5px; background: var(--st-deep); z-index: 2; }
.art-jar__body { position: absolute; inset: 8px 0 0; border: 3px solid var(--color-ink); border-radius: 8px 8px 14px 14px; background: var(--color-paper); overflow: hidden; }
.art-jar__fill { position: absolute; left: 0; right: 0; bottom: 0; height: 56%; background: var(--st); }
.art-jar__fill::before { content: ""; position: absolute; top: -7px; left: 0; right: 0; height: 13px; border-radius: 50%; background: var(--st); }
.art-jar .b { position: absolute; border-radius: 50%; background: oklch(99% 0.02 95 / 0.8); animation: bubble-rise 2.6s var(--ease-soft) infinite; }
.art-jar .b:nth-child(3) { left: 30%; bottom: 18%; width: 8px; height: 8px; }
.art-jar .b:nth-child(4) { left: 55%; bottom: 14%; width: 6px; height: 6px; animation-delay: 1s; }
.art-jar .b:nth-child(5) { left: 44%; bottom: 22%; width: 5px; height: 5px; animation-delay: 1.8s; }
/* — MIX: a dough mound sitting in a bowl, dusted with flour (dough behind the bowl's front) — */
.art-bowl { position: relative; width: 120px; height: 80px; }
.art-bowl__dough { position: absolute; z-index: 1; bottom: 20px; left: 50%; transform: translateX(-50%); width: 76px; height: 46px; border-radius: 50% 50% 42% 42%; background: var(--st); box-shadow: inset 0 -6px 0 -2px var(--st-deep); }
.art-bowl__rim { position: absolute; z-index: 3; bottom: 38px; left: 6px; right: 6px; height: 13px; border-radius: 50%; border: 3px solid var(--color-ink); background: transparent; }
.art-bowl__bowl { position: absolute; z-index: 2; bottom: 0; left: 8px; right: 8px; height: 44px; border: 3px solid var(--color-ink); border-top: 0; border-radius: 0 0 58px 58px / 0 0 52px 52px; background: var(--color-paper); }
.art-bowl .fl { position: absolute; z-index: 4; width: 4px; height: 4px; border-radius: 50%; background: var(--color-paper); box-shadow: 0 0 0 1.5px var(--st-deep); }
.art-bowl .fl:nth-child(4) { top: 8px; left: 34px; } .art-bowl .fl:nth-child(5) { top: 2px; left: 60px; } .art-bowl .fl:nth-child(6) { top: 14px; left: 82px; }
/* — PROVE: a banneton basket with a risen dome + rise tag — */
.art-banneton { position: relative; width: 120px; height: 80px; }
.art-banneton__basket { position: absolute; bottom: 0; left: 6px; right: 6px; height: 40px; border-radius: 0 0 46px 46px / 0 0 40px 40px; background: repeating-linear-gradient(96deg, var(--st) 0 6px, var(--st-deep) 6px 9px); border: 2.5px solid var(--color-ink); border-top: 0; }
.art-banneton__rim { position: absolute; top: 26px; left: 0; right: 0; height: 12px; border-radius: 50%; border: 3px solid var(--color-ink); background: var(--color-paper-2); }
.art-banneton__dome { position: absolute; top: 6px; left: 50%; transform: translateX(-50%); width: 76px; height: 40px; border-radius: 50% 50% 0 0; background: var(--color-accent); border: 2.5px solid var(--color-ink); border-bottom: 0; }
.art-banneton__tag { position: absolute; top: -4px; right: -2px; font-family: var(--font-label); font-size: 10px; font-weight: 500; letter-spacing: 0.04em; color: var(--color-ink); background: var(--color-paper); border: 1.5px solid var(--st-deep); border-radius: 999px; padding: 0.15rem 0.5rem; }
/* — BAKE: a scored loaf with an ear + steam — */
.art-loaf { position: relative; width: 122px; height: 84px; }
.art-loaf__boule { position: absolute; bottom: 4px; left: 50%; transform: translateX(-50%); width: 108px; height: 56px; border-radius: 50% 50% 44% 44% / 60% 60% 40% 40%; background: linear-gradient(var(--color-accent-3), var(--color-accent-3-deep)); border: 3px solid var(--color-ink); }
.art-loaf__score { position: absolute; top: 24px; left: 30px; width: 56px; height: 3px; background: oklch(96% 0.05 60 / 0.85); border-radius: 3px; transform: rotate(-12deg); }
.art-loaf__ear { position: absolute; top: 18px; left: 28px; width: 22px; height: 12px; border-radius: 50% 50% 0 0; background: oklch(80% 0.16 60); border: 2.5px solid var(--color-ink); border-bottom: 0; transform: rotate(-14deg); }
.art-loaf .st { position: absolute; top: -2px; width: 3px; height: 18px; border-radius: 3px; background: var(--color-rule-2); opacity: 0.7; animation: steam 2.4s ease-in-out infinite; }
.art-loaf .st:nth-child(4) { left: 44px; } .art-loaf .st:nth-child(5) { left: 60px; animation-delay: 0.6s; } .art-loaf .st:nth-child(6) { left: 76px; animation-delay: 1.1s; }
@keyframes steam { 0% { transform: translateY(4px) scaleY(0.7); opacity: 0; } 40% { opacity: 0.6; } 100% { transform: translateY(-10px) scaleY(1.1); opacity: 0; } }
/* ═══ HONEST NUMBERS (counter tick-up) ═══ */
.numbers { display: grid; grid-template-columns: 1fr; gap: 1rem; }
@media (min-width: 760px) { .numbers { grid-template-columns: repeat(3, 1fr); } }
.bignum { background: var(--color-paper); border: 1.5px solid var(--color-rule); border-radius: var(--radius-card); padding: 2.25rem 1.75rem; text-align: center; box-shadow: var(--shadow-card); }
.bignum__v { font-family: var(--font-display); font-weight: 700; font-size: clamp(3.25rem, 6vw, 4.75rem); line-height: 1; letter-spacing: -0.04em; color: var(--color-ink); margin: 0; display: inline-block; background-image: linear-gradient(oklch(80% 0.16 150 / 0.5) 0 0); background-repeat: no-repeat; background-size: 100% 0.14em; background-position: 0 88%; }
.bignum__pre { font-size: 0.46em; color: var(--color-muted); margin-right: 0.06em; vertical-align: 0.5em; }
.bignum__u { font-size: 0.42em; color: var(--color-mint-deep); margin-left: 0.04em; }
.bignum__k { font-size: 0.9375rem; line-height: 1.5; color: var(--color-ink-2); margin: 0.75rem auto 0; max-width: 26ch; }
/* ═══ VOICES (color-shift cards) ═══ */
.voices { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: 1fr; gap: 1rem; }
@media (min-width: 820px) { .voices { grid-template-columns: repeat(3, 1fr); } }
.voice { padding: 1.75rem; border-radius: var(--radius-card); border: 1.5px solid var(--color-rule); background: var(--color-paper); display: flex; flex-direction: column; gap: 1rem; transition: transform 320ms var(--ease-spring), background 320ms, box-shadow 320ms; }
.voice[data-accent="mint"]:hover { background: var(--tint-mint); }
.voice[data-accent="cyan"]:hover { background: var(--tint-cyan); }
.voice[data-accent="pear"]:hover { background: var(--tint-pear); }
.voice:hover { transform: translateY(-4px); box-shadow: var(--shadow-card-hover); }
.voice__text { font-family: var(--font-display); font-weight: 500; font-size: 1.0625rem; line-height: 1.45; letter-spacing: -0.01em; color: var(--color-ink); margin: 0; }
.voice__by { font-size: 0.875rem; color: var(--color-ink-2); margin: 0; padding-top: 0.85rem; border-top: 1px solid var(--color-rule); display: flex; align-items: center; gap: 0.6em; }
.voice__by b { font-weight: 600; color: var(--color-ink); }
.voice__chip { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto; }
.voice[data-accent="mint"] .voice__chip { background: var(--color-mint); }
.voice[data-accent="cyan"] .voice__chip { background: var(--color-accent-2); }
.voice[data-accent="pear"] .voice__chip { background: var(--color-accent); }
/* ═══ PRICING ═══ */
.plans { display: grid; grid-template-columns: 1fr; gap: 1rem; max-width: 48rem; margin-inline: auto; }
@media (min-width: 700px) { .plans { grid-template-columns: repeat(2, 1fr); } }
.plan { display: flex; flex-direction: column; gap: 0.85rem; padding: 2rem 1.85rem; border-radius: var(--radius-card); border: 1.5px solid var(--color-rule); background: var(--color-paper); box-shadow: var(--shadow-card); transition: transform 320ms var(--ease-spring); position: relative; }
.plan:hover { transform: translateY(-4px); }
.plan--feature { background: var(--tint-mint); border-color: var(--color-mint-deep); }
.plan__badge { position: absolute; top: -0.7rem; left: 1.85rem; font-family: var(--font-label); font-size: 10.5px; letter-spacing: 0.10em; text-transform: uppercase; padding: 0.3rem 0.7rem; background: var(--color-ink); color: var(--color-paper); border-radius: 999px; }
.plan__name { font-family: var(--font-label); font-size: 11px; letter-spacing: 0.10em; text-transform: uppercase; color: var(--color-muted); margin: 0; }
.plan__price { font-family: var(--font-display); font-weight: 700; font-size: 2.5rem; line-height: 1; letter-spacing: -0.03em; color: var(--color-ink); margin: 0; }
.plan__unit { font-family: var(--font-label); font-size: 0.3em; letter-spacing: 0.06em; text-transform: uppercase; color: var(--color-muted); margin-inline-start: 0.3em; vertical-align: 0.7em; }
.plan__note { font-size: 0.9rem; color: var(--color-ink-2); margin: 0; padding-bottom: 0.85rem; border-bottom: 1px solid var(--color-rule); line-height: 1.5; }
.plan__list { list-style: none; margin: 0 0 0.5rem; padding: 0; display: flex; flex-direction: column; gap: 0.6rem; flex: 1; }
.plan__list li { font-size: 0.9375rem; color: var(--color-ink-2); padding-left: 1.5rem; position: relative; }
.plan__list li::before { content: ""; position: absolute; left: 0; top: 0.4em; width: 12px; height: 12px; border-radius: 50%; background: var(--color-mint); box-shadow: inset 0 0 0 3.5px var(--color-paper); }
/* ═══ FINAL CTA ═══ */
.closer { text-align: center; }
.closer__title { font-family: var(--font-display); font-weight: 700; font-size: clamp(2.25rem, 5vw + 0.5rem, 4rem); line-height: 1.02; letter-spacing: -0.03em; color: var(--color-ink); margin: 0 0 1rem; text-wrap: balance; }
.closer__lede { font-size: 1.125rem; color: var(--color-ink-2); max-width: 44ch; margin: 0 auto 2rem; }
/* ═══ FOOTER (Ft5 statement) ═══ */
.footer { background: var(--color-paper-2); border-top: 1px solid var(--color-rule); }
.footer__inner { max-width: var(--page-max); margin: 0 auto; padding: clamp(4rem, 8vh, 6rem) var(--page-gutter) clamp(3rem, 6vh, 4.5rem); display: flex; flex-direction: column; gap: clamp(3rem, 6vh, 4.5rem); }
.footer__statement { font-family: var(--font-display); font-weight: 700; font-size: clamp(2.5rem, 5vw + 1rem, 5rem); line-height: 1.02; letter-spacing: -0.03em; color: var(--color-ink); margin: 0; }
.footer__meta { display: grid; grid-template-columns: auto 1fr 1fr 1fr; gap: 2rem; align-items: start; padding-top: 2rem; border-top: 1px solid var(--color-rule); }
@media (max-width: 880px) { .footer__meta { grid-template-columns: 1fr 1fr; } .footer__brand { grid-column: span 2; } }
@media (max-width: 540px) { .footer__meta { grid-template-columns: 1fr; } .footer__brand { grid-column: span 1; } }
.footer__brand { display: inline-flex; align-items: center; gap: 0.5em; font-family: var(--font-display); font-weight: 700; font-size: 1.25rem; color: var(--color-ink); align-self: start; }
.footer__col { margin: 0; font-family: var(--font-label); font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--color-muted); display: flex; flex-direction: column; gap: 0.4rem; }
.footer__col a { color: var(--color-muted); transition: color 180ms; }
.footer__col a:hover { color: var(--color-ink); }
.footer__hash { color: var(--color-rule-2); }
/* ═══ star-burst micro-celebration (coral) ═══ */
.star-burst { position: absolute; width: 24px; height: 24px; z-index: 999; pointer-events: none;
background: linear-gradient(90deg, transparent 47%, var(--color-accent-3) 47% 53%, transparent 53%),
linear-gradient(0deg, transparent 47%, var(--color-accent-3) 47% 53%, transparent 53%);
animation: star-burst 420ms ease-out forwards; }
@keyframes star-burst { 0% { transform: scale(0) rotate(0); opacity: 1; } 60% { transform: scale(1.2) rotate(35deg); opacity: 0.9; } 100% { transform: scale(1.4) rotate(45deg); opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
html { scroll-behavior: auto; }
.starter__pulse, .starter__bubble, .art-jar .b, .art-loaf .st { animation: none; }
.stage__node, .stage__line, .stage__panel { transition: none; opacity: 1; transform: none; }
* { animation-duration: 0.01ms !important; }
}
@@ -1,9 +1,10 @@
/* Hallmark · macrostructure: Marquee Hero · H1 hero knobs: today's-curio card, character=pulse-mark, pear-cta
* theme: Hum (catalog) · paper: oklch(97% 0.012 95) · accent: pear oklch(86% 0.18 95) + cyan oklch(66% 0.18 235) + coral oklch(68% 0.24 18)
* display: Plus Jakarta Sans 600 · body: Plus Jakarta Sans · labels: JetBrains Mono UPPERCASE
/* Hallmark · Hum · Bubble (guided sourdough app) · Narrative Workflow · N10 floating-on-scroll morph · hum-07
* theme: Hum (catalog) · paper oklch(97% 0.012 95)
* lead accent: mint oklch(80% 0.16 150) carries the primary CTA + most surfaces
* + cyan (mix) · pear (prove + the character jar) · coral (the bake climax, the one high-energy moment)
* display/body: Plus Jakarta Sans · labels: JetBrains Mono UPPERCASE
* axes: light / rounded-sans / multi-accent
* nav: N5 floating pill (relaxed for playful) · footer: Ft5 statement
* brief: "Curio — a 30-second daily curiosity app."
* footer: Ft5 statement · brief: "Bubble — guided sourdough, one stage at a time."
* v1.0.0
*/
@@ -37,7 +38,7 @@
--tint-pear: oklch(86% 0.18 95 / 0.16);
--tint-cyan: oklch(66% 0.18 235 / 0.14);
--tint-coral: oklch(68% 0.24 18 / 0.12);
--tint-mint: oklch(80% 0.16 150 / 0.16);
--tint-mint: oklch(80% 0.16 150 / 0.18);
--tint-lavender: oklch(74% 0.16 305 / 0.14);
/* Type — rounded sans only */
@@ -93,9 +94,6 @@
0 1px 2px oklch(20% 0.012 250 / 0.06);
--shadow-card-hover: 0 24px 56px -20px oklch(20% 0.012 250 / 0.18),
0 2px 6px oklch(20% 0.012 250 / 0.08);
--shadow-pear-base: 0 8px 0 -4px var(--color-accent-deep);
--shadow-pear-hover: 0 14px 0 -4px var(--color-accent-deep),
0 4px 12px oklch(20% 0.012 250 / 0.10);
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
--ease-soft: cubic-bezier(0.22, 0.61, 0.36, 1);
+1 -1
View File
@@ -6,7 +6,7 @@
{ "slug": "cobalt-01", "group": "theme", "theme": "Cobalt", "url": "examples/cobalt-01/", "title": "Distil", "kind": "content-extraction API", "durationSec": 16, "out": "_tests/_thumbs/cobalt-01-scroll.mp4" },
{ "slug": "carnival-01", "group": "theme", "theme": "Carnival", "url": "examples/carnival-01/", "title": "Cold Snap", "kind": "record-label EP page", "durationSec": 12, "out": "_tests/_thumbs/carnival-01-scroll.mp4" },
{ "slug": "lumen-01", "group": "theme", "theme": "Lumen", "url": "examples/lumen-01/", "title": "Cinder", "kind": "AI reasoning tool", "durationSec": 13, "out": "_tests/_thumbs/lumen-01-scroll.mp4" },
{ "slug": "hum-02", "group": "theme", "theme": "Hum", "url": "examples/hum-02/", "title": "Sprout", "kind": "daily-reading app", "durationSec": 13, "out": "_tests/_thumbs/hum-02-scroll.mp4" },
{ "slug": "hum-07", "group": "theme", "theme": "Hum", "url": "examples/hum-07/", "title": "Bubble", "kind": "guided sourdough app", "durationSec": 13, "out": "_tests/_thumbs/hum-07-scroll.mp4" },
{ "slug": "custom-01", "group": "custom", "url": "examples/custom-01/", "title": "Setting the Night Edition", "kind": "letterpress poem broadside", "durationSec": 14, "out": "_tests/_thumbs/custom-01-scroll.mp4" },
{ "slug": "custom-02", "group": "custom", "url": "examples/custom-02/", "title": "The Cascadia Nightjar", "kind": "sleeper-train ticket", "durationSec": 12, "out": "_tests/_thumbs/custom-02-scroll.mp4" },
+12 -40
View File
@@ -56,10 +56,10 @@
<link rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Albert+Sans:wght@400;500;600;700;800&family=Anton&family=Fraunces:ital,opsz,wght@0,9..144,300;0,9..144,400;0,9..144,500;0,9..144,600;0,9..144,900;1,9..144,400;1,9..144,500&family=Geist:wght@400;500;600;700&family=Geist+Mono:wght@400;500&family=Hanken+Grotesk:wght@400;500;600;700&family=Inter:wght@400;500;600;700&family=Inter+Tight:ital,wght@0,500;0,600;0,700;0,800;1,500;1,600;1,700&family=Newsreader:ital,opsz,wght@0,6..72,400;0,6..72,500;1,6..72,400;1,6..72,500&family=Playfair+Display:ital,wght@1,400;1,500;1,600&family=Public+Sans:wght@400;500;600;700&family=Young+Serif&family=Space+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" />
<link rel="stylesheet" href="css/tokens.css?v=18" />
<link rel="stylesheet" href="css/base.css?v=18" />
<link rel="stylesheet" href="css/components.css?v=18" />
<link rel="stylesheet" href="css/sections.css?v=18" />
<link rel="stylesheet" href="css/tokens.css?v=19" />
<link rel="stylesheet" href="css/base.css?v=19" />
<link rel="stylesheet" href="css/components.css?v=19" />
<link rel="stylesheet" href="css/sections.css?v=19" />
<script>
// Load theme before paint to avoid flash. Order: ?theme= → localStorage → default.
@@ -261,51 +261,23 @@
<p class="ex-card__prompt"><span class="ex-card__prompt-mark">$</span> /hallmark build an AI reasoning tool, Lumen</p>
</a>
<a class="ex-card" role="listitem" href="examples/hum-02/" target="_blank" rel="noopener" aria-label="Open the Sprout example — daily-reading app, Hum in a new tab">
<a class="ex-card" role="listitem" href="examples/hum-07/" target="_blank" rel="noopener" aria-label="Open the Bubble example, a guided sourdough app, Hum, in a new tab">
<div class="ex-card__thumb" aria-hidden="true">
<img src="_tests/_thumbs/hum-02.jpg" alt="" loading="lazy" width="1600" height="1000" />
<img src="_tests/_thumbs/hum-07.jpg" alt="" loading="lazy" width="1600" height="1000" />
</div>
<p class="ex-card__prompt"><span class="ex-card__prompt-mark">$</span> /hallmark build a playful daily-reading app, Hum</p>
<p class="ex-card__prompt"><span class="ex-card__prompt-mark">$</span> /hallmark build a guided sourdough app, Hum</p>
</a>
<!-- Custom / bespoke showcase. Scroll previews are generated by Remotion from the live
pages (see examples/scroll-demos.json); once rendered, swap each <img> for
<video data-hover-play src="_tests/_thumbs/<slug>-scroll.mp4" poster="..."> . -->
<a class="ex-card" role="listitem" href="examples/custom-01/" target="_blank" rel="noopener" aria-label="Open the Setting the Night Edition example — letterpress poem broadside, custom in a new tab">
<div class="ex-card__thumb" aria-hidden="true">
<img src="_tests/_thumbs/custom-01.jpg" alt="" loading="lazy" width="1600" height="1000" />
</div>
<p class="ex-card__prompt"><span class="ex-card__prompt-mark">$</span> /hallmark build a letterpress poem broadside, custom</p>
</a>
<a class="ex-card" role="listitem" href="examples/custom-02/" target="_blank" rel="noopener" aria-label="Open the Cascadia Nightjar example — sleeper-train ticket page, custom — in a new tab">
<div class="ex-card__thumb" aria-hidden="true">
<img src="_tests/_thumbs/custom-02.jpg" alt="" loading="lazy" width="1600" height="1000" />
</div>
<p class="ex-card__prompt"><span class="ex-card__prompt-mark">$</span> /hallmark build a sleeper-train ticket page, custom</p>
</a>
<a class="ex-card" role="listitem" href="examples/custom-03/" target="_blank" rel="noopener" aria-label="Open the Ferns & Fathom example — tea menu as a periodic table, custom — in a new tab">
<div class="ex-card__thumb" aria-hidden="true">
<img src="_tests/_thumbs/custom-03.jpg" alt="" loading="lazy" width="1600" height="1000" />
</div>
<p class="ex-card__prompt"><span class="ex-card__prompt-mark">$</span> /hallmark build a tea menu as a periodic table, custom</p>
</a>
<a class="ex-card" role="listitem" href="examples/custom-04/" target="_blank" rel="noopener" aria-label="Open the Mend Assembly example — repair-café manifesto poster, custom — in a new tab">
<!-- Custom / bespoke showcase. One featured bespoke build; the others live under
site/examples/custom-*. Scroll preview via Remotion (see examples/scroll-demos.json);
once rendered, swap the <img> for <video data-hover-play src="..." poster="...">. -->
<a class="ex-card" role="listitem" href="examples/custom-04/" target="_blank" rel="noopener" aria-label="Open the Mend Assembly example, a repair-café manifesto poster, custom, in a new tab">
<div class="ex-card__thumb" aria-hidden="true">
<img src="_tests/_thumbs/custom-04.jpg" alt="" loading="lazy" width="1600" height="1000" />
</div>
<p class="ex-card__prompt"><span class="ex-card__prompt-mark">$</span> /hallmark build a repair-café manifesto poster, custom</p>
</a>
<a class="ex-card" role="listitem" href="examples/custom-05/" target="_blank" rel="noopener" aria-label="Open the Hollow Coast example — sound-artist portfolio, custom — in a new tab">
<div class="ex-card__thumb" aria-hidden="true">
<img src="_tests/_thumbs/custom-05.jpg" alt="" loading="lazy" width="1600" height="1000" />
</div>
<p class="ex-card__prompt"><span class="ex-card__prompt-mark">$</span> /hallmark build a sound-artist portfolio, custom</p>
</a>
<a class="ex-card" role="listitem" href="examples/tally/" target="_blank" rel="noopener" aria-label="Open the Tally example — SaaS product page, modern-minimal — in a new tab">
<div class="ex-card__thumb ex-card__thumb--video" aria-hidden="true">
<video data-hover-play src="_tests/_thumbs/SaaS-loop.mp4" autoplay loop muted playsinline preload="metadata" width="1784" height="1080"></video>
@@ -1148,7 +1120,7 @@
</div>
</template>
<script type="module" src="js/main.js?v=18"></script>
<script type="module" src="js/main.js?v=19"></script>
</body>
</html>