Carnival rework, sharper anti-slop guards, and three new worked examples
Carnival: fixed the three palette drops whose second accent failed contrast, tightened the display tracking, and added section-rhythm, alignment, and drop-selection guidance so the theme lands well-aligned and readable by default. Lightened and tightened the Carnival headers on the landing. Anti-slop guards (skill references): heading-to-body alignment coherence as a principle rather than a rigid rule; centered overlays and command palettes must center via their container; and a hero headline is never only a number, a lead figure always pairs with words. Worked Examples: added three generated pages to the landing carousel, a small-batch honey farm, a risograph print fair, and an experimental type studio, after fixing their nav centering and section alignment. Media: removed the Soroe example card along with the unused scroll-videos, poster stills, and orphaned thumbnails it left behind, and bumped the asset cache-bust so the restyled CSS reloads.
|
Before Width: | Height: | Size: 126 KiB |
|
Before Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 133 KiB |
|
Before Width: | Height: | Size: 120 KiB |
|
Before Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 147 KiB |
|
Before Width: | Height: | Size: 163 KiB |
|
Before Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 144 KiB |
|
Before Width: | Height: | Size: 159 KiB |
|
After Width: | Height: | Size: 773 KiB |
|
After Width: | Height: | Size: 814 KiB |
|
After Width: | Height: | Size: 955 KiB |
|
Before Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 232 KiB |
|
Before Width: | Height: | Size: 124 KiB |
@@ -934,13 +934,14 @@
|
||||
--font-label: "JetBrains Mono", ui-monospace, monospace;
|
||||
--font-mono: "JetBrains Mono", ui-monospace, monospace;
|
||||
|
||||
--display-weight: 800;
|
||||
--display-weight: 700; /* lightened from 800 — 800 + loose tracking read too heavy */
|
||||
--display-style: normal;
|
||||
|
||||
--text-display: clamp(3.25rem, 6.5vw + 1rem, 6.5rem);
|
||||
--text-display-s: clamp(2.25rem, 4vw + 0.75rem, 3.5rem);
|
||||
|
||||
--tracking-display: 0.04em; /* all-caps, slightly tracked */
|
||||
--tracking-display: 0.02em; /* tight, professional (was 0.04em, which read as AI-spread) */
|
||||
--tracking-display-loose: 0.04em; /* spread look — marquee banners only */
|
||||
--tracking-tight: -0.005em;
|
||||
--tracking-label: 0.18em; /* mono all-caps labels */
|
||||
|
||||
|
||||
@@ -0,0 +1,386 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-theme="garden">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Hollowback Apiary — small-batch honey from one Vermont ridge</title>
|
||||
<meta name="description" content="Nine hives on a single ridge in northern Vermont. Unfiltered, single-season honey, hand-poured in lots of a few hundred jars. Reserve from this season's lot.">
|
||||
|
||||
<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=Young+Serif&family=Hanken+Grotesk:wght@400;500;600;700&family=Geist+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
|
||||
<link rel="stylesheet" href="./tokens.css">
|
||||
<link rel="stylesheet" href="./styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<a class="skip-link" href="#lot">Skip to this season’s lot</a>
|
||||
|
||||
<!-- ── N6 · Newspaper masthead ─────────────────────────────── -->
|
||||
<header class="masthead" role="banner">
|
||||
<div class="masthead__inner">
|
||||
<p class="masthead__dateline">Field Almanac · Northern Vermont · Season 2026</p>
|
||||
<a class="masthead__wordmark" href="#top">Hollowback Apiary</a>
|
||||
<nav class="masthead__nav" aria-label="Primary">
|
||||
<a href="#ridge">The ridge</a>
|
||||
<a href="#lot">This season’s lot</a>
|
||||
<a href="#poured">How it’s poured</a>
|
||||
<a class="masthead__cta" href="#standing">Reserve a jar</a>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main id="top">
|
||||
|
||||
<!-- ── Hero · typographic + hand-built honeycomb/jar craft ── -->
|
||||
<section class="hero" aria-labelledby="hero-title">
|
||||
<div class="hero__copy">
|
||||
<p class="hero__eyebrow reveal" style="--i:0">Nine hives. One ridge. No blending.</p>
|
||||
<h1 class="hero__title reveal" id="hero-title" style="--i:1">
|
||||
Honey that tastes like one hillside.
|
||||
</h1>
|
||||
<p class="hero__lede reveal" style="--i:2">
|
||||
We keep bees on a single south-facing ridge above the Lamoille valley and
|
||||
pour what they make, one season at a time — unfiltered, unblended, never
|
||||
warmed past the temperature of the hive.
|
||||
</p>
|
||||
<div class="hero__actions reveal" style="--i:3">
|
||||
<a class="btn btn--solid" href="#lot">See this season’s lot</a>
|
||||
<a class="btn btn--ghost" href="#ridge">Read about the ridge</a>
|
||||
</div>
|
||||
<p class="hero__note reveal" style="--i:4">
|
||||
<span class="hero__note-mark" aria-hidden="true"></span>
|
||||
Lot No. 14 · poured 4–5 September · 312 jars
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Tier-A CSS honeycomb wash + Tier-B hand-built SVG jar.
|
||||
Decorative, but anchored: it is the jar the page is selling. -->
|
||||
<figure class="hero__craft reveal" style="--i:3" aria-label="An illustration of a glass jar of amber honey resting on a field of honeycomb">
|
||||
<div class="comb" aria-hidden="true">
|
||||
<span style="--c:0"></span><span style="--c:1"></span><span style="--c:2"></span>
|
||||
<span style="--c:3"></span><span style="--c:4"></span><span style="--c:5"></span>
|
||||
<span style="--c:6"></span><span style="--c:7"></span><span style="--c:8"></span>
|
||||
<span style="--c:9"></span><span style="--c:10"></span><span style="--c:11"></span>
|
||||
</div>
|
||||
<svg class="jar" viewBox="0 0 200 240" aria-hidden="true" focusable="false">
|
||||
<!-- jar glass -->
|
||||
<path class="jar__glass" d="M44 78 q0 -14 14 -14 h84 q14 0 14 14 v126 q0 18 -18 18 H62 q-18 0 -18 -18 Z"/>
|
||||
<!-- honey fill, clipped to the glass -->
|
||||
<clipPath id="jarClip">
|
||||
<path d="M44 78 q0 -14 14 -14 h84 q14 0 14 14 v126 q0 18 -18 18 H62 q-18 0 -18 -18 Z"/>
|
||||
</clipPath>
|
||||
<g clip-path="url(#jarClip)">
|
||||
<rect class="jar__honey" x="40" y="96" width="120" height="150"/>
|
||||
<path class="jar__meniscus" d="M40 100 q40 -10 60 -2 q30 5 60 -4 v160 H40 Z"/>
|
||||
</g>
|
||||
<!-- lid + collar -->
|
||||
<rect class="jar__lid" x="56" y="40" width="88" height="22" rx="5"/>
|
||||
<rect class="jar__collar" x="62" y="58" width="76" height="10" rx="3"/>
|
||||
<!-- paper label -->
|
||||
<rect class="jar__label" x="64" y="120" width="72" height="64" rx="3"/>
|
||||
<line class="jar__rule" x1="74" y1="140" x2="126" y2="140"/>
|
||||
<line class="jar__rule jar__rule--short" x1="74" y1="152" x2="112" y2="152"/>
|
||||
<line class="jar__rule jar__rule--short" x1="74" y1="164" x2="118" y2="164"/>
|
||||
<!-- wooden dipper leaning in the jar -->
|
||||
<g class="jar__dipper">
|
||||
<line class="jar__dipper-stem" x1="150" y1="30" x2="120" y2="150"/>
|
||||
<ellipse class="jar__dipper-head" cx="118" cy="156" rx="9" ry="13"/>
|
||||
</g>
|
||||
</svg>
|
||||
</figure>
|
||||
</section>
|
||||
|
||||
<!-- ── The ridge · provenance prose (Long-Document beat) ──── -->
|
||||
<section class="ridge" id="ridge" aria-labelledby="ridge-title">
|
||||
<div class="ridge__head">
|
||||
<h2 class="section__title" id="ridge-title">
|
||||
<svg class="sprig" viewBox="0 0 40 80" aria-hidden="true">
|
||||
<path class="sprig__stem" d="M20 76 Q18 56 21 36 Q22 22 20 8"/>
|
||||
<g transform="translate(8 38) rotate(-25)">
|
||||
<ellipse class="sprig__leaf" cx="0" cy="0" rx="6" ry="11"/>
|
||||
<path class="sprig__vein" d="M0 -10 Q1 0 0 10"/>
|
||||
</g>
|
||||
<g transform="translate(28 52) rotate(30)">
|
||||
<ellipse class="sprig__leaf" cx="0" cy="0" rx="6" ry="11"/>
|
||||
<path class="sprig__vein" d="M0 -10 Q-1 0 0 10"/>
|
||||
</g>
|
||||
<path class="sprig__stem" d="M20 22 Q16 19 13 22"/>
|
||||
</svg>
|
||||
A ridge, and what grows on it.
|
||||
</h2>
|
||||
<p class="ridge__standfirst">
|
||||
Everything in a jar of Hollowback honey was within a bee’s morning flight of
|
||||
the same nine hives. That is the whole idea, and it is also the whole limit.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="ridge__prose">
|
||||
<p>
|
||||
The hives sit at the top of an old sheep pasture that runs down toward the
|
||||
Lamoille. From May the bees work the wild raspberry and the clover that took
|
||||
the field back; by July they are up in the basswood; in the cool weeks before
|
||||
frost they bring in goldenrod and aster, dark and a little wild on the tongue.
|
||||
</p>
|
||||
<p>
|
||||
We do not move the hives to follow a crop, and we do not buy honey from anyone
|
||||
else to stretch a season. So a poor June is a smaller June, and the
|
||||
<a href="#lot">summer lot</a> says so. We would rather sell less and have it be
|
||||
honestly from here.
|
||||
</p>
|
||||
<p>
|
||||
The honey is spun cold, strained once through cloth to take out the wax, and
|
||||
poured by hand the same week. Nothing is heated to make it pour faster or
|
||||
filtered bright to make it sit longer on a shelf. It will cloud and set in the
|
||||
cold — that is honey doing what raw honey does. Warm the jar in a bowl of
|
||||
tap-hot water and it comes back clear.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<dl class="ridge__facts" aria-label="The apiary, in brief">
|
||||
<div class="fact">
|
||||
<dt>Hives</dt>
|
||||
<dd>9</dd>
|
||||
<p>All on one ridge, all overwintered in place.</p>
|
||||
</div>
|
||||
<div class="fact">
|
||||
<dt>Elevation</dt>
|
||||
<dd>1,180 ft</dd>
|
||||
<p>South-facing, above the valley frost line.</p>
|
||||
</div>
|
||||
<div class="fact">
|
||||
<dt>Forage</dt>
|
||||
<dd>Wild</dd>
|
||||
<p>Raspberry, clover, basswood, goldenrod, aster.</p>
|
||||
</div>
|
||||
<div class="fact">
|
||||
<dt>Pour</dt>
|
||||
<dd>By hand</dd>
|
||||
<p>Cold-spun, strained once, jarred the same week.</p>
|
||||
</div>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<!-- ── This season's lot · Catalogue (F6 product-card grid) ── -->
|
||||
<section class="lot" id="lot" aria-labelledby="lot-title">
|
||||
<div class="lot__head">
|
||||
<h2 class="section__title" id="lot-title">This season’s lot</h2>
|
||||
<p class="lot__intro">
|
||||
Four pours from the 2026 season, each from one stretch of the bloom. When a
|
||||
pour is gone it is gone until the same flowers come round again next year.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ol class="lot__grid" role="list">
|
||||
<li class="card reveal" style="--i:0">
|
||||
<figure class="card__specimen" aria-hidden="true">
|
||||
<svg class="vial" viewBox="0 0 120 150" preserveAspectRatio="xMidYMax meet" aria-hidden="true" focusable="false">
|
||||
<clipPath id="vialA"><path d="M40 28 h40 v18 l16 78 a8 8 0 0 1 -8 9 H32 a8 8 0 0 1 -8 -9 l16 -78 Z"/></clipPath>
|
||||
<g clip-path="url(#vialA)"><rect class="vial__fill vial__fill--spring" x="20" y="58" width="80" height="80"/></g>
|
||||
<path class="vial__glass" d="M40 28 h40 v18 l16 78 a8 8 0 0 1 -8 9 H32 a8 8 0 0 1 -8 -9 l16 -78 Z"/>
|
||||
<rect class="vial__cork" x="42" y="18" width="36" height="14" rx="3"/>
|
||||
</svg>
|
||||
</figure>
|
||||
<div class="card__body">
|
||||
<p class="card__tag">Lot 14-A · late May</p>
|
||||
<h3 class="card__name">Raspberry & Clover</h3>
|
||||
<p class="card__notes">
|
||||
The first and palest pour. Light, almost floral, the taste of the field
|
||||
coming back after the sheep. Sets soft and white in the cold.
|
||||
</p>
|
||||
<div class="card__foot">
|
||||
<p class="card__price"><span class="card__amount">$16</span> <span class="card__unit">/ 12 oz</span></p>
|
||||
<a class="card__link" href="#standing">Reserve<span aria-hidden="true"> →</span></a>
|
||||
</div>
|
||||
<p class="card__stock" data-stock="low">14 jars left</p>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="card reveal" style="--i:1">
|
||||
<figure class="card__specimen" aria-hidden="true">
|
||||
<svg class="vial" viewBox="0 0 120 150" preserveAspectRatio="xMidYMax meet" aria-hidden="true" focusable="false">
|
||||
<clipPath id="vialB"><path d="M40 28 h40 v18 l16 78 a8 8 0 0 1 -8 9 H32 a8 8 0 0 1 -8 -9 l16 -78 Z"/></clipPath>
|
||||
<g clip-path="url(#vialB)"><rect class="vial__fill vial__fill--basswood" x="20" y="48" width="80" height="90"/></g>
|
||||
<path class="vial__glass" d="M40 28 h40 v18 l16 78 a8 8 0 0 1 -8 9 H32 a8 8 0 0 1 -8 -9 l16 -78 Z"/>
|
||||
<rect class="vial__cork" x="42" y="18" width="36" height="14" rx="3"/>
|
||||
</svg>
|
||||
</figure>
|
||||
<div class="card__body">
|
||||
<p class="card__tag">Lot 14-B · early July</p>
|
||||
<h3 class="card__name">Basswood</h3>
|
||||
<p class="card__notes">
|
||||
Our shortest bloom and the one we wait for. Cool and green-edged, a little
|
||||
minty, with the smell of the linden flower still in it. Stays liquid longest.
|
||||
</p>
|
||||
<div class="card__foot">
|
||||
<p class="card__price"><span class="card__amount">$19</span> <span class="card__unit">/ 12 oz</span></p>
|
||||
<a class="card__link" href="#standing">Reserve<span aria-hidden="true"> →</span></a>
|
||||
</div>
|
||||
<p class="card__stock" data-stock="out">Sold out for 2026</p>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="card reveal" style="--i:2">
|
||||
<figure class="card__specimen" aria-hidden="true">
|
||||
<svg class="vial" viewBox="0 0 120 150" preserveAspectRatio="xMidYMax meet" aria-hidden="true" focusable="false">
|
||||
<clipPath id="vialC"><path d="M40 28 h40 v18 l16 78 a8 8 0 0 1 -8 9 H32 a8 8 0 0 1 -8 -9 l16 -78 Z"/></clipPath>
|
||||
<g clip-path="url(#vialC)"><rect class="vial__fill vial__fill--summer" x="20" y="52" width="80" height="86"/></g>
|
||||
<path class="vial__glass" d="M40 28 h40 v18 l16 78 a8 8 0 0 1 -8 9 H32 a8 8 0 0 1 -8 -9 l16 -78 Z"/>
|
||||
<rect class="vial__cork" x="42" y="18" width="36" height="14" rx="3"/>
|
||||
</svg>
|
||||
</figure>
|
||||
<div class="card__body">
|
||||
<p class="card__tag">Lot 14-C · high summer</p>
|
||||
<h3 class="card__name">Ridge Summer</h3>
|
||||
<p class="card__notes">
|
||||
Everything blooming at once — the household jar. Round and even, equal
|
||||
parts clover and wildflower. The one we put on toast and never label fussily.
|
||||
</p>
|
||||
<div class="card__foot">
|
||||
<p class="card__price"><span class="card__amount">$15</span> <span class="card__unit">/ 12 oz</span></p>
|
||||
<a class="card__link" href="#standing">Reserve<span aria-hidden="true"> →</span></a>
|
||||
</div>
|
||||
<p class="card__stock" data-stock="in">In the pantry now</p>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="card reveal" style="--i:3">
|
||||
<figure class="card__specimen" aria-hidden="true">
|
||||
<svg class="vial" viewBox="0 0 120 150" preserveAspectRatio="xMidYMax meet" aria-hidden="true" focusable="false">
|
||||
<clipPath id="vialD"><path d="M40 28 h40 v18 l16 78 a8 8 0 0 1 -8 9 H32 a8 8 0 0 1 -8 -9 l16 -78 Z"/></clipPath>
|
||||
<g clip-path="url(#vialD)"><rect class="vial__fill vial__fill--autumn" x="20" y="46" width="80" height="92"/></g>
|
||||
<path class="vial__glass" d="M40 28 h40 v18 l16 78 a8 8 0 0 1 -8 9 H32 a8 8 0 0 1 -8 -9 l16 -78 Z"/>
|
||||
<rect class="vial__cork" x="42" y="18" width="36" height="14" rx="3"/>
|
||||
</svg>
|
||||
</figure>
|
||||
<div class="card__body">
|
||||
<p class="card__tag">Lot 14-D · before frost</p>
|
||||
<h3 class="card__name">Goldenrod & Aster</h3>
|
||||
<p class="card__notes">
|
||||
The last and darkest pour, brought in cool. Malty, almost smoky, with the
|
||||
bite the goldenrod leaves behind. For cheese, dark bread, strong tea.
|
||||
</p>
|
||||
<div class="card__foot">
|
||||
<p class="card__price"><span class="card__amount">$17</span> <span class="card__unit">/ 12 oz</span></p>
|
||||
<a class="card__link" href="#standing">Reserve<span aria-hidden="true"> →</span></a>
|
||||
</div>
|
||||
<p class="card__stock" data-stock="in">In the pantry now</p>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<p class="lot__foot">
|
||||
Every jar carries its lot letter and pour date. Shipped in straw, padded by hand;
|
||||
flat-rate within New England, at cost beyond it.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- ── How it's poured · short process (stacked, no eyebrows) ─ -->
|
||||
<section class="poured" id="poured" aria-labelledby="poured-title">
|
||||
<div class="poured__head">
|
||||
<h2 class="section__title" id="poured-title">How a jar is filled</h2>
|
||||
<p class="poured__intro">
|
||||
Four steps, and not one of them is in a hurry. The whole point of small-batch is
|
||||
that no part of it scales.
|
||||
</p>
|
||||
</div>
|
||||
<div class="poured__steps">
|
||||
<article class="step">
|
||||
<h3 class="step__name">Spun cold</h3>
|
||||
<p>
|
||||
Frames come off the ridge and into the shed the same morning. We spin them at
|
||||
cellar temperature, never warm. Heat makes honey pour fast and lose the smell
|
||||
of its own flowers; we trade the speed for the smell.
|
||||
</p>
|
||||
</article>
|
||||
<article class="step">
|
||||
<h3 class="step__name">Strained once</h3>
|
||||
<p>
|
||||
One pass through a square of cloth takes out the wax and a stray bit of comb.
|
||||
That is all. We don’t fine it bright or push it through paper —
|
||||
the pollen and the cloud are the honey, not a flaw in it.
|
||||
</p>
|
||||
</article>
|
||||
<article class="step">
|
||||
<h3 class="step__name">Settled a week</h3>
|
||||
<p>
|
||||
The strained honey rests in a covered tank for several days so the last air
|
||||
and froth rise on their own. No pump, no pressure, nothing forced. It clears
|
||||
the way a pond clears after rain.
|
||||
</p>
|
||||
</article>
|
||||
<article class="step">
|
||||
<h3 class="step__name">Poured by hand</h3>
|
||||
<p>
|
||||
We fill, weigh, and cap each jar one at a time, write the lot and the date on
|
||||
the label, and pack it in straw. A good day is a few hundred jars — and
|
||||
then that pour is closed.
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ── The standing jar · CTA (C2 inline form-as-CTA) ──────── -->
|
||||
<section class="standing" id="standing" aria-labelledby="standing-title">
|
||||
<div class="standing__inner">
|
||||
<div class="standing__copy">
|
||||
<h2 class="standing__title" id="standing-title">Keep a jar standing.</h2>
|
||||
<p class="standing__lede">
|
||||
A standing jar is the simplest way we know to sell honey: tell us your address
|
||||
once, and one jar of whatever we’re pouring arrives at the turn of each
|
||||
season — four jars a year, billed the week each ships. Pause or stop with
|
||||
one reply. No tiers, no app, no club.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form class="standing__form" id="standing-form" novalidate>
|
||||
<div class="field">
|
||||
<label class="field__label" for="email">Email for the harvest note</label>
|
||||
<div class="field__row">
|
||||
<input
|
||||
class="field__input"
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
inputmode="email"
|
||||
autocomplete="email"
|
||||
placeholder="you@example.com"
|
||||
aria-describedby="email-help"
|
||||
required>
|
||||
<button class="btn btn--solid" type="submit">Start a standing jar</button>
|
||||
</div>
|
||||
<p class="field__help" id="email-help" data-state="idle" role="status" aria-live="polite">
|
||||
We write once a season, the week a pour ships. Nothing else.
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<!-- ── Ft6 · Letter close ─────────────────────────────────────── -->
|
||||
<footer class="colophon" role="contentinfo">
|
||||
<div class="colophon__inner">
|
||||
<p class="colophon__close">
|
||||
That’s the whole farm — nine hives, one ridge, and however much the
|
||||
summer felt like giving. Thank you for buying honey that has to be honest about
|
||||
the weather.
|
||||
</p>
|
||||
<p class="colophon__sign">Yours, from the ridge,<br><span class="colophon__name">Tess & Roland Marsh</span></p>
|
||||
<p class="colophon__ps">
|
||||
P.S. — Beekeepers and the curious are welcome up in the off weeks. Write first;
|
||||
we’ll put the kettle on.
|
||||
</p>
|
||||
|
||||
<div class="colophon__meta">
|
||||
<span class="colophon__wordmark">Hollowback Apiary</span>
|
||||
<span>Hollowback Ridge, Johnson, Vermont</span>
|
||||
<span>Season 2026 · Lot No. 14</span>
|
||||
<a href="#top">Back to top</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="./script.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,76 @@
|
||||
/* Hollowback Apiary — progressive enhancement only.
|
||||
* Nothing here is required to read the page; it adds the card reveal
|
||||
* and the standing-jar form's states. No external requests. */
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
||||
|
||||
/* ── Card reveal: reveal-once on enter, never re-fires (motion.md) ── */
|
||||
var cards = document.querySelectorAll(".lot__grid .card");
|
||||
if (reduceMotion || !("IntersectionObserver" in window)) {
|
||||
cards.forEach(function (c) { c.classList.add("in-view"); });
|
||||
} else {
|
||||
var io = new IntersectionObserver(function (entries, obs) {
|
||||
entries.forEach(function (entry) {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.classList.add("in-view");
|
||||
obs.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
}, { threshold: 0.18, rootMargin: "0px 0px -8% 0px" });
|
||||
cards.forEach(function (c) { io.observe(c); });
|
||||
}
|
||||
|
||||
/* ── Standing-jar form: idle → loading → success | error ──────────── */
|
||||
var form = document.getElementById("standing-form");
|
||||
if (!form) return;
|
||||
|
||||
var input = document.getElementById("email");
|
||||
var help = document.getElementById("email-help");
|
||||
var button = form.querySelector("button[type=submit]");
|
||||
var idleText = help ? help.textContent : "";
|
||||
var EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
|
||||
function setHelp(state, message) {
|
||||
if (!help) return;
|
||||
help.dataset.state = state;
|
||||
help.textContent = message;
|
||||
}
|
||||
|
||||
// Clearing an error the moment the field is corrected.
|
||||
input.addEventListener("input", function () {
|
||||
if (input.getAttribute("aria-invalid") === "true" && EMAIL.test(input.value.trim())) {
|
||||
input.removeAttribute("aria-invalid");
|
||||
setHelp("idle", idleText);
|
||||
}
|
||||
});
|
||||
|
||||
form.addEventListener("submit", function (e) {
|
||||
e.preventDefault();
|
||||
var value = input.value.trim();
|
||||
|
||||
// Error: name what's wrong and how to fix it (copy.md error structure).
|
||||
if (!EMAIL.test(value)) {
|
||||
input.setAttribute("aria-invalid", "true");
|
||||
setHelp("error", "That address is missing an @ or a domain. Try the format you@example.com.");
|
||||
input.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
// Loading: the effect (an email on file) isn't visible, so we narrate it.
|
||||
input.removeAttribute("aria-invalid");
|
||||
button.disabled = true;
|
||||
button.textContent = "Adding…";
|
||||
setHelp("idle", "Adding your jar to the season list…");
|
||||
|
||||
// No backend in this demo; simulate the round-trip, then confirm the
|
||||
// invisible async result (this is the legitimate toast/confirm case).
|
||||
window.setTimeout(function () {
|
||||
button.disabled = false;
|
||||
button.textContent = "Start a standing jar";
|
||||
form.reset();
|
||||
setHelp("success", "You're on the list. We'll write the week the next pour ships — no jar bills until then.");
|
||||
}, reduceMotion ? 200 : 850);
|
||||
});
|
||||
})();
|
||||
@@ -0,0 +1,859 @@
|
||||
/* Hallmark · pre-emit critique: P5 H5 E5 S5 R5 V5
|
||||
* genre: editorial · macrostructure: Catalogue · theme: Garden
|
||||
* H9 hero knobs: build=tier-A CSS comb + tier-B SVG jar, animation=loop(jar-fill on hover) + reveal, scale=dominant
|
||||
* enrichment: E5 Custom Illustration · craft: tier-A honeycomb wash + tier-B hand-built SVG jars (no real photos)
|
||||
* F6 product-card knobs: ratio=portrait specimen, density=4-up, micro-action=Reserve →
|
||||
* nav: N6 Newspaper masthead (issue=below wordmark, rule=double) · footer: Ft6 Letter close (signoff=roman, postscript=yes)
|
||||
* motion: reveal (stagger by --i, ≤500ms) · jar-fill rise on hover · all transform/opacity, reduced-motion → opacity-only
|
||||
* accent: leaf-green ~3% + clay/honey on craft only · tone: editorial / craft / honest
|
||||
* contrast: pass (40–41) · honest: pass (46) · chrome: pass (47) · tokens: pass (48) · responsive: pass (49)
|
||||
* icons: pass (30, none) · mobile: pass (34, 49, 50–57) · slop: pass (42–45) · studied: no
|
||||
* diversification: isolated build — no prior .hallmark/log.json or CSS stamp in scope
|
||||
*/
|
||||
|
||||
/* ── Reset / base ──────────────────────────────────────────────── */
|
||||
*, *::before, *::after { box-sizing: border-box; }
|
||||
|
||||
html {
|
||||
overflow-x: clip; /* gate 34 — no horizontal scroll */
|
||||
scroll-behavior: smooth;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
body {
|
||||
overflow-x: clip; /* gate 34 fallback for older Safari */
|
||||
margin: 0;
|
||||
background: var(--color-paper);
|
||||
color: var(--color-ink-2);
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--text-md);
|
||||
font-weight: 400;
|
||||
line-height: var(--lh-relaxed);
|
||||
font-variant-numeric: oldstyle-nums;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
html { scroll-behavior: auto; }
|
||||
}
|
||||
|
||||
h1, h2, h3, p, dl, dd, figure, ol, ul { margin: 0; }
|
||||
ol, ul { padding: 0; list-style: none; }
|
||||
img, svg { display: block; max-width: 100%; }
|
||||
|
||||
a { color: inherit; text-decoration: none; }
|
||||
|
||||
:focus-visible {
|
||||
outline: 2px solid var(--color-focus);
|
||||
outline-offset: 2px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
::selection { background: var(--tint-comb); color: var(--color-ink); }
|
||||
|
||||
.skip-link {
|
||||
position: absolute;
|
||||
left: var(--space-md);
|
||||
top: var(--space-md);
|
||||
z-index: var(--z-skip);
|
||||
background: var(--color-ink);
|
||||
color: var(--color-paper);
|
||||
padding: var(--space-xs) var(--space-md);
|
||||
border-radius: var(--radius-pill);
|
||||
font: 500 var(--text-sm)/1 var(--font-label);
|
||||
letter-spacing: 0.02em;
|
||||
transform: translateY(-200%); /* transform, not top — gate 14 */
|
||||
transition: transform var(--dur-short) var(--ease-out);
|
||||
}
|
||||
.skip-link:focus-visible { transform: translateY(0); }
|
||||
|
||||
/* ── Shared layout primitives ──────────────────────────────────── */
|
||||
.section__title {
|
||||
font-family: var(--font-display);
|
||||
font-weight: var(--display-weight);
|
||||
font-style: var(--display-style); /* roman — gate 38a */
|
||||
font-size: var(--text-3xl);
|
||||
line-height: var(--lh-tight);
|
||||
letter-spacing: var(--tracking-tight);
|
||||
color: var(--color-ink);
|
||||
overflow-wrap: anywhere; /* gate 51 */
|
||||
min-width: 0;
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
/* ── Buttons ───────────────────────────────────────────────────── */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center; /* gate 36 */
|
||||
justify-content: center;
|
||||
gap: var(--space-xs);
|
||||
min-height: 44px; /* hit target */
|
||||
padding: 0 var(--space-lg);
|
||||
font: 600 var(--text-base)/1 var(--font-body); /* line-height 1 — gate 36 */
|
||||
letter-spacing: 0.005em;
|
||||
white-space: nowrap; /* gate 49 — never wraps */
|
||||
border-radius: var(--radius-pill);
|
||||
border: var(--rule-fine) solid transparent;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background-color var(--dur-short) var(--ease-out),
|
||||
border-color var(--dur-short) var(--ease-out),
|
||||
color var(--dur-short) var(--ease-out),
|
||||
transform var(--dur-micro) var(--ease-out);
|
||||
}
|
||||
.btn--solid {
|
||||
background: var(--color-accent);
|
||||
color: var(--color-accent-ink);
|
||||
}
|
||||
.btn--solid:hover { background: var(--color-accent-press); }
|
||||
.btn--solid:active { transform: translateY(1px); }
|
||||
|
||||
.btn--ghost {
|
||||
background: transparent;
|
||||
color: var(--color-ink);
|
||||
border-color: var(--color-rule-2);
|
||||
}
|
||||
.btn--ghost:hover {
|
||||
border-color: var(--color-accent);
|
||||
color: var(--color-accent);
|
||||
background: var(--tint-moss);
|
||||
}
|
||||
.btn--ghost:active { transform: translateY(1px); }
|
||||
|
||||
.btn:disabled,
|
||||
.btn[aria-disabled="true"] {
|
||||
opacity: 0.55;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
N6 · NEWSPAPER MASTHEAD
|
||||
════════════════════════════════════════════════════════════════ */
|
||||
.masthead {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: var(--z-sticky-nav); /* gate 56 — nav out-paints */
|
||||
background: color-mix(in oklch, var(--color-paper) 88%, transparent);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
border-bottom: var(--rule-fine) solid var(--color-rule-2);
|
||||
box-shadow: 0 1px 0 var(--color-rule); /* the second of the "double rule" */
|
||||
}
|
||||
.masthead__inner {
|
||||
max-width: var(--page-max);
|
||||
margin-inline: auto;
|
||||
padding: var(--space-sm) var(--page-gutter);
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
grid-template-areas:
|
||||
"dateline dateline"
|
||||
"wordmark nav";
|
||||
align-items: center;
|
||||
row-gap: var(--space-2xs);
|
||||
column-gap: var(--space-md);
|
||||
}
|
||||
.masthead__dateline {
|
||||
grid-area: dateline;
|
||||
font: 500 var(--text-xs)/1 var(--font-label);
|
||||
letter-spacing: var(--tracking-label);
|
||||
text-transform: uppercase;
|
||||
color: var(--color-muted);
|
||||
}
|
||||
.masthead__wordmark {
|
||||
grid-area: wordmark;
|
||||
font-family: var(--font-display);
|
||||
font-size: clamp(1.4rem, 1rem + 1.4vw, 1.9rem);
|
||||
line-height: 1;
|
||||
color: var(--color-ink);
|
||||
letter-spacing: var(--tracking-tight);
|
||||
}
|
||||
.masthead__nav {
|
||||
grid-area: nav;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: clamp(var(--space-md), 2vw, var(--space-xl));
|
||||
transform: translateY(-3px); /* optical: centre links to the serif wordmark's cap height */
|
||||
}
|
||||
.masthead__nav > a {
|
||||
font-size: var(--text-sm);
|
||||
font-weight: 500;
|
||||
white-space: nowrap; /* gate 49 */
|
||||
color: var(--color-ink-2);
|
||||
padding-block: var(--space-2xs);
|
||||
position: relative;
|
||||
transition: color var(--dur-short) var(--ease-out);
|
||||
}
|
||||
.masthead__nav > a:not(.masthead__cta)::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0; right: 0;
|
||||
bottom: -1px;
|
||||
height: 1.5px;
|
||||
background: var(--color-accent);
|
||||
transform: scaleX(0); /* transform, not inset — gate 14 */
|
||||
transform-origin: left;
|
||||
transition: transform var(--dur-short) var(--ease-out);
|
||||
}
|
||||
.masthead__nav > a:not(.masthead__cta):hover { color: var(--color-ink); }
|
||||
.masthead__nav > a:not(.masthead__cta):hover::after { transform: scaleX(1); }
|
||||
|
||||
.masthead__cta {
|
||||
color: var(--color-accent);
|
||||
font-weight: 600;
|
||||
}
|
||||
.masthead__cta:hover { color: var(--color-ink); }
|
||||
|
||||
/* the masthead links that aren't essential drop on phones */
|
||||
@media (max-width: 46rem) {
|
||||
.masthead__nav > a:not(.masthead__cta) { display: none; }
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
HERO — typographic, with hand-built craft panel
|
||||
════════════════════════════════════════════════════════════════ */
|
||||
.hero {
|
||||
max-width: var(--page-max);
|
||||
margin-inline: auto;
|
||||
padding: clamp(2.5rem, 5vw, 4rem) var(--page-gutter) clamp(4rem, 8vw, 7rem); /* asym: end ≥ 1.3× start — gate 44 */
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr); /* gate 50 */
|
||||
gap: clamp(var(--space-xl), 5vw, var(--space-3xl));
|
||||
align-items: center;
|
||||
}
|
||||
.hero__copy { max-width: 36rem; }
|
||||
|
||||
.hero__eyebrow {
|
||||
font: 500 var(--text-sm)/1.3 var(--font-label);
|
||||
letter-spacing: var(--tracking-label);
|
||||
text-transform: uppercase;
|
||||
color: var(--color-accent);
|
||||
margin-bottom: var(--space-md);
|
||||
}
|
||||
.hero__title {
|
||||
font-family: var(--font-display);
|
||||
font-weight: var(--display-weight);
|
||||
font-style: var(--display-style);
|
||||
font-size: var(--text-display);
|
||||
line-height: var(--lh-tight);
|
||||
letter-spacing: var(--tracking-display);
|
||||
color: var(--color-ink);
|
||||
overflow-wrap: anywhere; /* gate 51 */
|
||||
min-width: 0;
|
||||
margin-bottom: var(--space-lg);
|
||||
}
|
||||
.hero__lede {
|
||||
font-size: var(--text-lg);
|
||||
line-height: var(--lh-normal);
|
||||
color: var(--color-ink-2);
|
||||
max-width: 46ch;
|
||||
margin-bottom: var(--space-xl);
|
||||
}
|
||||
.hero__actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center; /* gate 36 */
|
||||
gap: var(--space-sm);
|
||||
margin-bottom: var(--space-xl);
|
||||
}
|
||||
.hero__note {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-xs);
|
||||
font: 500 var(--text-sm)/1.2 var(--font-label);
|
||||
letter-spacing: 0.04em;
|
||||
color: var(--color-muted);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.hero__note-mark {
|
||||
width: 9px; height: 9px;
|
||||
flex: none;
|
||||
background: var(--color-accent-2);
|
||||
border-radius: 2px;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
/* ── Hero craft: honeycomb wash + hand-built jar ───────────────── */
|
||||
.hero__craft {
|
||||
position: relative;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
min-height: 22rem;
|
||||
isolation: isolate;
|
||||
}
|
||||
.comb {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: -1;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: var(--space-xs);
|
||||
padding: var(--space-md);
|
||||
opacity: 0.9;
|
||||
-webkit-mask-image: radial-gradient(circle at 55% 45%, var(--mask-matte) 28%, transparent 74%);
|
||||
mask-image: radial-gradient(circle at 55% 45%, var(--mask-matte) 28%, transparent 74%);
|
||||
}
|
||||
.comb > span {
|
||||
aspect-ratio: 1 / 1.1;
|
||||
clip-path: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
|
||||
background: var(--tint-comb);
|
||||
border: 1px solid var(--comb-edge);
|
||||
transform: translateY(6px);
|
||||
opacity: 0;
|
||||
animation: comb-in var(--dur-long) var(--ease-out) forwards;
|
||||
animation-delay: calc(220ms + var(--c) * 45ms);
|
||||
}
|
||||
/* offset alternate columns into a real honeycomb stagger */
|
||||
.comb > span:nth-child(4n + 2),
|
||||
.comb > span:nth-child(4n) { transform: translateY(calc(50% + 6px)); }
|
||||
@keyframes comb-in {
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
.comb > span:nth-child(4n + 2),
|
||||
.comb > span:nth-child(4n) {
|
||||
animation-name: comb-in-offset;
|
||||
}
|
||||
@keyframes comb-in-offset {
|
||||
to { opacity: 1; transform: translateY(50%); }
|
||||
}
|
||||
|
||||
.jar { width: clamp(11rem, 30vw, 15rem); height: auto; filter: drop-shadow(var(--shadow-jar)); }
|
||||
.jar__glass {
|
||||
fill: var(--glass);
|
||||
stroke: var(--color-rule-2);
|
||||
stroke-width: 2;
|
||||
}
|
||||
.jar__honey { fill: var(--color-honey-deep); }
|
||||
.jar__meniscus { fill: var(--color-honey); }
|
||||
.jar__lid { fill: var(--color-accent-2); }
|
||||
.jar__collar { fill: var(--collar); }
|
||||
.jar__label {
|
||||
fill: var(--color-paper);
|
||||
stroke: var(--color-rule);
|
||||
stroke-width: 1;
|
||||
}
|
||||
.jar__rule { stroke: var(--color-muted); stroke-width: 2; stroke-linecap: round; }
|
||||
.jar__rule--short { stroke: var(--color-rule-2); }
|
||||
.jar__dipper-stem { stroke: var(--wood); stroke-width: 5; stroke-linecap: round; }
|
||||
.jar__dipper-head { fill: var(--wood); }
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
THE RIDGE — provenance prose
|
||||
════════════════════════════════════════════════════════════════ */
|
||||
.ridge {
|
||||
max-width: var(--page-max);
|
||||
margin-inline: auto;
|
||||
padding: var(--section-gap) var(--page-gutter) clamp(3rem, 6vw, 5rem);
|
||||
border-top: var(--rule-hair) solid var(--color-rule);
|
||||
}
|
||||
.ridge__head {
|
||||
display: flex; /* gate 54 — single column */
|
||||
flex-direction: column;
|
||||
gap: var(--section-head-gap);
|
||||
max-width: 40ch;
|
||||
margin-bottom: var(--space-2xl);
|
||||
}
|
||||
.section__title .sprig {
|
||||
width: 26px; height: 52px;
|
||||
display: inline-block;
|
||||
vertical-align: -0.55em;
|
||||
margin-inline-end: 0.25em;
|
||||
color: var(--color-accent);
|
||||
}
|
||||
.sprig__stem { fill: none; stroke: currentColor; stroke-width: 1.4; stroke-linecap: round; }
|
||||
.sprig__leaf { fill: var(--tint-moss); stroke: currentColor; stroke-width: 1.4; }
|
||||
.sprig__vein { fill: none; stroke: currentColor; stroke-width: 0.9; opacity: 0.6; stroke-linecap: round; }
|
||||
|
||||
.ridge__standfirst {
|
||||
font-size: var(--text-lg);
|
||||
line-height: var(--lh-snug);
|
||||
color: var(--color-ink);
|
||||
}
|
||||
.ridge__prose {
|
||||
max-width: var(--measure);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-lg);
|
||||
font-size: var(--text-md);
|
||||
line-height: var(--lh-relaxed);
|
||||
color: var(--color-ink-2);
|
||||
}
|
||||
.ridge__prose a {
|
||||
color: var(--color-accent);
|
||||
text-decoration: underline;
|
||||
text-decoration-color: var(--color-rule-2);
|
||||
text-decoration-thickness: 1.5px;
|
||||
text-underline-offset: 3px;
|
||||
transition: text-decoration-color var(--dur-short) var(--ease-out);
|
||||
}
|
||||
.ridge__prose a:hover { text-decoration-color: var(--color-accent); }
|
||||
|
||||
.ridge__facts {
|
||||
margin-top: var(--space-2xl);
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(min(13rem, 100%), 1fr));
|
||||
gap: var(--space-md) var(--space-xl);
|
||||
}
|
||||
.fact {
|
||||
border-top: var(--rule-fine) solid var(--color-rule-2);
|
||||
padding-top: var(--space-sm);
|
||||
}
|
||||
.fact dt {
|
||||
font: 500 var(--text-xs)/1 var(--font-label);
|
||||
letter-spacing: var(--tracking-label);
|
||||
text-transform: uppercase;
|
||||
color: var(--color-muted);
|
||||
margin-bottom: var(--space-xs);
|
||||
}
|
||||
.fact dd {
|
||||
font-family: var(--font-display);
|
||||
font-size: var(--text-2xl);
|
||||
line-height: 1;
|
||||
color: var(--color-ink);
|
||||
font-variant-numeric: tabular-nums;
|
||||
margin-bottom: var(--space-xs);
|
||||
}
|
||||
.fact p {
|
||||
font-size: var(--text-sm);
|
||||
line-height: var(--lh-normal);
|
||||
color: var(--color-ink-2);
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
THIS SEASON'S LOT — Catalogue / F6 product-card grid
|
||||
════════════════════════════════════════════════════════════════ */
|
||||
.lot {
|
||||
background: var(--tint-comb);
|
||||
border-block: var(--rule-fine) solid var(--color-rule-2);
|
||||
}
|
||||
.lot,
|
||||
.poured,
|
||||
.standing { /* full-bleed band; inner content holds the page max */ }
|
||||
.lot__head,
|
||||
.lot__grid,
|
||||
.lot__foot {
|
||||
max-width: var(--page-max);
|
||||
margin-inline: auto;
|
||||
padding-inline: var(--page-gutter);
|
||||
}
|
||||
.lot__head {
|
||||
display: flex; /* gate 54 */
|
||||
flex-direction: column;
|
||||
align-items: flex-start; /* left-flush; coheres with the grid below */
|
||||
gap: var(--section-head-gap);
|
||||
padding-top: var(--section-gap);
|
||||
margin-bottom: var(--space-2xl);
|
||||
}
|
||||
.lot__head > * { max-width: 46ch; } /* cap the text width, not the head's alignment */
|
||||
.lot__intro {
|
||||
font-size: var(--text-lg);
|
||||
line-height: var(--lh-snug);
|
||||
color: var(--color-ink-2);
|
||||
}
|
||||
.lot__grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(min(15.5rem, 100%), 1fr)); /* gate 50 safe */
|
||||
gap: var(--space-lg);
|
||||
}
|
||||
.lot__foot {
|
||||
margin-top: var(--space-2xl);
|
||||
padding-bottom: var(--section-gap);
|
||||
max-width: 56ch;
|
||||
font-size: var(--text-sm);
|
||||
line-height: var(--lh-normal);
|
||||
color: var(--color-muted);
|
||||
}
|
||||
|
||||
.card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--color-paper);
|
||||
border: var(--rule-card) solid var(--color-rule);
|
||||
border-radius: var(--radius-card);
|
||||
overflow: hidden;
|
||||
box-shadow: var(--shadow-card);
|
||||
transition:
|
||||
transform var(--dur-short) var(--ease-out),
|
||||
box-shadow var(--dur-short) var(--ease-out);
|
||||
}
|
||||
/* One signal: the card lifts (translate + its shadow deepen together as a
|
||||
single "raised" gesture). No separate colour/border shift on hover. */
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
.card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: var(--shadow-card-hover);
|
||||
}
|
||||
}
|
||||
.card__specimen {
|
||||
display: grid;
|
||||
place-items: end center;
|
||||
padding: var(--space-lg) var(--space-lg) 0;
|
||||
background:
|
||||
radial-gradient(120% 80% at 50% 120%, var(--tint-comb), transparent 70%);
|
||||
min-height: 9.5rem;
|
||||
}
|
||||
.vial { width: 4.5rem; height: auto; filter: drop-shadow(var(--shadow-vial)); }
|
||||
.vial__glass { fill: var(--glass-vial); stroke: var(--color-rule-2); stroke-width: 2; }
|
||||
.vial__cork { fill: var(--wood); }
|
||||
/* honey fills — four warm shades, palest→darkest, all token-derived */
|
||||
.vial__fill { transform: translateY(0); transition: transform var(--dur-long) var(--ease-out); }
|
||||
.vial__fill--spring { fill: var(--honey-spring); }
|
||||
.vial__fill--basswood { fill: var(--honey-bass); }
|
||||
.vial__fill--summer { fill: var(--color-honey); }
|
||||
.vial__fill--autumn { fill: var(--honey-autumn); }
|
||||
|
||||
/* jar-fill motion: the honey rises a touch when you pick up the card */
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
.card:hover .vial__fill { transform: translateY(-7px); }
|
||||
}
|
||||
|
||||
.card__body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-xs);
|
||||
padding: var(--space-lg);
|
||||
flex: 1;
|
||||
}
|
||||
.card__tag {
|
||||
font: 500 var(--text-xs)/1 var(--font-label);
|
||||
letter-spacing: var(--tracking-label);
|
||||
text-transform: uppercase;
|
||||
color: var(--color-muted);
|
||||
}
|
||||
.card__name {
|
||||
font-family: var(--font-display);
|
||||
font-weight: var(--display-weight);
|
||||
font-size: var(--text-xl);
|
||||
line-height: var(--lh-tight);
|
||||
color: var(--color-ink);
|
||||
overflow-wrap: anywhere; /* gate 51 */
|
||||
}
|
||||
.card__notes {
|
||||
font-size: var(--text-sm);
|
||||
line-height: var(--lh-normal);
|
||||
color: var(--color-ink-2);
|
||||
margin-top: var(--space-2xs);
|
||||
}
|
||||
.card__foot {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-sm);
|
||||
margin-top: auto;
|
||||
padding-top: var(--space-sm);
|
||||
}
|
||||
.card__price {
|
||||
display: inline-flex;
|
||||
align-items: baseline;
|
||||
gap: 0.3ch;
|
||||
font-variant-numeric: tabular-nums;
|
||||
color: var(--color-ink);
|
||||
}
|
||||
.card__amount {
|
||||
font-family: var(--font-display);
|
||||
font-size: var(--text-lg);
|
||||
line-height: 1;
|
||||
}
|
||||
.card__unit { font-size: var(--text-xs); color: var(--color-muted); }
|
||||
.card__link {
|
||||
font: 600 var(--text-sm)/1 var(--font-body);
|
||||
color: var(--color-accent);
|
||||
white-space: nowrap; /* gate 49 */
|
||||
transition: color var(--dur-short) var(--ease-out);
|
||||
}
|
||||
.card__link:hover { color: var(--color-ink); }
|
||||
.card__stock {
|
||||
margin-top: var(--space-sm);
|
||||
padding-top: var(--space-sm);
|
||||
border-top: var(--rule-hair) solid var(--color-rule);
|
||||
font: 500 var(--text-xs)/1 var(--font-label);
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-xs);
|
||||
color: var(--color-muted);
|
||||
}
|
||||
.card__stock::before {
|
||||
content: "";
|
||||
width: 7px; height: 7px;
|
||||
border-radius: 50%;
|
||||
flex: none;
|
||||
background: var(--color-rule-2);
|
||||
}
|
||||
.card__stock[data-stock="in"] { color: var(--color-accent); }
|
||||
.card__stock[data-stock="in"]::before { background: var(--color-accent); }
|
||||
.card__stock[data-stock="low"] { color: var(--color-accent-2); }
|
||||
.card__stock[data-stock="low"]::before { background: var(--color-accent-2); }
|
||||
.card__stock[data-stock="out"]::before { background: var(--color-muted); }
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
HOW A JAR IS FILLED — short process, stacked (no eyebrows)
|
||||
════════════════════════════════════════════════════════════════ */
|
||||
.poured__head,
|
||||
.poured__steps {
|
||||
max-width: var(--page-max);
|
||||
margin-inline: auto;
|
||||
padding-inline: var(--page-gutter);
|
||||
}
|
||||
.poured__head {
|
||||
display: flex; /* gate 54 */
|
||||
flex-direction: column;
|
||||
align-items: flex-start; /* left-flush; coheres with the steps below */
|
||||
gap: var(--section-head-gap);
|
||||
padding-top: var(--section-gap);
|
||||
margin-bottom: var(--space-2xl);
|
||||
}
|
||||
.poured__head > * { max-width: 44ch; }
|
||||
.poured__intro {
|
||||
font-size: var(--text-lg);
|
||||
line-height: var(--lh-snug);
|
||||
color: var(--color-ink-2);
|
||||
}
|
||||
.poured__steps {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr));
|
||||
gap: var(--space-xl) var(--space-2xl);
|
||||
padding-bottom: var(--section-gap);
|
||||
counter-reset: step;
|
||||
}
|
||||
.step { counter-increment: step; }
|
||||
.step__name {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: var(--space-sm);
|
||||
font-family: var(--font-display);
|
||||
font-weight: var(--display-weight);
|
||||
font-size: var(--text-xl);
|
||||
line-height: var(--lh-tight);
|
||||
color: var(--color-ink);
|
||||
margin-bottom: var(--space-sm);
|
||||
padding-bottom: var(--space-sm);
|
||||
border-bottom: var(--rule-fine) solid var(--color-rule-2);
|
||||
}
|
||||
.step__name::before {
|
||||
content: counter(step, decimal-leading-zero);
|
||||
font: 500 var(--text-sm)/1 var(--font-label);
|
||||
color: var(--color-accent);
|
||||
letter-spacing: var(--tracking-label);
|
||||
}
|
||||
.step p {
|
||||
font-size: var(--text-base);
|
||||
line-height: var(--lh-relaxed);
|
||||
color: var(--color-ink-2);
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
THE STANDING JAR — CTA (C2 inline form-as-CTA)
|
||||
════════════════════════════════════════════════════════════════ */
|
||||
.standing {
|
||||
background: var(--color-ink);
|
||||
color: var(--color-paper);
|
||||
border-top: var(--rule-fine) solid var(--color-rule-2);
|
||||
}
|
||||
.standing__inner {
|
||||
max-width: var(--page-max);
|
||||
margin-inline: auto;
|
||||
padding: var(--section-gap) var(--page-gutter);
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
gap: var(--space-2xl);
|
||||
}
|
||||
@media (min-width: 60rem) {
|
||||
.standing__inner { grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr); align-items: center; }
|
||||
}
|
||||
.standing__title {
|
||||
font-family: var(--font-display);
|
||||
font-weight: var(--display-weight);
|
||||
font-style: var(--display-style);
|
||||
font-size: var(--text-display-s);
|
||||
line-height: var(--lh-tight);
|
||||
letter-spacing: var(--tracking-tight);
|
||||
color: var(--color-paper); /* gate 41 — light ink on dark */
|
||||
margin-bottom: var(--space-lg);
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.standing__lede {
|
||||
font-size: var(--text-md);
|
||||
line-height: var(--lh-relaxed);
|
||||
color: var(--ink-paper); /* light, ≥4.5:1 on dark ink */
|
||||
max-width: 48ch;
|
||||
}
|
||||
|
||||
/* C2 · inline form-as-CTA */
|
||||
.field { display: flex; flex-direction: column; gap: var(--space-sm); }
|
||||
.field__label {
|
||||
font: 500 var(--text-xs)/1 var(--font-label);
|
||||
letter-spacing: var(--tracking-label);
|
||||
text-transform: uppercase;
|
||||
color: var(--ink-label);
|
||||
}
|
||||
.field__row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center; /* gate 36 */
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
.field__input {
|
||||
flex: 1 1 14rem;
|
||||
min-width: 0;
|
||||
min-height: 44px; /* matches button — gate 39 */
|
||||
padding: 0 var(--space-md);
|
||||
font: 400 var(--text-base)/1 var(--font-body);
|
||||
color: var(--color-paper);
|
||||
background: var(--ink-field);
|
||||
border: var(--rule-fine) solid var(--color-rule-2); /* width never shifts — gate 39 */
|
||||
border-radius: var(--radius-input);
|
||||
outline: 2px solid transparent; /* reserve geometry — gate 39 */
|
||||
outline-offset: 1px;
|
||||
transition:
|
||||
border-color var(--dur-short) var(--ease-out),
|
||||
background-color var(--dur-short) var(--ease-out);
|
||||
}
|
||||
.field__input::placeholder { color: var(--ink-placeholder); }
|
||||
.field__input:hover { border-color: var(--ink-field-hover); }
|
||||
.field__input:focus-visible {
|
||||
outline: 2px solid var(--color-accent); /* outline, not border — gate 39 */
|
||||
border-color: var(--color-accent);
|
||||
background: var(--ink-field-2);
|
||||
}
|
||||
.field__row .btn--solid { flex: 0 0 auto; }
|
||||
.field__row .btn--solid:hover { background: var(--color-accent-lift); }
|
||||
|
||||
.field__input[aria-invalid="true"] {
|
||||
border-color: var(--color-accent-2);
|
||||
}
|
||||
.field__help {
|
||||
min-height: 1.2lh; /* reserve slot — gate 39 */
|
||||
font-size: var(--text-sm);
|
||||
line-height: var(--lh-normal);
|
||||
color: var(--ink-help);
|
||||
}
|
||||
.field__help[data-state="error"] { color: var(--on-dark-error); }
|
||||
.field__help[data-state="success"] { color: var(--on-dark-success); }
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
Ft6 · LETTER CLOSE
|
||||
════════════════════════════════════════════════════════════════ */
|
||||
.colophon {
|
||||
background: var(--color-paper);
|
||||
border-top: var(--rule-hair) solid var(--color-rule);
|
||||
}
|
||||
.colophon__inner {
|
||||
max-width: var(--page-max);
|
||||
margin-inline: auto;
|
||||
padding: var(--section-gap) var(--page-gutter) var(--space-2xl);
|
||||
}
|
||||
.colophon__close {
|
||||
max-width: 52ch;
|
||||
font-family: var(--font-display);
|
||||
font-weight: var(--display-weight);
|
||||
font-size: var(--text-2xl);
|
||||
line-height: var(--lh-snug);
|
||||
color: var(--color-ink);
|
||||
}
|
||||
.colophon__sign {
|
||||
margin-top: var(--space-xl);
|
||||
font-size: var(--text-lg);
|
||||
line-height: var(--lh-snug);
|
||||
color: var(--color-ink-2);
|
||||
}
|
||||
.colophon__name {
|
||||
font-family: var(--font-display);
|
||||
color: var(--color-ink);
|
||||
}
|
||||
.colophon__ps {
|
||||
margin-top: var(--space-lg);
|
||||
max-width: 50ch;
|
||||
font-size: var(--text-sm);
|
||||
line-height: var(--lh-relaxed);
|
||||
color: var(--color-muted);
|
||||
}
|
||||
.colophon__meta {
|
||||
margin-top: var(--space-2xl);
|
||||
padding-top: var(--space-lg);
|
||||
border-top: var(--rule-fine) solid var(--color-rule-2);
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: var(--space-xs) var(--space-lg);
|
||||
font: 500 var(--text-xs)/1.4 var(--font-label);
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-muted);
|
||||
}
|
||||
.colophon__wordmark {
|
||||
font-family: var(--font-display);
|
||||
font-size: var(--text-md);
|
||||
text-transform: none;
|
||||
letter-spacing: var(--tracking-tight);
|
||||
color: var(--color-ink);
|
||||
}
|
||||
.colophon__meta a {
|
||||
margin-left: auto;
|
||||
color: var(--color-accent);
|
||||
white-space: nowrap;
|
||||
transition: color var(--dur-short) var(--ease-out);
|
||||
}
|
||||
.colophon__meta a:hover { color: var(--color-ink); }
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
MOTION — one orchestrated reveal on load (stagger by --i)
|
||||
════════════════════════════════════════════════════════════════ */
|
||||
.reveal {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
animation: reveal var(--dur-long) var(--ease-out) forwards;
|
||||
animation-delay: calc(var(--i, 0) * 70ms);
|
||||
}
|
||||
@keyframes reveal { to { opacity: 1; transform: none; } }
|
||||
|
||||
/* Cards reveal as they enter the viewport — JS adds .in-view; CSS does the move. */
|
||||
.lot__grid .card {
|
||||
animation-delay: 0ms; /* hold until in view */
|
||||
animation-play-state: paused;
|
||||
}
|
||||
.lot__grid .card.in-view { animation-play-state: running; animation-delay: calc(var(--i, 0) * 70ms); }
|
||||
@supports not (animation-play-state: paused) {
|
||||
.lot__grid .card { opacity: 1; transform: none; animation: none; }
|
||||
}
|
||||
|
||||
/* ── Reduced motion — collapse all spatial motion to a fast fade ── */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*, *::before, *::after {
|
||||
animation-duration: 1ms !important;
|
||||
animation-delay: 0ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 1ms !important;
|
||||
}
|
||||
.reveal,
|
||||
.lot__grid .card { opacity: 1; transform: none; }
|
||||
.comb > span { opacity: 1; transform: none; }
|
||||
.comb > span:nth-child(4n + 2),
|
||||
.comb > span:nth-child(4n) { transform: translateY(50%); }
|
||||
.card:hover .vial__fill,
|
||||
.card:hover { transform: none; }
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
RESPONSIVE
|
||||
════════════════════════════════════════════════════════════════ */
|
||||
@media (max-width: 60rem) {
|
||||
.hero {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
gap: var(--space-xl);
|
||||
padding-top: clamp(2rem, 5vw, 3rem);
|
||||
}
|
||||
.hero__craft { order: -1; min-height: 16rem; } /* jar above the copy on stack */
|
||||
.comb { -webkit-mask-image: radial-gradient(circle at 50% 45%, var(--mask-matte) 30%, transparent 80%);
|
||||
mask-image: radial-gradient(circle at 50% 45%, var(--mask-matte) 30%, transparent 80%); }
|
||||
}
|
||||
|
||||
@media (max-width: 40rem) {
|
||||
body { font-size: var(--text-base); }
|
||||
.section__title { font-size: var(--text-2xl); }
|
||||
.hero__lede { font-size: var(--text-md); }
|
||||
.lot__intro, .ridge__standfirst, .poured__intro { font-size: var(--text-md); }
|
||||
.field__row .btn--solid { flex: 1 1 100%; } /* full-width CTA on phone — gate 49 friendly */
|
||||
.colophon__meta a { margin-left: 0; }
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/* Hallmark · tokens · theme: Garden (modern botanical almanac)
|
||||
* Warm oat-cream paper · botanical-ink green · living leaf-green accent + earthy clay pop.
|
||||
* Young Serif display · Hanken Grotesk body · Geist Mono specimen labels.
|
||||
* Axes: paper-band = light (95.5% L) · display-style = roman-serif · accent-hue = chromatic-green (140).
|
||||
* Every colour + font in styles.css references a token here — no inline values.
|
||||
*/
|
||||
|
||||
:root,
|
||||
[data-theme="garden"] {
|
||||
/* ── Surface + ink ─────────────────────────────────────────── */
|
||||
--color-paper: oklch(95.5% 0.022 92); /* warm oat cream — never grey-sage */
|
||||
--color-paper-2: oklch(92.5% 0.026 92); /* tinted band */
|
||||
--color-paper-3: oklch(88.5% 0.030 90); /* hover / deeper */
|
||||
--color-rule: oklch(83% 0.028 115); /* soft green-grey hairline */
|
||||
--color-rule-2: oklch(62% 0.045 132); /* mossy rule */
|
||||
--color-muted: oklch(48% 0.040 138); /* captions — muted green */
|
||||
--color-neutral: oklch(40% 0.045 142);
|
||||
--color-ink-2: oklch(33% 0.050 150); /* deep green body */
|
||||
--color-ink: oklch(24% 0.052 152); /* botanical ink — warm near-black green */
|
||||
|
||||
/* ── Accents ───────────────────────────────────────────────── */
|
||||
--color-accent: oklch(47% 0.13 140); /* living leaf-green (primary) */
|
||||
--color-accent-2: oklch(54% 0.14 46); /* earthy clay / terracotta pop */
|
||||
--color-honey: oklch(74% 0.155 80); /* lifted: warm honey gold for the jar fills */
|
||||
--color-honey-deep: oklch(63% 0.165 66); /* honey, deeper — gradient base + comb caps */
|
||||
--color-accent-ink: oklch(96% 0.02 92); /* cream sitting on green / clay / honey fills */
|
||||
--color-focus: oklch(47% 0.13 140);
|
||||
|
||||
/* botanical tints — soft section bands */
|
||||
--tint-moss: oklch(47% 0.13 140 / 0.10);
|
||||
--tint-clay: oklch(54% 0.14 46 / 0.10);
|
||||
--tint-comb: oklch(74% 0.155 80 / 0.16); /* faint honeycomb wash */
|
||||
|
||||
/* ── Type ──────────────────────────────────────────────────── */
|
||||
--font-display: "Young Serif", ui-serif, Georgia, serif;
|
||||
--font-body: "Hanken Grotesk", "Geist", ui-sans-serif, system-ui, sans-serif;
|
||||
--font-label: "Geist Mono", ui-monospace, "SF Mono", Menlo, monospace;
|
||||
|
||||
--display-weight: 400; /* Young Serif ships one organic weight */
|
||||
--display-style: normal; /* roman — no italic headers (gate 38a) */
|
||||
|
||||
--text-xs: 0.75rem;
|
||||
--text-sm: 0.8125rem;
|
||||
--text-base: 1rem;
|
||||
--text-md: 1.0625rem;
|
||||
--text-lg: 1.25rem;
|
||||
--text-xl: 1.5rem;
|
||||
--text-2xl: 1.9375rem;
|
||||
--text-3xl: 2.5rem;
|
||||
--text-display: clamp(2.7rem, 4.4vw + 1rem, 4.4rem);
|
||||
--text-display-s: clamp(2.1rem, 3.2vw + 1rem, 3.1rem);
|
||||
|
||||
--tracking-display: -0.006em;
|
||||
--tracking-tight: -0.004em;
|
||||
--tracking-normal: 0;
|
||||
--tracking-label: 0.14em; /* herbarium specimen tags */
|
||||
|
||||
--lh-tight: 1.06;
|
||||
--lh-snug: 1.22;
|
||||
--lh-normal: 1.5;
|
||||
--lh-relaxed: 1.62;
|
||||
|
||||
/* ── Space (4pt scale, role-named) ─────────────────────────── */
|
||||
--space-3xs: 0.125rem;
|
||||
--space-2xs: 0.25rem;
|
||||
--space-xs: 0.5rem;
|
||||
--space-sm: 0.75rem;
|
||||
--space-md: 1rem;
|
||||
--space-lg: 1.5rem;
|
||||
--space-xl: 2.5rem;
|
||||
--space-2xl: 4rem;
|
||||
--space-3xl: 6.5rem;
|
||||
--space-4xl: 10rem;
|
||||
|
||||
--section-gap: 6.5rem;
|
||||
--section-head-gap: 1.6rem;
|
||||
|
||||
--page-max: 74rem;
|
||||
--page-gutter: clamp(1.25rem, 4vw, 3rem);
|
||||
--measure: 58ch;
|
||||
|
||||
/* ── Shape ─────────────────────────────────────────────────── */
|
||||
--radius-card: 10px;
|
||||
--radius-pill: 8px;
|
||||
--radius-input: 8px;
|
||||
--rule-hair: 0.5px;
|
||||
--rule-fine: 1px;
|
||||
--rule-card: 1px;
|
||||
--shadow-card: 0 14px 34px -20px oklch(24% 0.05 150 / 0.22),
|
||||
0 1px 2px oklch(24% 0.05 150 / 0.08);
|
||||
--shadow-card-hover: 0 26px 52px -24px oklch(24% 0.05 150 / 0.28),
|
||||
0 2px 4px oklch(24% 0.05 150 / 0.10);
|
||||
|
||||
/* ── Motion ────────────────────────────────────────────────── */
|
||||
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
|
||||
--ease-in: cubic-bezier(0.7, 0, 0.84, 0);
|
||||
--ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
|
||||
--dur-micro: 120ms;
|
||||
--dur-short: 220ms;
|
||||
--dur-long: 420ms;
|
||||
|
||||
/* ── Z-scale (named, no freestyling) ───────────────────────── */
|
||||
--z-base: 1;
|
||||
--z-raised: 10;
|
||||
--z-sticky: 200;
|
||||
--z-sticky-nav: 300;
|
||||
--z-skip: 600; /* skip-link must out-paint the sticky nav */
|
||||
|
||||
--banner-height: 60px;
|
||||
|
||||
/* ── Interaction shades (button states) ────────────────────── */
|
||||
--color-accent-press: oklch(42% 0.135 142); /* solid CTA :hover on light */
|
||||
--color-accent-lift: oklch(54% 0.135 142); /* solid CTA :hover on dark band */
|
||||
|
||||
/* ── Craft palette (hand-built jar + vials + honeycomb) ────── */
|
||||
--glass: oklch(92% 0.03 92 / 0.45); /* jar glass body */
|
||||
--glass-vial: oklch(92% 0.03 92 / 0.40); /* specimen vial glass */
|
||||
--comb-edge: oklch(74% 0.155 80 / 0.30); /* honeycomb cell hairline */
|
||||
--wood: oklch(58% 0.07 62); /* dipper + cork (raw wood) */
|
||||
--collar: oklch(48% 0.13 46); /* jar collar (deep clay) */
|
||||
--honey-spring: oklch(82% 0.13 88); /* palest pour */
|
||||
--honey-bass: oklch(78% 0.135 96); /* basswood */
|
||||
--honey-autumn: oklch(60% 0.14 60); /* darkest pour */
|
||||
--shadow-jar: 0 18px 24px oklch(24% 0.05 150 / 0.18);
|
||||
--shadow-vial: 0 6px 10px oklch(24% 0.05 150 / 0.16);
|
||||
--mask-matte: oklch(0% 0 0); /* mask alpha only — never painted */
|
||||
|
||||
/* ── Dark band (the standing-jar CTA on --color-ink) ───────── */
|
||||
--ink-paper: oklch(88% 0.012 110); /* light body text on dark ink */
|
||||
--ink-label: oklch(82% 0.03 110); /* field label on dark */
|
||||
--ink-help: oklch(78% 0.02 110); /* field help on dark */
|
||||
--ink-placeholder: oklch(70% 0.02 110); /* placeholder on dark */
|
||||
--ink-field: oklch(30% 0.04 150); /* input fill on dark */
|
||||
--ink-field-2: oklch(33% 0.045 150); /* input fill :focus on dark */
|
||||
--ink-field-hover: oklch(70% 0.05 132); /* input border :hover on dark */
|
||||
--on-dark-error: oklch(74% 0.12 46); /* error help on dark */
|
||||
--on-dark-success: oklch(78% 0.13 142); /* success help on dark */
|
||||
}
|
||||
@@ -0,0 +1,306 @@
|
||||
<!doctype html>
|
||||
<html lang="en" data-theme="press">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
||||
<title>Bríd Halloran — experimental typographer · concrete poetry, variable fonts, broken-grid posters</title>
|
||||
<meta name="description" content="Bríd Halloran sets type that won't hold still: concrete poems, variable-font specimens you can pull by hand, and posters built on a grid she breaks on purpose. A working studio in Cork." />
|
||||
|
||||
<!-- inline mark — a red square, the studio's only logo; avoids a favicon 404 -->
|
||||
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect width='16' height='16' fill='%23151013'/%3E%3Crect x='3' y='3' width='10' height='10' fill='%23e0452f'/%3E%3C/svg%3E" />
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link rel="preload" as="style"
|
||||
href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,200..800&family=Fraunces:ital,opsz,wght@0,9..144,300..600;1,9..144,300..500&family=JetBrains+Mono:wght@400;500&display=swap" />
|
||||
<link rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,200..800&family=Fraunces:ital,opsz,wght@0,9..144,300..600;1,9..144,300..500&family=JetBrains+Mono:wght@400;500&display=swap" />
|
||||
|
||||
<link rel="stylesheet" href="./tokens.css" />
|
||||
<link rel="stylesheet" href="./styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<a class="skip-link" href="#main">Skip to content</a>
|
||||
|
||||
<!-- N3 · Side-rail — rotated wordmark + section dot index. The vertical
|
||||
type is itself a small typographic argument. -->
|
||||
<nav class="rail" aria-label="Primary">
|
||||
<a class="rail__mark" href="#top">
|
||||
<span class="rail__name">Bríd Halloran</span>
|
||||
<span class="rail__role" aria-hidden="true">typographer</span>
|
||||
</a>
|
||||
<ul class="rail__dots" aria-label="Sections">
|
||||
<li><a href="#living" data-label="Living specimen"><span class="rail__dot"></span></a></li>
|
||||
<li><a href="#weights" data-label="The family"><span class="rail__dot"></span></a></li>
|
||||
<li><a href="#concrete" data-label="Concrete poetry"><span class="rail__dot"></span></a></li>
|
||||
<li><a href="#poster" data-label="Broken grid"><span class="rail__dot"></span></a></li>
|
||||
<li><a href="#work" data-label="Selected work"><span class="rail__dot"></span></a></li>
|
||||
<li><a href="#studio" data-label="The studio"><span class="rail__dot"></span></a></li>
|
||||
</ul>
|
||||
<a class="rail__year" href="#studio">Cork · MMXXVI</a>
|
||||
</nav>
|
||||
|
||||
<div class="shell" id="top">
|
||||
<main id="main">
|
||||
|
||||
<!-- ───────── HERO · the living specimen ───────── -->
|
||||
<section class="hero" id="living" aria-labelledby="hero-h">
|
||||
<p class="hero__slug reveal" style="--i:0">
|
||||
Press Quaternary <span aria-hidden="true">— </span>a working type studio. Bríd Halloran sets letters that refuse to sit still.
|
||||
</p>
|
||||
|
||||
<h1 class="hero__h" id="hero-h">
|
||||
<span class="reveal" style="--i:1">A typeface is</span>
|
||||
<span class="reveal" style="--i:2">a verb, not</span>
|
||||
<span class="reveal" style="--i:3">a noun.</span>
|
||||
</h1>
|
||||
|
||||
<!-- The colossal word IS the imagery — a live variable specimen the
|
||||
reader can pull by hand. Ambient breathing on the weight/width axes;
|
||||
the slider overrides it. Pure CSS + a small JS bridge. -->
|
||||
<figure class="specimen-live reveal" style="--i:4" aria-labelledby="live-cap">
|
||||
<div class="specimen-live__stage">
|
||||
<span class="specimen-live__word" id="liveWord" aria-hidden="true">Restless</span>
|
||||
</div>
|
||||
<figcaption class="specimen-live__panel" id="live-cap">
|
||||
<div class="axis">
|
||||
<div class="axis__row">
|
||||
<label class="axis__label" for="axWght">Weight<span class="axis__val" id="wghtVal">320</span></label>
|
||||
<input class="axis__range" id="axWght" type="range" min="200" max="800" value="320" step="1"
|
||||
aria-describedby="axis-help" />
|
||||
</div>
|
||||
<div class="axis__row">
|
||||
<label class="axis__label" for="axWdth">Width<span class="axis__val" id="wdthVal">100</span></label>
|
||||
<input class="axis__range" id="axWdth" type="range" min="75" max="100" value="100" step="1"
|
||||
aria-describedby="axis-help" />
|
||||
</div>
|
||||
<div class="axis__row">
|
||||
<label class="axis__label" for="axOpsz">Optical<span class="axis__val" id="opszVal">96</span></label>
|
||||
<input class="axis__range" id="axOpsz" type="range" min="12" max="96" value="96" step="1"
|
||||
aria-describedby="axis-help" />
|
||||
</div>
|
||||
<p class="axis__help" id="axis-help">
|
||||
Pull the axes. The word reads <em>Restless</em> by design — that is the point of a variable face.
|
||||
</p>
|
||||
</div>
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
<a class="link-arrow hero__cta reveal" style="--i:5" href="#work">
|
||||
<span>See the work</span>
|
||||
<svg class="link-arrow__glyph" viewBox="0 0 24 12" width="32" height="16" aria-hidden="true">
|
||||
<path d="M0 6h22M17 1l5 5-5 5" fill="none" stroke="currentColor" stroke-width="1.5" />
|
||||
</svg>
|
||||
</a>
|
||||
</section>
|
||||
|
||||
<!-- ───────── SPECIMEN · the family, set at sizes ───────── -->
|
||||
<section class="proof" id="weights" aria-labelledby="weights-h">
|
||||
<header class="plate-head">
|
||||
<p class="plate-head__no" aria-hidden="true">Plate 01</p>
|
||||
<h2 class="plate-head__title" id="weights-h">Quaternary Grotesque</h2>
|
||||
<p class="plate-head__sub">Six masters on one weight axis, one width axis, one optical size. Drawn 2024–2026. Latin Extended, set here at the sizes it was made to do work at.</p>
|
||||
</header>
|
||||
|
||||
<ol class="weights" aria-label="Weights, light to bold">
|
||||
<li class="weights__row" data-w="240">
|
||||
<span class="weights__tag">Thin · 240</span>
|
||||
<p class="weights__line" style="font-variation-settings:'wght' 240,'opsz' 96;">Hairlines hold at billboard size.</p>
|
||||
</li>
|
||||
<li class="weights__row" data-w="340">
|
||||
<span class="weights__tag">Book · 340</span>
|
||||
<p class="weights__line" style="font-variation-settings:'wght' 340,'opsz' 64;">Reads quiet in a paragraph, loud on a wall.</p>
|
||||
</li>
|
||||
<li class="weights__row" data-w="480">
|
||||
<span class="weights__tag">Medium · 480</span>
|
||||
<p class="weights__line" style="font-variation-settings:'wght' 480,'opsz' 48;">A working middle, for captions and credits.</p>
|
||||
</li>
|
||||
<li class="weights__row" data-w="620">
|
||||
<span class="weights__tag">Semibold · 620</span>
|
||||
<p class="weights__line" style="font-variation-settings:'wght' 620,'opsz' 40;">Where a headline starts to push back.</p>
|
||||
</li>
|
||||
<li class="weights__row" data-w="800">
|
||||
<span class="weights__tag">Black · 800</span>
|
||||
<p class="weights__line" style="font-variation-settings:'wght' 800,'opsz' 24,'wdth' 92;">Set it once. Let it shout.</p>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<dl class="glyphset" aria-label="Glyph and feature coverage">
|
||||
<div class="glyphset__cell">
|
||||
<dt>Character set</dt>
|
||||
<dd>Latin Extended-A · 642 glyphs</dd>
|
||||
</div>
|
||||
<div class="glyphset__cell">
|
||||
<dt>Figures</dt>
|
||||
<dd>Lining, oldstyle, tabular, fractions</dd>
|
||||
</div>
|
||||
<div class="glyphset__cell">
|
||||
<dt>Axes</dt>
|
||||
<dd>Weight 200–800 · Width 75–100 · Optical 12–96</dd>
|
||||
</div>
|
||||
<div class="glyphset__cell">
|
||||
<dt>Format</dt>
|
||||
<dd>Variable OTF · WOFF2 · one file</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<!-- ───────── CONCRETE POETRY · a calligramme, set by hand ───────── -->
|
||||
<section class="concrete" id="concrete" aria-labelledby="concrete-h">
|
||||
<header class="plate-head plate-head--ink">
|
||||
<p class="plate-head__no" aria-hidden="true">Plate 02</p>
|
||||
<h2 class="plate-head__title" id="concrete-h">Concrete poetry</h2>
|
||||
<p class="plate-head__sub">The shape carries the meaning. Below: <em>“Rain on the Lee,”</em> set so the lines fall the way the rain does — typography as the picture, not its caption.</p>
|
||||
</header>
|
||||
|
||||
<!-- Calligramme: the poem arranged into falling rain using pure CSS
|
||||
transforms. Decorative as art, but fully readable as text. -->
|
||||
<figure class="poem" aria-labelledby="poem-cap">
|
||||
<p class="poem__visually-hidden">
|
||||
Rain on the Lee, a concrete poem. The words fall down the page in slanting lines,
|
||||
faster and heavier toward the bottom, where they pool into the single word: river.
|
||||
</p>
|
||||
<div class="poem__field" aria-hidden="true">
|
||||
<span class="poem__w" style="--c:1;--r:1;--rot:-4deg;--w:300">slow</span>
|
||||
<span class="poem__w" style="--c:4;--r:1;--rot:-7deg;--w:320">at</span>
|
||||
<span class="poem__w" style="--c:7;--r:1;--rot:-5deg;--w:340">first</span>
|
||||
<span class="poem__w" style="--c:2;--r:2;--rot:-9deg;--w:360">the</span>
|
||||
<span class="poem__w" style="--c:5;--r:2;--rot:-8deg;--w:380">grey</span>
|
||||
<span class="poem__w" style="--c:8;--r:2;--rot:-6deg;--w:360">comes</span>
|
||||
<span class="poem__w" style="--c:1;--r:3;--rot:-11deg;--w:420">down</span>
|
||||
<span class="poem__w" style="--c:4;--r:3;--rot:-10deg;--w:440">the</span>
|
||||
<span class="poem__w" style="--c:7;--r:3;--rot:-9deg;--w:420">quays</span>
|
||||
<span class="poem__w" style="--c:3;--r:4;--rot:-13deg;--w:520">harder</span>
|
||||
<span class="poem__w" style="--c:6;--r:4;--rot:-12deg;--w:540">now</span>
|
||||
<span class="poem__w" style="--c:2;--r:5;--rot:-15deg;--w:640">faster</span>
|
||||
<span class="poem__w" style="--c:6;--r:5;--rot:-14deg;--w:660">heavier</span>
|
||||
<span class="poem__w poem__w--pool" style="--c:4;--r:6;--rot:0deg;--w:800">river</span>
|
||||
</div>
|
||||
<figcaption class="poem__cap" id="poem-cap">
|
||||
<span class="poem__title">Rain on the Lee</span>
|
||||
<span class="poem__meta">Calligramme · Quaternary Grotesque · set by hand, 2025</span>
|
||||
</figcaption>
|
||||
</figure>
|
||||
</section>
|
||||
|
||||
<!-- ───────── BROKEN-GRID POSTER · the plate she breaks on purpose ───────── -->
|
||||
<section class="proof" id="poster" aria-labelledby="poster-h">
|
||||
<header class="plate-head">
|
||||
<p class="plate-head__no" aria-hidden="true">Plate 03</p>
|
||||
<h2 class="plate-head__title" id="poster-h">Broken-grid posters</h2>
|
||||
<p class="plate-head__sub">A twelve-column grid is a promise. I keep it just long enough that breaking it means something. One element always crosses the line on purpose.</p>
|
||||
</header>
|
||||
|
||||
<figure class="poster" aria-labelledby="poster-cap">
|
||||
<div class="poster__plate">
|
||||
<span class="poster__baseline" aria-hidden="true"></span>
|
||||
<span class="poster__kicker">Lent Series · Triskel</span>
|
||||
<h3 class="poster__title" aria-hidden="true">
|
||||
<span class="poster__word poster__word--a">OFF</span>
|
||||
<span class="poster__word poster__word--b">THE</span>
|
||||
<span class="poster__word poster__word--c">GRID</span>
|
||||
</h3>
|
||||
<span class="poster__vis-hidden">Poster reading: Off the Grid — a night of new typography, Triskel Arts Centre.</span>
|
||||
<p class="poster__line poster__line--date">Fri 14 March · 8pm</p>
|
||||
<p class="poster__line poster__line--place">Triskel Arts Centre, Tobin Street</p>
|
||||
<span class="poster__bleed" aria-hidden="true">14</span>
|
||||
</div>
|
||||
<figcaption class="poster__cap" id="poster-cap">
|
||||
The numeral bleeds past the right margin — the only element allowed to cross.
|
||||
Everything else holds the grid so the break reads as a decision, not an accident.
|
||||
</figcaption>
|
||||
</figure>
|
||||
</section>
|
||||
|
||||
<!-- ───────── SELECTED WORK · tabular index (F3) ───────── -->
|
||||
<section class="work" id="work" aria-labelledby="work-h">
|
||||
<header class="plate-head plate-head--ink">
|
||||
<p class="plate-head__no" aria-hidden="true">Index</p>
|
||||
<h2 class="plate-head__title" id="work-h">Selected work</h2>
|
||||
<p class="plate-head__sub">Eleven years, set in a ledger. Type design, identities, poster series, and the occasional book — each with whoever commissioned it.</p>
|
||||
</header>
|
||||
|
||||
<ol class="ledger" aria-label="Selected work, newest first">
|
||||
<li class="ledger__row">
|
||||
<span class="ledger__year">2026</span>
|
||||
<span class="ledger__name">Quaternary Grotesque</span>
|
||||
<span class="ledger__kind">Variable typeface · self-released</span>
|
||||
</li>
|
||||
<li class="ledger__row">
|
||||
<span class="ledger__year">2025</span>
|
||||
<span class="ledger__name">Rain on the Lee</span>
|
||||
<span class="ledger__kind">Concrete poem · Cork Midsummer</span>
|
||||
</li>
|
||||
<li class="ledger__row">
|
||||
<span class="ledger__year">2025</span>
|
||||
<span class="ledger__name">Off the Grid · poster series</span>
|
||||
<span class="ledger__kind">Identity & print · Triskel Arts Centre</span>
|
||||
</li>
|
||||
<li class="ledger__row">
|
||||
<span class="ledger__year">2024</span>
|
||||
<span class="ledger__name">Strand & Marsh</span>
|
||||
<span class="ledger__kind">Bespoke wordmark · a saltmarsh distillery</span>
|
||||
</li>
|
||||
<li class="ledger__row">
|
||||
<span class="ledger__year">2023</span>
|
||||
<span class="ledger__name">The Compositor’s Almanac</span>
|
||||
<span class="ledger__kind">204-page book · designed & typeset</span>
|
||||
</li>
|
||||
<li class="ledger__row">
|
||||
<span class="ledger__year">2022</span>
|
||||
<span class="ledger__name">Marsh Mono</span>
|
||||
<span class="ledger__kind">Monospace family · for a print zine</span>
|
||||
</li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<!-- ───────── THE STUDIO · the close + contact ───────── -->
|
||||
<section class="studio" id="studio" aria-labelledby="studio-h">
|
||||
<h2 class="studio__h" id="studio-h">Set by hand, in Cork.</h2>
|
||||
<div class="studio__cols">
|
||||
<p class="studio__lede">
|
||||
I draw type, set poems into shapes, and make posters that argue with their own grid.
|
||||
Most of it starts on tracing paper and ends as one variable file. I take a few
|
||||
commissions a year — identities that need a face nobody else has, and print
|
||||
that wants to be read across a room.
|
||||
</p>
|
||||
|
||||
<!-- Inline form-as-CTA (C2). Eight input states; helper slot reserved. -->
|
||||
<form class="enquire" id="enquire" novalidate>
|
||||
<label class="enquire__label" for="enq-email">Start a conversation</label>
|
||||
<div class="enquire__row">
|
||||
<input class="enquire__input" id="enq-email" name="email" type="email"
|
||||
inputmode="email" autocomplete="email" placeholder="you@studio.com"
|
||||
aria-describedby="enq-help" required />
|
||||
<button class="btn enquire__btn" type="submit">
|
||||
<span class="btn__label">Send a note</span>
|
||||
</button>
|
||||
</div>
|
||||
<p class="enquire__help" id="enq-help" aria-live="polite">A real reply, from me, within a few days.</p>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
<!-- ───────── Ft1 · Mast-headed footer ───────── -->
|
||||
<footer class="foot" role="contentinfo">
|
||||
<p class="foot__mark">Press Quaternary</p>
|
||||
<p class="foot__tag">The studio of Bríd Halloran — type, poems, and posters, set against the grid.</p>
|
||||
<p class="foot__set">
|
||||
Set in Bricolage Grotesque & Fraunces, with JetBrains Mono for the margins.
|
||||
Proofed in Cork. <span class="foot__sep" aria-hidden="true">·</span> © 2026 Bríd Halloran.
|
||||
</p>
|
||||
<p class="foot__links">
|
||||
<a href="#living">Top</a>
|
||||
<span class="foot__sep" aria-hidden="true">·</span>
|
||||
<a href="mailto:studio@pressquaternary.example">studio@pressquaternary.example</a>
|
||||
<span class="foot__sep" aria-hidden="true">·</span>
|
||||
<a href="#work">Specimen PDF</a>
|
||||
</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script src="./script.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,139 @@
|
||||
/* gen-13 · Bríd Halloran — Press Quaternary
|
||||
* Three small, purposeful behaviours:
|
||||
* 1. the live variable-font specimen (range axes drive font-variation-settings)
|
||||
* 2. a scroll-spy that marks the current section dot in the side-rail
|
||||
* 3. an optimistic enquire form with the full set of states
|
||||
* All motion respects prefers-reduced-motion.
|
||||
*/
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
/* ── 1 · Living variable specimen ───────────────────────── */
|
||||
var word = document.getElementById("liveWord");
|
||||
var fig = document.querySelector(".specimen-live");
|
||||
var axes = [
|
||||
{ input: document.getElementById("axWght"), out: document.getElementById("wghtVal"), prop: "--w" },
|
||||
{ input: document.getElementById("axWdth"), out: document.getElementById("wdthVal"), prop: "--wd" },
|
||||
{ input: document.getElementById("axOpsz"), out: document.getElementById("opszVal"), prop: "--op" }
|
||||
];
|
||||
|
||||
function applyAxes() {
|
||||
if (!word) return;
|
||||
var w = axes[0].input ? axes[0].input.value : 320;
|
||||
var wd = axes[1].input ? axes[1].input.value : 100;
|
||||
var op = axes[2].input ? axes[2].input.value : 96;
|
||||
// write the variation directly so it overrides the breathing keyframes
|
||||
word.style.fontVariationSettings =
|
||||
"'wght' " + w + ", 'wdth' " + wd + ", 'opsz' " + op;
|
||||
axes.forEach(function (a) {
|
||||
if (a.out && a.input) a.out.textContent = a.input.value;
|
||||
});
|
||||
}
|
||||
|
||||
axes.forEach(function (a) {
|
||||
if (!a.input) return;
|
||||
a.input.addEventListener("input", function () {
|
||||
if (fig) fig.dataset.grabbed = "true"; // stop the ambient breathe loop
|
||||
applyAxes();
|
||||
});
|
||||
});
|
||||
// initialise the value read-outs (breathing loop owns the glyph until first grab)
|
||||
axes.forEach(function (a) { if (a.out && a.input) a.out.textContent = a.input.value; });
|
||||
|
||||
/* ── 2 · Scroll-spy for the rail dots ───────────────────── */
|
||||
var dotLinks = Array.prototype.slice.call(
|
||||
document.querySelectorAll(".rail__dots a")
|
||||
);
|
||||
var sections = dotLinks
|
||||
.map(function (a) {
|
||||
var id = a.getAttribute("href").slice(1);
|
||||
return { link: a, el: document.getElementById(id) };
|
||||
})
|
||||
.filter(function (s) { return s.el; });
|
||||
|
||||
if ("IntersectionObserver" in window && sections.length) {
|
||||
var spy = new IntersectionObserver(
|
||||
function (entries) {
|
||||
entries.forEach(function (entry) {
|
||||
if (!entry.isIntersecting) return;
|
||||
var match = sections.find(function (s) { return s.el === entry.target; });
|
||||
if (!match) return;
|
||||
dotLinks.forEach(function (l) {
|
||||
var on = l === match.link;
|
||||
l.classList.toggle("is-current", on);
|
||||
if (on) { l.setAttribute("aria-current", "true"); }
|
||||
else { l.removeAttribute("aria-current"); }
|
||||
});
|
||||
});
|
||||
},
|
||||
{ rootMargin: "-45% 0px -50% 0px", threshold: 0 }
|
||||
);
|
||||
sections.forEach(function (s) { spy.observe(s.el); });
|
||||
}
|
||||
|
||||
/* ── 3 · Enquire form — optimistic, all states ──────────── */
|
||||
var form = document.getElementById("enquire");
|
||||
if (form) {
|
||||
var input = document.getElementById("enq-email");
|
||||
var help = document.getElementById("enq-help");
|
||||
var btn = form.querySelector(".enquire__btn");
|
||||
var label = btn ? btn.querySelector(".btn__label") : null;
|
||||
var defaultHelp = help ? help.textContent : "";
|
||||
var touched = false;
|
||||
|
||||
function setHelp(text, tone) {
|
||||
if (!help) return;
|
||||
help.textContent = text;
|
||||
if (tone) help.setAttribute("data-tone", tone);
|
||||
else help.removeAttribute("data-tone");
|
||||
}
|
||||
|
||||
function validEmail(v) {
|
||||
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v.trim());
|
||||
}
|
||||
|
||||
function validate() {
|
||||
if (!input) return false;
|
||||
var ok = validEmail(input.value);
|
||||
input.setAttribute("aria-invalid", ok ? "false" : "true");
|
||||
return ok;
|
||||
}
|
||||
|
||||
if (input) {
|
||||
input.addEventListener("blur", function () {
|
||||
touched = true;
|
||||
if (input.value && !validate()) {
|
||||
setHelp("That address is missing an @ or a domain. Check it and try again.", "error");
|
||||
} else {
|
||||
setHelp(defaultHelp, null);
|
||||
}
|
||||
});
|
||||
input.addEventListener("input", function () {
|
||||
if (!touched) return;
|
||||
if (validate()) setHelp(defaultHelp, null);
|
||||
});
|
||||
}
|
||||
|
||||
form.addEventListener("submit", function (e) {
|
||||
e.preventDefault();
|
||||
touched = true;
|
||||
if (!validate()) {
|
||||
setHelp("That address is missing an @ or a domain. Check it and try again.", "error");
|
||||
if (input) input.focus({ preventScroll: true });
|
||||
return;
|
||||
}
|
||||
|
||||
// optimistic: show loading, then resolve to a quiet success (no toast)
|
||||
if (btn) { btn.dataset.state = "loading"; btn.disabled = true; }
|
||||
if (label) label.textContent = "Sending…";
|
||||
setHelp("Sending your note…", null);
|
||||
|
||||
window.setTimeout(function () {
|
||||
if (btn) { btn.dataset.state = "success"; btn.disabled = false; }
|
||||
if (label) label.textContent = "Note sent ✓";
|
||||
if (input) { input.value = ""; input.setAttribute("aria-invalid", "false"); }
|
||||
setHelp("Thanks — your note is on its way. I’ll reply from studio@pressquaternary within a few days.", "success");
|
||||
}, 900);
|
||||
});
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,995 @@
|
||||
/* Hallmark · macrostructure: Type Specimen · H1 Marquee knobs: size=colossal, alignment=left-bias, underlay=single rule below
|
||||
* theme: custom (tuned) · vibe: "ink-black press, off-register, broken-grid, kinetic"
|
||||
* paper: oklch(13% 0.014 25) · accent: oklch(60% 0.21 25)
|
||||
* display: Bricolage Grotesque (variable) · body: Fraunces (variable) · outlier: JetBrains Mono
|
||||
* axes: dark / display-heavy / warm · studied: no · context: inferred
|
||||
* genre: editorial · nav: N3 Side-rail · footer: Ft1 Mast-headed · enrichment: none (typography only)
|
||||
* Hallmark · pre-emit critique: P5 H5 E5 S5 R4 V5
|
||||
* contrast: pass (40–41) · honest: pass (46) · chrome: pass (47) · tokens: pass (48)
|
||||
* responsive: pass (49) · icons: pass (30) · mobile: pass (34, 49, 50–57) · slop: pass (42–45)
|
||||
*/
|
||||
|
||||
/* ── Reset / base ─────────────────────────────────────────── */
|
||||
*, *::before, *::after { box-sizing: border-box; }
|
||||
|
||||
html {
|
||||
overflow-x: clip;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
scroll-behavior: smooth;
|
||||
scroll-padding-top: var(--space-xl);
|
||||
}
|
||||
|
||||
body {
|
||||
overflow-x: clip;
|
||||
margin: 0;
|
||||
background: var(--color-paper);
|
||||
color: var(--color-ink);
|
||||
font-family: var(--font-body);
|
||||
font-optical-sizing: auto;
|
||||
font-weight: 360;
|
||||
font-size: var(--text-base);
|
||||
line-height: var(--lh-normal);
|
||||
font-variant-numeric: oldstyle-nums;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
padding-inline: max(0px, env(safe-area-inset-left));
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
html { scroll-behavior: auto; }
|
||||
}
|
||||
|
||||
h1, h2, h3, p, ul, ol, dl, dd, figure, blockquote { margin: 0; }
|
||||
ul, ol { list-style: none; padding: 0; }
|
||||
a { color: inherit; text-decoration: none; }
|
||||
img, svg { display: block; max-width: 100%; }
|
||||
:focus { outline: none; }
|
||||
:focus-visible {
|
||||
outline: 2px solid var(--color-focus);
|
||||
outline-offset: 3px;
|
||||
}
|
||||
|
||||
.skip-link {
|
||||
position: fixed;
|
||||
left: var(--space-md);
|
||||
top: var(--space-md);
|
||||
z-index: var(--z-toast);
|
||||
background: var(--color-accent);
|
||||
color: var(--color-accent-ink);
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-sm);
|
||||
letter-spacing: var(--tracking-label);
|
||||
text-transform: uppercase;
|
||||
padding: var(--space-sm) var(--space-md);
|
||||
transform: translateY(-150%);
|
||||
transition: transform var(--dur-short) var(--ease-out);
|
||||
}
|
||||
.skip-link:focus-visible { transform: translateY(0); }
|
||||
|
||||
/* ── Shell — offset for the fixed side-rail ───────────────── */
|
||||
.shell {
|
||||
margin-inline-start: var(--rail-width);
|
||||
}
|
||||
.shell > main { display: block; }
|
||||
|
||||
/* every major section shares one width model + rhythm */
|
||||
.hero,
|
||||
.proof,
|
||||
.concrete,
|
||||
.work,
|
||||
.studio {
|
||||
padding-inline: var(--page-gutter);
|
||||
}
|
||||
.proof,
|
||||
.concrete,
|
||||
.work {
|
||||
padding-block: var(--section-gap);
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════
|
||||
N3 · SIDE-RAIL NAV
|
||||
Rotated wordmark + section dot index, fixed to the left edge.
|
||||
════════════════════════════════════════════════════════════ */
|
||||
.rail {
|
||||
position: fixed;
|
||||
inset-block: 0;
|
||||
inset-inline-start: 0;
|
||||
width: var(--rail-width);
|
||||
z-index: var(--z-sticky-nav);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-block: var(--space-lg);
|
||||
background: var(--color-paper);
|
||||
border-inline-end: var(--rule-hair) solid var(--color-rule);
|
||||
}
|
||||
|
||||
.rail__mark {
|
||||
writing-mode: vertical-rl;
|
||||
transform: rotate(180deg);
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: var(--space-md);
|
||||
}
|
||||
.rail__name {
|
||||
font-family: var(--font-display);
|
||||
font-variation-settings: 'wght' 600, 'opsz' 18;
|
||||
font-size: var(--text-md);
|
||||
letter-spacing: 0.02em;
|
||||
color: var(--color-ink);
|
||||
}
|
||||
.rail__role {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-xs);
|
||||
letter-spacing: var(--tracking-label);
|
||||
text-transform: uppercase;
|
||||
color: var(--color-muted);
|
||||
}
|
||||
.rail__mark:hover .rail__name { color: var(--color-accent); }
|
||||
|
||||
.rail__dots {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-md);
|
||||
align-items: center;
|
||||
}
|
||||
.rail__dots a {
|
||||
position: relative;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
}
|
||||
/* expand the touch target without changing visual size */
|
||||
.rail__dots a::before { content: ""; position: absolute; inset: -10px; }
|
||||
.rail__dot {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border: var(--rule-hair) solid var(--color-rule-2);
|
||||
background: transparent;
|
||||
transition: background-color var(--dur-short) var(--ease-out),
|
||||
border-color var(--dur-short) var(--ease-out),
|
||||
transform var(--dur-short) var(--ease-out);
|
||||
}
|
||||
@media (hover: hover) {
|
||||
.rail__dots a:hover .rail__dot { border-color: var(--color-accent); transform: scale(1.25); }
|
||||
}
|
||||
.rail__dots a:focus-visible { outline-offset: 4px; }
|
||||
.rail__dots a.is-current .rail__dot {
|
||||
background: var(--color-accent);
|
||||
border-color: var(--color-accent);
|
||||
}
|
||||
/* tooltip-style label on hover/focus — focus shows instantly, hover delays */
|
||||
.rail__dots a::after {
|
||||
content: attr(data-label);
|
||||
position: absolute;
|
||||
inset-inline-start: 140%;
|
||||
white-space: nowrap;
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-xs);
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-ink);
|
||||
background: var(--color-paper-2);
|
||||
border: var(--rule-hair) solid var(--color-rule);
|
||||
padding: var(--space-2xs) var(--space-xs);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity var(--dur-short) var(--ease-out) 800ms;
|
||||
}
|
||||
.rail__dots a:hover::after { opacity: 1; }
|
||||
.rail__dots a:focus-visible::after { opacity: 1; transition-delay: 0ms; }
|
||||
|
||||
.rail__year {
|
||||
writing-mode: vertical-rl;
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-xs);
|
||||
letter-spacing: var(--tracking-micro);
|
||||
text-transform: uppercase;
|
||||
color: var(--color-muted);
|
||||
}
|
||||
.rail__year:hover { color: var(--color-ink-2); }
|
||||
|
||||
/* ════════════════════════════════════════════════════════════
|
||||
HERO · the living specimen
|
||||
════════════════════════════════════════════════════════════ */
|
||||
.hero {
|
||||
position: relative;
|
||||
display: grid;
|
||||
align-content: start;
|
||||
gap: var(--space-md);
|
||||
padding-block: var(--space-2xl) var(--space-3xl);
|
||||
max-width: var(--page-max);
|
||||
border-bottom: var(--rule-hair) solid var(--color-rule);
|
||||
}
|
||||
|
||||
.hero__slug {
|
||||
max-width: 56ch;
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-sm);
|
||||
letter-spacing: 0.04em;
|
||||
line-height: 1.6;
|
||||
color: var(--color-muted);
|
||||
}
|
||||
.hero__slug::before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: 2.5rem;
|
||||
height: var(--rule-fine);
|
||||
background: var(--color-accent);
|
||||
vertical-align: middle;
|
||||
margin-inline-end: var(--space-sm);
|
||||
}
|
||||
|
||||
.hero__h {
|
||||
font-family: var(--font-display);
|
||||
font-optical-sizing: auto;
|
||||
font-variation-settings: 'wght' 560, 'opsz' 96;
|
||||
font-size: var(--text-display);
|
||||
line-height: var(--lh-tight);
|
||||
letter-spacing: var(--tracking-display);
|
||||
color: var(--color-ink);
|
||||
max-width: 18ch;
|
||||
overflow-wrap: anywhere;
|
||||
min-width: 0;
|
||||
}
|
||||
.hero__h span { display: block; }
|
||||
.hero__h span:last-child { color: var(--color-accent); }
|
||||
|
||||
/* ── The colossal live specimen ── */
|
||||
.specimen-live {
|
||||
margin-block-start: var(--space-md);
|
||||
display: grid;
|
||||
gap: var(--space-lg);
|
||||
}
|
||||
.specimen-live__stage {
|
||||
position: relative;
|
||||
border-block: var(--rule-hair) solid var(--color-rule);
|
||||
padding-block: var(--space-xs);
|
||||
overflow: clip;
|
||||
}
|
||||
.specimen-live__word {
|
||||
display: block;
|
||||
font-family: var(--font-display);
|
||||
font-optical-sizing: none;
|
||||
font-variation-settings: 'wght' var(--w, 320), 'wdth' var(--wd, 100), 'opsz' var(--op, 96);
|
||||
font-size: var(--text-colossal);
|
||||
line-height: 0.82;
|
||||
letter-spacing: -0.04em;
|
||||
color: var(--color-ink);
|
||||
white-space: nowrap;
|
||||
overflow-wrap: normal;
|
||||
/* off-register CMYK misprint — the kinetic, press-y signature */
|
||||
text-shadow:
|
||||
0.045em 0 0 var(--ghost-red),
|
||||
-0.045em 0.012em 0 var(--ghost-cyan);
|
||||
animation: breathe var(--dur-breathe) var(--ease-in-out) infinite alternate;
|
||||
}
|
||||
/* once the reader grabs an axis, JS sets data-grabbed and we stop breathing */
|
||||
.specimen-live[data-grabbed="true"] .specimen-live__word { animation: none; }
|
||||
|
||||
@keyframes breathe {
|
||||
from { --w: 280; --wd: 100; }
|
||||
to { --w: 600; --wd: 88; }
|
||||
}
|
||||
/* register the custom props so they can be animated */
|
||||
@property --w { syntax: "<number>"; inherits: true; initial-value: 320; }
|
||||
@property --wd { syntax: "<number>"; inherits: true; initial-value: 100; }
|
||||
@property --op { syntax: "<number>"; inherits: true; initial-value: 96; }
|
||||
|
||||
.specimen-live__panel { margin: 0; }
|
||||
.axis {
|
||||
display: grid;
|
||||
gap: var(--space-sm);
|
||||
max-width: 30rem;
|
||||
}
|
||||
.axis__row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
gap: var(--space-2xs);
|
||||
}
|
||||
.axis__label {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-sm);
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-xs);
|
||||
letter-spacing: var(--tracking-label);
|
||||
text-transform: uppercase;
|
||||
color: var(--color-neutral);
|
||||
}
|
||||
.axis__val {
|
||||
font-variant-numeric: tabular-nums;
|
||||
color: var(--color-accent);
|
||||
}
|
||||
.axis__help {
|
||||
margin-block-start: var(--space-2xs);
|
||||
font-size: var(--text-sm);
|
||||
line-height: 1.55;
|
||||
color: var(--color-muted);
|
||||
max-width: 44ch;
|
||||
}
|
||||
.axis__help em { font-style: italic; color: var(--color-ink-2); }
|
||||
|
||||
/* range / slider — thumb gets the focus state, not the track */
|
||||
.axis__range {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background: var(--color-rule-2);
|
||||
cursor: pointer;
|
||||
margin: var(--space-xs) 0;
|
||||
}
|
||||
.axis__range::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 14px;
|
||||
height: 22px;
|
||||
background: var(--color-accent);
|
||||
border: none;
|
||||
cursor: grab;
|
||||
transition: transform var(--dur-micro) var(--ease-out);
|
||||
}
|
||||
.axis__range::-moz-range-thumb {
|
||||
width: 14px;
|
||||
height: 22px;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
background: var(--color-accent);
|
||||
cursor: grab;
|
||||
}
|
||||
.axis__range:active::-webkit-slider-thumb { transform: scaleY(0.9); cursor: grabbing; }
|
||||
.axis__range:active::-moz-range-thumb { cursor: grabbing; }
|
||||
.axis__range:focus-visible { outline: none; }
|
||||
.axis__range:focus-visible::-webkit-slider-thumb {
|
||||
outline: 2px solid var(--color-focus);
|
||||
outline-offset: 3px;
|
||||
}
|
||||
.axis__range:focus-visible::-moz-range-thumb {
|
||||
outline: 2px solid var(--color-focus);
|
||||
outline-offset: 3px;
|
||||
}
|
||||
|
||||
/* ── arrow link / CTA voice (C3 typographic link) ── */
|
||||
.link-arrow {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-sm);
|
||||
width: fit-content;
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-sm);
|
||||
letter-spacing: var(--tracking-label);
|
||||
text-transform: uppercase;
|
||||
color: var(--color-ink);
|
||||
white-space: nowrap;
|
||||
padding-block: var(--space-2xs);
|
||||
}
|
||||
.link-arrow span {
|
||||
background-image: linear-gradient(var(--color-accent), var(--color-accent));
|
||||
background-size: 100% var(--rule-fine);
|
||||
background-position: 0 100%;
|
||||
background-repeat: no-repeat;
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
.link-arrow__glyph { color: var(--color-accent); transition: transform var(--dur-short) var(--ease-out); }
|
||||
@media (hover: hover) {
|
||||
.link-arrow:hover .link-arrow__glyph { transform: translateX(4px); }
|
||||
}
|
||||
.hero__cta { margin-block-start: var(--space-sm); }
|
||||
|
||||
/* ════════════════════════════════════════════════════════════
|
||||
PROOF SHEET — surface inversion to warm bone
|
||||
Every flipped surface sets BOTH background and color (gate 41).
|
||||
════════════════════════════════════════════════════════════ */
|
||||
.proof {
|
||||
background: var(--proof-paper);
|
||||
color: var(--proof-ink);
|
||||
}
|
||||
|
||||
/* plate heads — eyebrow stacked ABOVE heading, single column (gate 54) */
|
||||
.plate-head {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-sm);
|
||||
max-width: 64ch;
|
||||
margin-block-end: var(--space-2xl);
|
||||
}
|
||||
.plate-head__no {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-xs);
|
||||
letter-spacing: var(--tracking-label);
|
||||
text-transform: uppercase;
|
||||
color: var(--proof-accent);
|
||||
}
|
||||
.plate-head__title {
|
||||
font-family: var(--font-display);
|
||||
font-variation-settings: 'wght' 600, 'opsz' 72;
|
||||
font-size: var(--text-display-s);
|
||||
line-height: 1.02;
|
||||
letter-spacing: var(--tracking-tight);
|
||||
color: var(--proof-ink);
|
||||
overflow-wrap: anywhere;
|
||||
min-width: 0;
|
||||
}
|
||||
.plate-head__sub {
|
||||
max-width: var(--measure);
|
||||
font-size: var(--text-md);
|
||||
line-height: var(--lh-relaxed);
|
||||
color: var(--proof-ink-2);
|
||||
}
|
||||
.plate-head__sub em { font-style: italic; }
|
||||
|
||||
/* heading register that sits on the dark ground (Plate 02 + Index) */
|
||||
.plate-head--ink .plate-head__no { color: var(--color-accent); }
|
||||
.plate-head--ink .plate-head__title { color: var(--color-ink); }
|
||||
.plate-head--ink .plate-head__sub { color: var(--color-neutral); }
|
||||
|
||||
/* ── Weights ladder ── */
|
||||
.weights {
|
||||
display: grid;
|
||||
border-top: var(--rule-hair) solid var(--proof-rule);
|
||||
max-width: var(--page-max);
|
||||
}
|
||||
.weights__row {
|
||||
display: grid;
|
||||
grid-template-columns: 9rem minmax(0, 1fr);
|
||||
align-items: baseline;
|
||||
gap: var(--space-lg);
|
||||
padding-block: var(--space-lg);
|
||||
border-bottom: var(--rule-hair) solid var(--proof-rule);
|
||||
}
|
||||
.weights__tag {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-xs);
|
||||
letter-spacing: var(--tracking-label);
|
||||
text-transform: uppercase;
|
||||
color: var(--proof-muted);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.weights__line {
|
||||
font-family: var(--font-display);
|
||||
font-optical-sizing: none;
|
||||
font-size: clamp(1.5rem, 4.5vw, 3rem);
|
||||
line-height: 1.04;
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--proof-ink);
|
||||
overflow-wrap: anywhere;
|
||||
min-width: 0;
|
||||
transition: font-variation-settings var(--dur-long) var(--ease-out);
|
||||
}
|
||||
@media (hover: hover) {
|
||||
/* hover lifts the weight one master — demonstrates the axis live */
|
||||
.weights__row:hover .weights__line { font-variation-settings: 'wght' 800, 'opsz' 18 !important; }
|
||||
}
|
||||
|
||||
/* ── Glyph / feature spec sheet (F3) ── */
|
||||
.glyphset {
|
||||
margin-block-start: var(--space-2xl);
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr));
|
||||
gap: 0;
|
||||
border: var(--rule-hair) solid var(--proof-rule);
|
||||
max-width: var(--page-max);
|
||||
}
|
||||
.glyphset__cell {
|
||||
padding: var(--space-lg);
|
||||
border-inline-start: var(--rule-hair) solid var(--proof-rule);
|
||||
}
|
||||
.glyphset__cell:first-child { border-inline-start: none; }
|
||||
.glyphset dt {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-xs);
|
||||
letter-spacing: var(--tracking-label);
|
||||
text-transform: uppercase;
|
||||
color: var(--proof-muted);
|
||||
margin-block-end: var(--space-xs);
|
||||
}
|
||||
.glyphset dd {
|
||||
font-size: var(--text-md);
|
||||
line-height: 1.45;
|
||||
color: var(--proof-ink);
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════
|
||||
CONCRETE POETRY · the calligramme (on the dark ground)
|
||||
════════════════════════════════════════════════════════════ */
|
||||
.concrete {
|
||||
background: var(--color-paper);
|
||||
color: var(--color-ink);
|
||||
}
|
||||
.poem {
|
||||
position: relative;
|
||||
max-width: var(--page-max);
|
||||
margin-block-start: var(--space-xl);
|
||||
border: var(--rule-hair) solid var(--color-rule);
|
||||
padding: clamp(var(--space-lg), 5vw, var(--space-3xl));
|
||||
background:
|
||||
repeating-linear-gradient(
|
||||
90deg,
|
||||
transparent 0,
|
||||
transparent calc((100% - 11 * 1px) / 12),
|
||||
var(--color-paper-2) calc((100% - 11 * 1px) / 12),
|
||||
var(--color-paper-2) calc((100% - 11 * 1px) / 12 + 1px)
|
||||
);
|
||||
}
|
||||
.poem__visually-hidden,
|
||||
.poster__vis-hidden {
|
||||
position: absolute;
|
||||
width: 1px; height: 1px;
|
||||
padding: 0; margin: -1px;
|
||||
overflow: hidden; clip: rect(0 0 0 0);
|
||||
white-space: nowrap; border: 0;
|
||||
}
|
||||
.poem__field {
|
||||
position: relative;
|
||||
min-height: clamp(20rem, 46vw, 30rem);
|
||||
font-family: var(--font-display);
|
||||
font-optical-sizing: none;
|
||||
}
|
||||
/* each word placed on a 9-column × 6-row implicit grid, slanted like rain */
|
||||
.poem__w {
|
||||
position: absolute;
|
||||
left: calc(var(--c) * 10%);
|
||||
top: calc((var(--r) - 1) * 16% + 2%);
|
||||
font-variation-settings: 'wght' var(--w, 360), 'wdth' 88;
|
||||
font-size: clamp(1.1rem, 2.4vw, 2rem);
|
||||
line-height: 1;
|
||||
letter-spacing: -0.01em;
|
||||
color: var(--color-ink-2);
|
||||
transform: rotate(var(--rot, -8deg));
|
||||
transform-origin: left top;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.poem__w--pool {
|
||||
left: 50%;
|
||||
top: auto;
|
||||
bottom: 2%;
|
||||
transform: translateX(-50%);
|
||||
font-size: clamp(2.5rem, 8vw, 5.5rem);
|
||||
letter-spacing: -0.04em;
|
||||
color: var(--color-accent);
|
||||
text-shadow:
|
||||
0.03em 0 0 var(--ghost-red),
|
||||
-0.03em 0.01em 0 var(--ghost-cyan);
|
||||
}
|
||||
.poem__cap {
|
||||
position: relative;
|
||||
margin-block-start: var(--space-xl);
|
||||
padding-block-start: var(--space-md);
|
||||
border-top: var(--rule-hair) solid var(--color-rule);
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-sm) var(--space-lg);
|
||||
}
|
||||
.poem__title {
|
||||
font-family: var(--font-body);
|
||||
font-style: italic;
|
||||
font-size: var(--text-lg);
|
||||
color: var(--color-ink);
|
||||
}
|
||||
.poem__meta {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-xs);
|
||||
letter-spacing: var(--tracking-label);
|
||||
text-transform: uppercase;
|
||||
color: var(--color-muted);
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════
|
||||
BROKEN-GRID POSTER (on the proof/bone surface)
|
||||
════════════════════════════════════════════════════════════ */
|
||||
.poster {
|
||||
max-width: var(--page-max);
|
||||
display: grid;
|
||||
gap: var(--space-lg);
|
||||
}
|
||||
.poster__plate {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(12, minmax(0, 1fr));
|
||||
grid-auto-rows: min-content;
|
||||
gap: var(--space-sm) var(--space-md);
|
||||
background: var(--proof-paper-2);
|
||||
border: var(--rule-fine) solid var(--proof-ink);
|
||||
padding: clamp(var(--space-lg), 4vw, var(--space-2xl));
|
||||
overflow: clip; /* the bleed numeral is clipped by the plate edge, page stays put */
|
||||
isolation: isolate;
|
||||
}
|
||||
/* a single baseline rule the type hangs from */
|
||||
.poster__baseline {
|
||||
grid-column: 1 / -1;
|
||||
grid-row: 2;
|
||||
height: var(--rule-fine);
|
||||
background: var(--proof-accent);
|
||||
align-self: end;
|
||||
margin-block-end: -0.2em;
|
||||
z-index: var(--z-base);
|
||||
}
|
||||
.poster__kicker {
|
||||
grid-column: 1 / 7;
|
||||
grid-row: 1;
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-xs);
|
||||
letter-spacing: var(--tracking-label);
|
||||
text-transform: uppercase;
|
||||
color: var(--proof-ink-2);
|
||||
}
|
||||
.poster__title {
|
||||
grid-column: 1 / -1;
|
||||
grid-row: 2;
|
||||
display: grid;
|
||||
z-index: var(--z-raised);
|
||||
margin: 0;
|
||||
}
|
||||
.poster__word {
|
||||
font-family: var(--font-display);
|
||||
font-optical-sizing: none;
|
||||
font-variation-settings: 'wght' 800, 'wdth' 80, 'opsz' 96;
|
||||
font-size: clamp(3rem, 16vw, 9rem);
|
||||
line-height: 1.0; /* all-caps display floor — no cap-collision between stacked words (gate 55) */
|
||||
letter-spacing: -0.04em;
|
||||
color: var(--proof-ink);
|
||||
text-transform: uppercase;
|
||||
overflow-wrap: anywhere;
|
||||
min-width: 0;
|
||||
}
|
||||
/* the break: each word steps off the left margin by a different amount,
|
||||
and the middle word jumps register to red — the grid bent on purpose */
|
||||
.poster__word--a { margin-inline-start: 0; }
|
||||
.poster__word--b { margin-inline-start: 14%; color: var(--proof-accent); }
|
||||
.poster__word--c { margin-inline-start: 5%; }
|
||||
|
||||
.poster__line {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-sm);
|
||||
line-height: 1.5;
|
||||
color: var(--proof-ink-2);
|
||||
align-self: end;
|
||||
}
|
||||
.poster__line--date { grid-column: 1 / 6; grid-row: 3; }
|
||||
.poster__line--place { grid-column: 7 / 13; grid-row: 3; text-align: right; }
|
||||
|
||||
/* the ONE element allowed to cross the margin — a colossal numeral bleeding right */
|
||||
.poster__bleed {
|
||||
position: absolute;
|
||||
right: -0.12em;
|
||||
bottom: -0.28em;
|
||||
font-family: var(--font-display);
|
||||
font-optical-sizing: none;
|
||||
font-variation-settings: 'wght' 800, 'wdth' 75;
|
||||
font-size: clamp(9rem, 30vw, 22rem);
|
||||
line-height: 0.7;
|
||||
letter-spacing: -0.06em;
|
||||
color: transparent;
|
||||
-webkit-text-stroke: 2px var(--proof-rule-2);
|
||||
text-stroke: 2px var(--proof-rule-2);
|
||||
z-index: var(--z-base);
|
||||
pointer-events: none;
|
||||
font-variant-numeric: lining-nums;
|
||||
}
|
||||
.poster__cap {
|
||||
max-width: var(--measure);
|
||||
font-size: var(--text-sm);
|
||||
line-height: 1.6;
|
||||
color: var(--proof-muted);
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════
|
||||
SELECTED WORK · ledger (on the dark ground)
|
||||
════════════════════════════════════════════════════════════ */
|
||||
.work {
|
||||
background: var(--color-paper);
|
||||
color: var(--color-ink);
|
||||
}
|
||||
.ledger {
|
||||
max-width: var(--page-max);
|
||||
border-top: var(--rule-hair) solid var(--color-rule);
|
||||
}
|
||||
.ledger__row {
|
||||
display: grid;
|
||||
grid-template-columns: 5rem minmax(0, 1.1fr) minmax(0, 1.4fr);
|
||||
align-items: baseline;
|
||||
gap: var(--space-md) var(--space-lg);
|
||||
padding-block: var(--space-lg);
|
||||
border-bottom: var(--rule-hair) solid var(--color-rule);
|
||||
transition: background-color var(--dur-short) var(--ease-out);
|
||||
}
|
||||
@media (hover: hover) {
|
||||
.ledger__row:hover { background: var(--color-paper-2); }
|
||||
.ledger__row:hover .ledger__name { color: var(--color-accent); }
|
||||
}
|
||||
.ledger__year {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-sm);
|
||||
color: var(--color-muted);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.ledger__name {
|
||||
font-family: var(--font-display);
|
||||
font-variation-settings: 'wght' 520, 'opsz' 28;
|
||||
font-size: var(--text-xl);
|
||||
line-height: 1.1;
|
||||
letter-spacing: var(--tracking-tight);
|
||||
color: var(--color-ink);
|
||||
overflow-wrap: anywhere;
|
||||
min-width: 0;
|
||||
transition: color var(--dur-short) var(--ease-out);
|
||||
}
|
||||
.ledger__kind {
|
||||
font-size: var(--text-md);
|
||||
line-height: 1.45;
|
||||
color: var(--color-neutral);
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════
|
||||
THE STUDIO · close + enquire form
|
||||
════════════════════════════════════════════════════════════ */
|
||||
.studio {
|
||||
background: var(--color-paper);
|
||||
color: var(--color-ink);
|
||||
padding-block: var(--section-gap) var(--space-4xl);
|
||||
border-top: var(--rule-hair) solid var(--color-rule);
|
||||
max-width: var(--page-max);
|
||||
}
|
||||
.studio__h {
|
||||
font-family: var(--font-display);
|
||||
font-variation-settings: 'wght' 580, 'opsz' 96;
|
||||
font-size: var(--text-display-s);
|
||||
line-height: 1.02;
|
||||
letter-spacing: var(--tracking-display);
|
||||
color: var(--color-ink);
|
||||
max-width: 16ch;
|
||||
margin-block-end: var(--space-2xl);
|
||||
overflow-wrap: anywhere;
|
||||
min-width: 0;
|
||||
}
|
||||
.studio__cols {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr);
|
||||
gap: var(--space-2xl);
|
||||
align-items: start;
|
||||
}
|
||||
.studio__lede {
|
||||
font-size: var(--text-lg);
|
||||
line-height: var(--lh-relaxed);
|
||||
color: var(--color-ink-2);
|
||||
max-width: var(--measure);
|
||||
}
|
||||
|
||||
/* ── Enquire (inline form-as-CTA · 8 states) ── */
|
||||
.enquire { display: grid; gap: var(--space-xs); align-content: start; }
|
||||
.enquire__label {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-xs);
|
||||
letter-spacing: var(--tracking-label);
|
||||
text-transform: uppercase;
|
||||
color: var(--color-neutral);
|
||||
}
|
||||
.enquire__row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
.enquire__input {
|
||||
flex: 1 1 14rem;
|
||||
min-width: 0;
|
||||
height: 3rem; /* = button height */
|
||||
padding-inline: var(--space-md);
|
||||
padding-inline-end: 2rem; /* reserved right-edge slot */
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-sm);
|
||||
color: var(--color-ink);
|
||||
background: var(--color-paper-2);
|
||||
border: 1px solid var(--color-rule-2);
|
||||
outline: 2px solid transparent; /* reserved focus slot — no shift */
|
||||
outline-offset: 1px;
|
||||
transition: background-color var(--dur-short) var(--ease-out),
|
||||
border-color var(--dur-short) var(--ease-out);
|
||||
}
|
||||
.enquire__input::placeholder { color: var(--color-muted); }
|
||||
@media (hover: hover) {
|
||||
.enquire__input:hover { background: var(--color-paper-3); }
|
||||
}
|
||||
.enquire__input:focus-visible {
|
||||
outline: 2px solid var(--color-focus);
|
||||
border-color: var(--color-ink-2);
|
||||
background: var(--color-paper-2);
|
||||
}
|
||||
.enquire__input[aria-invalid="true"] {
|
||||
border-color: var(--color-accent);
|
||||
}
|
||||
.enquire__input:disabled {
|
||||
opacity: 0.55;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.btn {
|
||||
height: 3rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--space-xs);
|
||||
padding-inline: var(--space-lg);
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-sm);
|
||||
letter-spacing: var(--tracking-label);
|
||||
text-transform: uppercase;
|
||||
line-height: 1;
|
||||
color: var(--color-accent-ink);
|
||||
background: var(--color-accent);
|
||||
border: 1px solid var(--color-accent);
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
transition: transform var(--dur-micro) var(--ease-out),
|
||||
background-color var(--dur-short) var(--ease-out);
|
||||
}
|
||||
@media (hover: hover) {
|
||||
.btn:hover { background: var(--color-focus); border-color: var(--color-focus); }
|
||||
}
|
||||
.btn:active { transform: translateY(1px); }
|
||||
/* the CTA fill is red, so its focus ring is bone-ink (≥APCA Lc 90 on the red
|
||||
fill, ≥7:1 on the dark paper) — never a red-on-red ring. */
|
||||
.btn:focus-visible { outline: 2px solid var(--color-ink); outline-offset: 3px; }
|
||||
.btn:disabled, .btn[aria-disabled="true"] {
|
||||
opacity: 0.55;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
.btn[data-state="loading"] { cursor: progress; }
|
||||
.btn[data-state="loading"] .btn__label { opacity: 0.6; }
|
||||
.btn[data-state="success"] {
|
||||
background: transparent;
|
||||
color: var(--color-ink);
|
||||
border-color: var(--color-rule-2);
|
||||
}
|
||||
|
||||
.enquire__help {
|
||||
min-height: 1.4lh; /* reserved so error doesn't shift the page */
|
||||
font-size: var(--text-sm);
|
||||
line-height: 1.4;
|
||||
color: var(--color-muted);
|
||||
}
|
||||
.enquire__help[data-tone="error"] { color: var(--color-focus); }
|
||||
.enquire__help[data-tone="success"] { color: var(--color-ink-2); }
|
||||
|
||||
/* ════════════════════════════════════════════════════════════
|
||||
Ft1 · MAST-HEADED FOOTER
|
||||
════════════════════════════════════════════════════════════ */
|
||||
.foot {
|
||||
background: var(--color-paper);
|
||||
color: var(--color-ink);
|
||||
border-top: var(--rule-heavy) solid var(--color-accent);
|
||||
padding: var(--space-2xl) var(--page-gutter) var(--space-3xl);
|
||||
display: grid;
|
||||
gap: var(--space-sm);
|
||||
max-width: var(--page-max);
|
||||
}
|
||||
.foot__mark {
|
||||
font-family: var(--font-display);
|
||||
font-variation-settings: 'wght' 640, 'opsz' 60, 'wdth' 90;
|
||||
font-size: clamp(2.5rem, 9vw, 5.5rem);
|
||||
line-height: 0.92;
|
||||
letter-spacing: -0.03em;
|
||||
color: var(--color-ink);
|
||||
overflow-wrap: anywhere;
|
||||
min-width: 0;
|
||||
}
|
||||
.foot__tag {
|
||||
max-width: 48ch;
|
||||
font-size: var(--text-md);
|
||||
line-height: var(--lh-relaxed);
|
||||
color: var(--color-neutral);
|
||||
margin-block-start: var(--space-xs);
|
||||
}
|
||||
.foot__set {
|
||||
max-width: 56ch;
|
||||
margin-block-start: var(--space-md);
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-xs);
|
||||
line-height: 1.7;
|
||||
letter-spacing: 0.02em;
|
||||
color: var(--color-muted);
|
||||
}
|
||||
.foot__links {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: var(--space-sm);
|
||||
margin-block-start: var(--space-xs);
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-xs);
|
||||
letter-spacing: var(--tracking-label);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.foot__links a {
|
||||
color: var(--color-ink-2);
|
||||
white-space: nowrap;
|
||||
background-image: linear-gradient(var(--color-accent), var(--color-accent));
|
||||
background-size: 0% var(--rule-fine);
|
||||
background-position: 0 100%;
|
||||
background-repeat: no-repeat;
|
||||
padding-bottom: 2px;
|
||||
transition: background-size var(--dur-short) var(--ease-out), color var(--dur-short) var(--ease-out);
|
||||
}
|
||||
@media (hover: hover) {
|
||||
.foot__links a:hover { color: var(--color-ink); background-size: 100% var(--rule-fine); }
|
||||
}
|
||||
.foot__sep { color: var(--color-rule-2); }
|
||||
|
||||
/* ════════════════════════════════════════════════════════════
|
||||
MOTION · one orchestrated entrance (reveal), reduced-motion safe
|
||||
════════════════════════════════════════════════════════════ */
|
||||
.reveal {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
animation: reveal var(--dur-reveal) var(--ease-out) forwards;
|
||||
animation-delay: calc(var(--i, 0) * 70ms);
|
||||
}
|
||||
@keyframes reveal { to { opacity: 1; transform: none; } }
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*, *::before, *::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
.reveal { opacity: 1; transform: none; animation: none; }
|
||||
.specimen-live__word { animation: none; }
|
||||
.weights__line { transition: none; }
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════
|
||||
RESPONSIVE
|
||||
════════════════════════════════════════════════════════════ */
|
||||
|
||||
/* tablet / small laptop */
|
||||
@media (max-width: 60rem) {
|
||||
.studio__cols { grid-template-columns: minmax(0, 1fr); gap: var(--space-2xl); }
|
||||
.ledger__row { grid-template-columns: 4rem minmax(0, 1fr); }
|
||||
.ledger__kind { grid-column: 2; }
|
||||
.poster__word { font-size: clamp(2.75rem, 17vw, 7rem); }
|
||||
}
|
||||
|
||||
/* phone — the rail collapses to a slim top bar; sections go single column */
|
||||
@media (max-width: 48rem) {
|
||||
.rail {
|
||||
inset-block: auto;
|
||||
inset-block-start: 0;
|
||||
inset-inline: 0;
|
||||
width: auto;
|
||||
height: var(--rail-bar, 3rem);
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
padding: 0 var(--page-gutter);
|
||||
border-inline-end: none;
|
||||
border-bottom: var(--rule-hair) solid var(--color-rule);
|
||||
}
|
||||
.rail__mark { writing-mode: horizontal-tb; transform: none; gap: var(--space-sm); }
|
||||
.rail__name { font-size: var(--text-sm); }
|
||||
.rail__role { display: none; }
|
||||
.rail__dots { flex-direction: row; gap: var(--space-sm); }
|
||||
.rail__dots a::after { display: none; } /* labels would overflow the bar */
|
||||
.rail__year { writing-mode: horizontal-tb; }
|
||||
|
||||
.shell { margin-inline-start: 0; padding-block-start: 3rem; }
|
||||
|
||||
.hero { min-height: auto; padding-block: var(--space-3xl) var(--space-3xl); }
|
||||
|
||||
.weights__row {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
gap: var(--space-2xs);
|
||||
padding-block: var(--space-md);
|
||||
}
|
||||
.poster__line--place { grid-column: 1 / -1; text-align: left; }
|
||||
.poster__line--date { grid-column: 1 / -1; }
|
||||
}
|
||||
|
||||
@media (max-width: 30rem) {
|
||||
.hero__h { font-size: var(--text-3xl); }
|
||||
.rail__dots a { width: 1.25rem; }
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/* Hallmark · tokens — gen-13
|
||||
* route: custom (tuned) · vibe: "ink-black press, off-register, broken-grid, kinetic"
|
||||
* paper: oklch(13% 0.014 25) · accent: oklch(60% 0.21 25) signal-red
|
||||
* display: Bricolage Grotesque (variable) · body: Fraunces (variable) · outlier: JetBrains Mono
|
||||
* axes: dark / display-heavy / warm
|
||||
*
|
||||
* One palette, two surfaces: the page runs on an ink-black press ground;
|
||||
* the "proof sheet" sections invert to a warm bone paper so the specimens
|
||||
* read like a printed proof pulled off the press. Both carry the warm-red tilt.
|
||||
*/
|
||||
|
||||
:root {
|
||||
/* ── Press ground — the dark default surface ──────────────── */
|
||||
--color-paper: oklch(13% 0.014 25); /* ink-black, warm-red tilt — never #000 */
|
||||
--color-paper-2: oklch(17% 0.018 25); /* elevated panel */
|
||||
--color-paper-3: oklch(22% 0.022 24); /* deeper hover / plate */
|
||||
--color-rule: oklch(32% 0.020 24); /* hairline divider */
|
||||
--color-rule-2: oklch(44% 0.024 22); /* heavier rule */
|
||||
--color-muted: oklch(64% 0.020 26); /* de-emphasised text */
|
||||
--color-neutral: oklch(74% 0.016 28); /* mid text */
|
||||
--color-ink-2: oklch(86% 0.012 30); /* secondary headings/body */
|
||||
--color-ink: oklch(95% 0.010 32); /* primary text — warm bone */
|
||||
--color-accent: oklch(60% 0.21 25); /* signal red — the one chromatic note */
|
||||
--color-accent-2: oklch(78% 0.15 85); /* off-register amber ghost (misprint only) */
|
||||
--color-accent-ink: oklch(97% 0.012 30); /* bone text on red fill — APCA-checked */
|
||||
--color-focus: oklch(72% 0.19 25); /* brighter red, ≥3:1 on ground + on red */
|
||||
|
||||
/* Off-register misprint ghosts — translucent, decorative only */
|
||||
--ghost-cyan: oklch(70% 0.16 220 / 0.42);
|
||||
--ghost-red: oklch(62% 0.22 25 / 0.40);
|
||||
|
||||
/* ── Proof sheet — warm bone surface for inverted sections ── */
|
||||
--proof-paper: oklch(93% 0.020 70); /* warm bone proof stock */
|
||||
--proof-paper-2: oklch(89% 0.024 68);
|
||||
--proof-rule: oklch(74% 0.024 60);
|
||||
--proof-rule-2: oklch(48% 0.020 45);
|
||||
--proof-muted: oklch(46% 0.018 40);
|
||||
--proof-ink-2: oklch(28% 0.020 35);
|
||||
--proof-ink: oklch(17% 0.024 32); /* near-black warm ink */
|
||||
--proof-accent: oklch(52% 0.20 25); /* red reads darker on bone for contrast */
|
||||
|
||||
/* ── Type stack — 2+1: display + body + mono outlier ──────── */
|
||||
--font-display: "Bricolage Grotesque", ui-sans-serif, system-ui, sans-serif;
|
||||
--font-body: "Fraunces", ui-serif, Georgia, serif;
|
||||
--font-mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;
|
||||
|
||||
/* ── Type scale — major third (1.25), clamped display ─────── */
|
||||
--text-xs: 0.75rem;
|
||||
--text-sm: 0.8125rem;
|
||||
--text-base: 1rem;
|
||||
--text-md: 1.125rem;
|
||||
--text-lg: 1.375rem;
|
||||
--text-xl: 1.75rem;
|
||||
--text-2xl: 2.25rem;
|
||||
--text-3xl: 3rem;
|
||||
--text-display: clamp(3rem, 7vw + 0.5rem, 6rem);
|
||||
--text-display-s: clamp(2.25rem, 4vw + 0.75rem, 3.75rem);
|
||||
/* the colossal living specimen — single short word, may exceed display cap */
|
||||
--text-colossal: clamp(3.75rem, 16vw, 12.5rem);
|
||||
|
||||
--lh-tight: 1.0; /* display floor (no all-caps collision risk: mixed case) */
|
||||
--lh-snug: 1.12;
|
||||
--lh-normal: 1.55;
|
||||
--lh-relaxed: 1.66;
|
||||
|
||||
--tracking-display: -0.03em;
|
||||
--tracking-tight: -0.015em;
|
||||
--tracking-normal: 0;
|
||||
--tracking-label: 0.16em; /* mono specimen tags */
|
||||
--tracking-micro: 0.26em; /* rotated rail wordmark */
|
||||
|
||||
/* ── 4pt spacing scale ────────────────────────────────────── */
|
||||
--space-3xs: 0.125rem;
|
||||
--space-2xs: 0.25rem;
|
||||
--space-xs: 0.5rem;
|
||||
--space-sm: 0.75rem;
|
||||
--space-md: 1rem;
|
||||
--space-lg: 1.5rem;
|
||||
--space-xl: 2.5rem;
|
||||
--space-2xl: 4rem;
|
||||
--space-3xl: 6.5rem;
|
||||
--space-4xl: 10rem;
|
||||
--space-5xl: 13rem;
|
||||
|
||||
--section-gap: 6.5rem;
|
||||
--section-head-gap: 1.5rem;
|
||||
|
||||
/* ── Layout shell ─────────────────────────────────────────── */
|
||||
--rail-width: 3.25rem; /* the fixed side-rail */
|
||||
--page-max: 78rem;
|
||||
--page-gutter: clamp(1.25rem, 4vw, 3.5rem);
|
||||
--measure: 62ch;
|
||||
--rule-hair: 1px;
|
||||
--rule-fine: 2px;
|
||||
--rule-heavy: 4px;
|
||||
|
||||
/* Square edges — this is a press, not a soft app */
|
||||
--radius-card: 0;
|
||||
--radius-pill: 0;
|
||||
--radius-input: 0;
|
||||
|
||||
/* ── Motion ───────────────────────────────────────────────── */
|
||||
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
|
||||
--ease-in: cubic-bezier(0.7, 0, 0.84, 0);
|
||||
--ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
|
||||
--dur-micro: 120ms;
|
||||
--dur-short: 220ms;
|
||||
--dur-long: 420ms;
|
||||
--dur-reveal: 560ms;
|
||||
--dur-breathe: 9s; /* ambient variable-axis loop on the living specimen */
|
||||
|
||||
/* ── Z scale ──────────────────────────────────────────────── */
|
||||
--z-base: 1;
|
||||
--z-raised: 10;
|
||||
--z-dropdown: 100;
|
||||
--z-sticky: 200;
|
||||
--z-sticky-nav: 300;
|
||||
--z-modal: 400;
|
||||
--z-toast: 500;
|
||||
--z-tooltip: 600;
|
||||
}
|
||||
@@ -0,0 +1,352 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-theme="riso" class="no-js">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>OFF-REGISTER · A Risograph Print Fair · Pulp Yard, 18–19 Oct 2026</title>
|
||||
<meta name="description" content="OFF-REGISTER is a two-day risograph print fair at the Pulp Yard, Govanhill. Thirty-four studios, eight presses, soy ink everywhere. Free entry, Saturday and Sunday." />
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css2?family=Public+Sans:wght@400;600;800;900&family=Newsreader:ital,opsz,wght@0,6..72,400;0,6..72,500;1,6..72,400&family=Space+Mono:wght@400;700&display=swap" />
|
||||
|
||||
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' fill='%23f0cdbf'/%3E%3Ccircle cx='15' cy='17' r='9' fill='%23f0c33a'/%3E%3Ccircle cx='13' cy='15' r='9' fill='none' stroke='%231aa3c2' stroke-width='3'/%3E%3Ccircle cx='18' cy='19' r='6' fill='none' stroke='%23e8497a' stroke-width='3'/%3E%3C/svg%3E" />
|
||||
<link rel="stylesheet" href="./tokens.css" />
|
||||
<link rel="stylesheet" href="./styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<a class="skip-link" href="#catalogue">Skip to the exhibitor list</a>
|
||||
|
||||
<!-- ░░ N6 · Newspaper masthead ░░ -->
|
||||
<header class="masthead" role="banner">
|
||||
<p class="masthead__issue">Edition 04 · Govanhill · 18–19 October 2026 · Free entry</p>
|
||||
<a class="masthead__name riso-mis" href="#top" data-text="OFF-REGISTER" aria-label="Off-Register, home">OFF-REGISTER</a>
|
||||
<nav class="masthead__nav" aria-label="Primary">
|
||||
<ul>
|
||||
<li><a href="#catalogue">Exhibitors</a></li>
|
||||
<li><a href="#programme">Programme</a></li>
|
||||
<li><a href="#workshops">Workshops</a></li>
|
||||
<li><a href="#visit">Visit</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<hr class="masthead__rule" aria-hidden="true" />
|
||||
</header>
|
||||
|
||||
<main id="top">
|
||||
|
||||
<!-- ░░ HERO · Catalogue inventory header + Tier-A CSS riso mark ░░ -->
|
||||
<section class="hero" aria-labelledby="hero-title">
|
||||
<div class="hero__copy">
|
||||
<p class="kicker">A risograph print fair · Two days · One drum at a time</p>
|
||||
<h1 id="hero-title" class="hero__title">
|
||||
<span class="riso-mis" data-text="Thirty-four">Thirty-four</span>
|
||||
studios. Eight presses. Soy ink everywhere.
|
||||
</h1>
|
||||
<p class="hero__lede">
|
||||
design like print: warm, off-register, intentional. For one weekend the
|
||||
Pulp Yard fills with spot colour — fluorescent pink that won’t sit
|
||||
still, teal that bleeds a hair past its edge, paper you can smell. Come
|
||||
turn the drum.
|
||||
</p>
|
||||
<div class="hero__actions">
|
||||
<a class="btn btn--solid" href="#visit">Plan your visit</a>
|
||||
<a class="btn btn--text" href="#catalogue">Read the exhibitor list <span aria-hidden="true">→</span></a>
|
||||
</div>
|
||||
<dl class="hero__facts">
|
||||
<div class="fact"><dt>Where</dt><dd>The Pulp Yard, 31 Calder St, Govanhill</dd></div>
|
||||
<div class="fact"><dt>When</dt><dd>Sat & Sun, 11:00–18:00</dd></div>
|
||||
<div class="fact"><dt>Entry</dt><dd>Free — first 200 get a printed map</dd></div>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<!-- Pure-CSS risograph centrepiece: three overprinted ink layers,
|
||||
deliberately misregistered, multiply-blended, with paper grain. -->
|
||||
<figure class="hero__art" aria-label="A risographed poster mark: overlapping cyan, yellow and pink shapes printed slightly out of register.">
|
||||
<div class="riso-plate">
|
||||
<span class="riso-ink riso-ink--yellow"></span>
|
||||
<span class="riso-ink riso-ink--cyan"></span>
|
||||
<span class="riso-ink riso-ink--pink"></span>
|
||||
<span class="riso-grain" aria-hidden="true"></span>
|
||||
</div>
|
||||
<figcaption class="riso-plate__cap">Plate 04 · 3 colours · 280 gsm</figcaption>
|
||||
</figure>
|
||||
</section>
|
||||
|
||||
<!-- ░░ EXHIBITOR CATALOGUE · the core — uniform filterable grid ░░ -->
|
||||
<section id="catalogue" class="catalogue" aria-labelledby="cat-title">
|
||||
<div class="band">
|
||||
<h2 id="cat-title" class="band__title">The exhibitor list</h2>
|
||||
<p class="band__count">Thirty-four studios · Hall A & the Mezzanine · updated 2 Oct</p>
|
||||
</div>
|
||||
|
||||
<div class="filters" role="group" aria-label="Filter exhibitors by medium">
|
||||
<button type="button" class="chip is-active" data-filter="all" aria-pressed="true">All <span class="chip__n">34</span></button>
|
||||
<button type="button" class="chip" data-filter="zine" aria-pressed="false">Zines <span class="chip__n">14</span></button>
|
||||
<button type="button" class="chip" data-filter="poster" aria-pressed="false">Posters <span class="chip__n">9</span></button>
|
||||
<button type="button" class="chip" data-filter="comic" aria-pressed="false">Comics <span class="chip__n">7</span></button>
|
||||
<button type="button" class="chip" data-filter="press" aria-pressed="false">Working presses <span class="chip__n">4</span></button>
|
||||
</div>
|
||||
<p class="filters__status" aria-live="polite" data-empty="No studios match that medium yet.">Showing all 34 studios.</p>
|
||||
|
||||
<ul class="grid" role="list">
|
||||
<li class="card" data-medium="press">
|
||||
<a class="card__media" href="#visit" aria-label="Brae & Drum, working two-colour press, stand A1">
|
||||
<span class="print print--1" aria-hidden="true"></span>
|
||||
</a>
|
||||
<div class="card__meta">
|
||||
<p class="card__tag">Working press · Stand A1</p>
|
||||
<h3 class="card__name">Brae & Drum</h3>
|
||||
<p class="card__line">A live two-colour MZ. Pull a print, keep it wet.</p>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="card" data-medium="zine">
|
||||
<a class="card__media" href="#visit" aria-label="Saltmarket Editions, zines, stand A4">
|
||||
<span class="print print--2" aria-hidden="true"></span>
|
||||
</a>
|
||||
<div class="card__meta">
|
||||
<p class="card__tag">Zines · Stand A4</p>
|
||||
<h3 class="card__name">Saltmarket Editions</h3>
|
||||
<p class="card__line">Perfect-bound photo zines, two spot colours, no black.</p>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="card" data-medium="poster">
|
||||
<a class="card__media" href="#visit" aria-label="Mono No Aware Press, posters, stand A6">
|
||||
<span class="print print--3" aria-hidden="true"></span>
|
||||
</a>
|
||||
<div class="card__meta">
|
||||
<p class="card__tag">Posters · Stand A6</p>
|
||||
<h3 class="card__name">Mono No Aware Press</h3>
|
||||
<p class="card__line">A2 gig posters. Fluoro pink that glows under the strip lights.</p>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="card" data-medium="comic">
|
||||
<a class="card__media" href="#visit" aria-label="Tenement Comics, comics, stand A9">
|
||||
<span class="print print--4" aria-hidden="true"></span>
|
||||
</a>
|
||||
<div class="card__meta">
|
||||
<p class="card__tag">Comics · Stand A9</p>
|
||||
<h3 class="card__name">Tenement Comics</h3>
|
||||
<p class="card__line">Riso-printed shorts about the close, the bin store, the No. 6 bus.</p>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="card" data-medium="zine">
|
||||
<a class="card__media" href="#visit" aria-label="Verdigris & Co, zines, stand A11">
|
||||
<span class="print print--5" aria-hidden="true"></span>
|
||||
</a>
|
||||
<div class="card__meta">
|
||||
<p class="card__tag">Zines · Stand A11</p>
|
||||
<h3 class="card__name">Verdigris & Co</h3>
|
||||
<p class="card__line">Botanical risographs on grey speckle stock. Teal on teal.</p>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="card" data-medium="poster">
|
||||
<a class="card__media" href="#visit" aria-label="Halftone Social Club, posters, stand A13">
|
||||
<span class="print print--6" aria-hidden="true"></span>
|
||||
</a>
|
||||
<div class="card__meta">
|
||||
<p class="card__tag">Posters · Stand A13</p>
|
||||
<h3 class="card__name">Halftone Social Club</h3>
|
||||
<p class="card__line">Big dot screens, on purpose. Bring a loupe.</p>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="card" data-medium="comic">
|
||||
<a class="card__media" href="#visit" aria-label="Closemouth Press, comics, stand M2">
|
||||
<span class="print print--7" aria-hidden="true"></span>
|
||||
</a>
|
||||
<div class="card__meta">
|
||||
<p class="card__tag">Comics · Mezzanine M2</p>
|
||||
<h3 class="card__name">Closemouth Press</h3>
|
||||
<p class="card__line">A risograph graphic novel, four years in, finally bound.</p>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="card" data-medium="press">
|
||||
<a class="card__media" href="#visit" aria-label="The Spirit Master, working press, stand M5">
|
||||
<span class="print print--8" aria-hidden="true"></span>
|
||||
</a>
|
||||
<div class="card__meta">
|
||||
<p class="card__tag">Working press · Mezzanine M5</p>
|
||||
<h3 class="card__name">The Spirit Master</h3>
|
||||
<p class="card__line">We cut a master from your drawing while you wait. Bring a felt tip.</p>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="card" data-medium="zine">
|
||||
<a class="card__media" href="#visit" aria-label="Riso Riot, zines, stand M7">
|
||||
<span class="print print--9" aria-hidden="true"></span>
|
||||
</a>
|
||||
<div class="card__meta">
|
||||
<p class="card__tag">Zines · Mezzanine M7</p>
|
||||
<h3 class="card__name">Riso Riot</h3>
|
||||
<p class="card__line">A queer riso anthology, eighty pages, sold by donation.</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p class="catalogue__more">
|
||||
… and twenty-five more, from <em>Govan Letterpress</em> to <em>Fluoro Bakery</em>.
|
||||
The full A–Z and the floor plan go up the week before.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- ░░ PROGRAMME · F3 tabular spec sheet (genuinely ordinal — a timetable) ░░ -->
|
||||
<section id="programme" class="programme" aria-labelledby="prog-title">
|
||||
<div class="band band--invert">
|
||||
<h2 id="prog-title" class="band__title">Two days, hour by hour</h2>
|
||||
<p class="band__count">Talks on the Mezzanine · printing in Hall A · all free, drop in</p>
|
||||
</div>
|
||||
|
||||
<div class="prog__day">
|
||||
<h3 class="prog__heading"><span class="riso-mis" data-text="Saturday">Saturday</span> · 18 Oct</h3>
|
||||
<dl class="timetable">
|
||||
<div class="slot">
|
||||
<dt class="slot__time"><time datetime="2026-10-18T11:00">11:00</time></dt>
|
||||
<dd class="slot__body"><span class="slot__title">Doors & first pull</span><span class="slot__where">Hall A</span></dd>
|
||||
</div>
|
||||
<div class="slot">
|
||||
<dt class="slot__time"><time datetime="2026-10-18T12:30">12:30</time></dt>
|
||||
<dd class="slot__body"><span class="slot__title">Why pink moves: a talk on misregistration</span><span class="slot__where">Mezzanine</span></dd>
|
||||
</div>
|
||||
<div class="slot">
|
||||
<dt class="slot__time"><time datetime="2026-10-18T14:00">14:00</time></dt>
|
||||
<dd class="slot__body"><span class="slot__title">Bind-your-own, with Closemouth Press</span><span class="slot__where">Hall A · table 3</span></dd>
|
||||
</div>
|
||||
<div class="slot">
|
||||
<dt class="slot__time"><time datetime="2026-10-18T16:30">16:30</time></dt>
|
||||
<dd class="slot__body"><span class="slot__title">Trade & swap: bring a print, take a print</span><span class="slot__where">The yard</span></dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div class="prog__day">
|
||||
<h3 class="prog__heading"><span class="riso-mis" data-text="Sunday">Sunday</span> · 19 Oct</h3>
|
||||
<dl class="timetable">
|
||||
<div class="slot">
|
||||
<dt class="slot__time"><time datetime="2026-10-19T11:00">11:00</time></dt>
|
||||
<dd class="slot__body"><span class="slot__title">Doors & slow coffee</span><span class="slot__where">The yard</span></dd>
|
||||
</div>
|
||||
<div class="slot">
|
||||
<dt class="slot__time"><time datetime="2026-10-19T13:00">13:00</time></dt>
|
||||
<dd class="slot__body"><span class="slot__title">Soy ink & paper: what actually composts</span><span class="slot__where">Mezzanine</span></dd>
|
||||
</div>
|
||||
<div class="slot">
|
||||
<dt class="slot__time"><time datetime="2026-10-19T15:00">15:00</time></dt>
|
||||
<dd class="slot__body"><span class="slot__title">Kids’ print hour — one colour, two hands</span><span class="slot__where">Hall A · table 1</span></dd>
|
||||
</div>
|
||||
<div class="slot">
|
||||
<dt class="slot__time"><time datetime="2026-10-19T17:30">17:30</time></dt>
|
||||
<dd class="slot__body"><span class="slot__title">Last drum spins & pack-down</span><span class="slot__where">Hall A</span></dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ░░ WORKSHOPS · three honest cards, varied — not an icon-tile grid ░░ -->
|
||||
<section id="workshops" class="workshops" aria-labelledby="work-title">
|
||||
<div class="band">
|
||||
<h2 id="work-title" class="band__title">Get your hands inky</h2>
|
||||
<p class="band__count">Small groups · materials included · book at the door</p>
|
||||
</div>
|
||||
|
||||
<ul class="work-list" role="list">
|
||||
<li class="work">
|
||||
<p class="work__when">Sat 13:00 · 90 min · £18</p>
|
||||
<h3 class="work__name">Make a one-colour mini-zine</h3>
|
||||
<p class="work__body">
|
||||
Draw it, fold it, run it through the press, staple it. You leave with
|
||||
twenty copies of an eight-page zine and ink under at least one fingernail.
|
||||
No experience — we mean it.
|
||||
</p>
|
||||
<p class="work__cap">8 places · ages 12+</p>
|
||||
</li>
|
||||
<li class="work">
|
||||
<p class="work__when">Sat 15:30 · 2 hrs · £26</p>
|
||||
<h3 class="work__name">Two-colour overprint & the art of being slightly wrong</h3>
|
||||
<p class="work__body">
|
||||
The whole point of riso is the misregister. We’ll layer teal over
|
||||
yellow, shift the paper a few millimetres on purpose, and chase the green
|
||||
you can only get by accident. Bring a drawing or use one of ours.
|
||||
</p>
|
||||
<p class="work__cap">6 places · ages 16+</p>
|
||||
</li>
|
||||
<li class="work">
|
||||
<p class="work__when">Sun 15:00 · 60 min · free</p>
|
||||
<h3 class="work__name">Kids’ print hour</h3>
|
||||
<p class="work__body">
|
||||
One colour, big shapes, no precious objects. Little ones cut a stencil
|
||||
and pull their own poster to take home. Aprons supplied; the floor is
|
||||
already a mess.
|
||||
</p>
|
||||
<p class="work__cap">Drop in · under 12 with an adult</p>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<!-- ░░ VISIT · C2 inline form CTA + location detail ░░ -->
|
||||
<section id="visit" class="visit" aria-labelledby="visit-title">
|
||||
<div class="visit__lede">
|
||||
<h2 id="visit-title" class="visit__title">Getting there is the easy part</h2>
|
||||
<p class="visit__body">
|
||||
Free entry, both days. No ticket needed — but leave your email and we’ll
|
||||
send the floor plan, the full A–Z of studios, and a heads-up when workshop
|
||||
places open. One message, the week before. Nothing after.
|
||||
</p>
|
||||
|
||||
<form class="signup" novalidate>
|
||||
<label class="signup__label" for="email">Email address</label>
|
||||
<div class="signup__row">
|
||||
<input class="signup__input" type="email" id="email" name="email"
|
||||
inputmode="email" autocomplete="email"
|
||||
placeholder="you@studio.co" required
|
||||
aria-describedby="signup-help" />
|
||||
<button class="btn btn--solid signup__submit" type="submit">Send me the map</button>
|
||||
</div>
|
||||
<p class="signup__help" id="signup-help">We print the map on the same presses. It’s pink.</p>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<dl class="visit__info">
|
||||
<div class="info">
|
||||
<dt>Address</dt>
|
||||
<dd>The Pulp Yard<br />31 Calder Street<br />Govanhill, Glasgow G42 7RA</dd>
|
||||
</div>
|
||||
<div class="info">
|
||||
<dt>Getting in</dt>
|
||||
<dd>Level access throughout. The Mezzanine has a lift. Guide dogs welcome; the presses are loud.</dd>
|
||||
</div>
|
||||
<div class="info">
|
||||
<dt>Travel</dt>
|
||||
<dd>4 min from Queen’s Park station. Bus 6, 38, 75. Bike racks in the yard.</dd>
|
||||
</div>
|
||||
<div class="info">
|
||||
<dt>Ask us</dt>
|
||||
<dd><a href="mailto:hello@offregister.print">hello@offregister.print</a></dd>
|
||||
</div>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
<!-- ░░ Ft4 · Dense colophon ░░ -->
|
||||
<footer class="colophon" role="contentinfo">
|
||||
<p class="colophon__body">
|
||||
<span class="colophon__mark riso-mis" data-text="OFF-REGISTER">OFF-REGISTER</span>
|
||||
— a risograph print fair, run by volunteers from the Govanhill print room since 2022.
|
||||
Edition 04 · 18–19 October 2026 · The Pulp Yard, 31 Calder Street, Glasgow G42 7RA.
|
||||
Set in Public Sans, Newsreader and Space Mono. Every mark on this page is hand-built in CSS —
|
||||
no photographs, printed on screen at 72 dpi. Soy ink, recycled stock, no black plate.
|
||||
Programme subject to which drums survive the weekend. Free entry, all welcome.
|
||||
© 2026 the fair & its makers.
|
||||
</p>
|
||||
</footer>
|
||||
|
||||
<script src="./script.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,128 @@
|
||||
/* OFF-REGISTER — riso fair. Progressive enhancement only.
|
||||
Three behaviours: page-load reveal stagger · catalogue filter · signup validation.
|
||||
Everything works without JS; JS only adds polish. */
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var root = document.documentElement;
|
||||
root.classList.remove("no-js");
|
||||
|
||||
var reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
||||
|
||||
/* ── One orchestrated reveal on first load ────────────────── */
|
||||
// Assign reveal + a DOM-order index so CSS staggers via --i.
|
||||
var revealTargets = [
|
||||
".hero__copy", ".hero__art",
|
||||
"#catalogue .band", ".filters", ".filters__status",
|
||||
"#programme .band", "#workshops .band", "#visit .visit__lede", "#visit .visit__info"
|
||||
];
|
||||
if (!reduceMotion) {
|
||||
revealTargets.forEach(function (sel, i) {
|
||||
var el = document.querySelector(sel);
|
||||
if (el) {
|
||||
el.classList.add("reveal");
|
||||
el.style.setProperty("--i", i % 6); // cap stagger window
|
||||
}
|
||||
});
|
||||
// Cards reveal as a one-shot when the grid enters the viewport.
|
||||
var cards = Array.prototype.slice.call(document.querySelectorAll(".card"));
|
||||
if ("IntersectionObserver" in window && cards.length) {
|
||||
var io = new IntersectionObserver(function (entries, obs) {
|
||||
entries.forEach(function (entry) {
|
||||
if (entry.isIntersecting) {
|
||||
var card = entry.target;
|
||||
var idx = cards.indexOf(card);
|
||||
card.classList.add("reveal");
|
||||
card.style.setProperty("--i", Math.min(idx, 5));
|
||||
obs.unobserve(card);
|
||||
}
|
||||
});
|
||||
}, { rootMargin: "0px 0px -8% 0px", threshold: 0.1 });
|
||||
cards.forEach(function (c) { io.observe(c); });
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Catalogue filter ─────────────────────────────────────── */
|
||||
var chips = Array.prototype.slice.call(document.querySelectorAll(".chip"));
|
||||
var cardEls = Array.prototype.slice.call(document.querySelectorAll(".card"));
|
||||
var status = document.querySelector(".filters__status");
|
||||
var emptyMsg = status ? status.getAttribute("data-empty") : "";
|
||||
|
||||
function applyFilter(value) {
|
||||
var shown = 0;
|
||||
cardEls.forEach(function (card) {
|
||||
var match = value === "all" || card.getAttribute("data-medium") === value;
|
||||
card.classList.toggle("is-hidden", !match);
|
||||
if (match) shown += 1;
|
||||
});
|
||||
if (status) {
|
||||
if (shown === 0) {
|
||||
status.textContent = emptyMsg;
|
||||
} else if (value === "all") {
|
||||
status.textContent = "Showing all " + shown + " of these nine featured studios.";
|
||||
} else {
|
||||
var labelChip = chips.filter(function (c) { return c.getAttribute("data-filter") === value; })[0];
|
||||
var label = labelChip ? labelChip.textContent.replace(/\s*\d+\s*$/, "").trim().toLowerCase() : value;
|
||||
status.textContent = "Showing " + shown + " featured " + label + (shown === 1 ? " studio." : " studios.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
chips.forEach(function (chip) {
|
||||
chip.addEventListener("click", function () {
|
||||
chips.forEach(function (c) {
|
||||
var on = c === chip;
|
||||
c.classList.toggle("is-active", on);
|
||||
c.setAttribute("aria-pressed", on ? "true" : "false");
|
||||
});
|
||||
applyFilter(chip.getAttribute("data-filter"));
|
||||
});
|
||||
});
|
||||
|
||||
/* ── Signup validation (touched pattern · silent success) ─── */
|
||||
var form = document.querySelector(".signup");
|
||||
if (form) {
|
||||
var input = form.querySelector(".signup__input");
|
||||
var help = form.querySelector(".signup__help");
|
||||
var defaultHelp = help ? help.textContent : "";
|
||||
var touched = false;
|
||||
|
||||
function validityMessage() {
|
||||
var v = input.value.trim();
|
||||
if (!v) return "Add your email address so we know where to send the map.";
|
||||
// Simple, forgiving shape check — enough to catch typos, not gatekeep.
|
||||
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v)) {
|
||||
return "That doesn’t look like an email yet — check for a missing @ or dot.";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function setError(msg) {
|
||||
if (msg) {
|
||||
input.setAttribute("aria-invalid", "true");
|
||||
if (help) { help.textContent = msg; help.setAttribute("data-tone", "error"); }
|
||||
} else {
|
||||
input.removeAttribute("aria-invalid");
|
||||
if (help) { help.textContent = defaultHelp; help.removeAttribute("data-tone"); }
|
||||
}
|
||||
}
|
||||
|
||||
input.addEventListener("blur", function () { touched = true; setError(validityMessage()); });
|
||||
input.addEventListener("input", function () { if (touched) setError(validityMessage()); });
|
||||
|
||||
form.addEventListener("submit", function (e) {
|
||||
e.preventDefault();
|
||||
touched = true;
|
||||
var msg = validityMessage();
|
||||
setError(msg);
|
||||
if (msg) { input.focus(); return; }
|
||||
// Silent success: the form becomes a plain confirmation in place — no toast.
|
||||
var row = form.querySelector(".signup__row");
|
||||
if (row) row.remove();
|
||||
if (help) {
|
||||
help.textContent = "You’re on the list. Watch for a pink map the week before.";
|
||||
help.removeAttribute("data-tone");
|
||||
}
|
||||
});
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,813 @@
|
||||
/* Hallmark · pre-emit critique: P5 H5 E5 S5 R4 V5
|
||||
* macrostructure: Catalogue · genre: editorial · theme: Riso
|
||||
* tone: editorial / tactile-print · anchor hue: warm peach 30 · accent: riso cyan 220 (~3%)
|
||||
* nav: N6 Newspaper masthead · footer: Ft4 Dense colophon
|
||||
* enrichment: E5 centrepiece + per-card prints · craft: tier-A pure-CSS risograph
|
||||
* (overprinted cyan/yellow/pink ink layers, multiply blend, deliberate misregistration, grain) — no photos
|
||||
* F6 product-card knobs: ratio=4/3, density=3-up, micro-action=none (the print is the card)
|
||||
* F3 timetable knobs: 2-col key/val, rule=every row, numbers=tabular
|
||||
* motion: stagger reveal (load, one-shot) · card hover-lift · filter crossfade — 3 primitives, reduced-motion safe
|
||||
* studied: no
|
||||
* contrast: pass (40–41) · nav: N6 · footer: Ft4 · slop: pass (42–45)
|
||||
* honest: pass (46) · chrome: pass (47) · tokens: pass (48) · responsive: pass (49) · icons: pass (30)
|
||||
* mobile: pass (34, 49, 50–57) · slop-test: 57/57
|
||||
*/
|
||||
|
||||
*, *::before, *::after { box-sizing: border-box; }
|
||||
|
||||
html { overflow-x: clip; -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
|
||||
body { overflow-x: clip; }
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
html { scroll-behavior: auto; }
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background: var(--color-paper);
|
||||
color: var(--color-ink);
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--text-base);
|
||||
line-height: var(--lh-normal);
|
||||
font-variant-numeric: oldstyle-nums;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
/* faint paper grain over the whole page — riso stock, barely there */
|
||||
body::before {
|
||||
content: "";
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: var(--z-base);
|
||||
pointer-events: none;
|
||||
opacity: 0.5;
|
||||
mix-blend-mode: multiply;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)' opacity='0.05'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
img, svg { max-width: 100%; display: block; }
|
||||
|
||||
a { color: inherit; }
|
||||
|
||||
::selection { background: var(--color-accent-2); color: var(--color-on-accent-2); }
|
||||
|
||||
:focus-visible {
|
||||
outline: var(--rule-fine) solid var(--color-focus);
|
||||
outline-offset: 3px;
|
||||
}
|
||||
|
||||
.skip-link {
|
||||
position: absolute;
|
||||
left: var(--space-md);
|
||||
top: var(--space-md);
|
||||
transform: translateY(-150%);
|
||||
background: var(--color-ink);
|
||||
color: var(--color-paper);
|
||||
padding: var(--space-xs) var(--space-md);
|
||||
font-family: var(--font-label);
|
||||
font-size: var(--text-sm);
|
||||
letter-spacing: var(--tracking-label);
|
||||
text-transform: uppercase;
|
||||
text-decoration: none;
|
||||
z-index: var(--z-toast);
|
||||
transition: transform var(--dur-short) var(--ease-out);
|
||||
}
|
||||
.skip-link:focus { transform: translateY(0); }
|
||||
|
||||
/* ── Shared shells ──────────────────────────────────────────── */
|
||||
.kicker,
|
||||
.card__tag,
|
||||
.band__count,
|
||||
.slot__where,
|
||||
.work__when,
|
||||
.work__cap,
|
||||
.masthead__issue,
|
||||
.riso-plate__cap {
|
||||
font-family: var(--font-label);
|
||||
font-size: var(--text-xs);
|
||||
letter-spacing: var(--tracking-label);
|
||||
text-transform: uppercase;
|
||||
color: var(--color-muted);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* ── Risograph misregistration — the signature type treatment ──
|
||||
A layered text-shadow throws a cyan ghost up-left and a pink ghost
|
||||
down-right, so display words read like a slightly-misregistered
|
||||
3-colour print. Roman only (no italic headers). */
|
||||
.riso-mis {
|
||||
color: var(--color-ink);
|
||||
text-shadow:
|
||||
-0.02em -0.018em 0 var(--ink-cyan),
|
||||
0.022em 0.02em 0 var(--ink-pink);
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════
|
||||
N6 · NEWSPAPER MASTHEAD
|
||||
════════════════════════════════════════════════════════════ */
|
||||
.masthead {
|
||||
display: grid;
|
||||
justify-items: center;
|
||||
gap: var(--space-xs);
|
||||
padding: var(--space-lg) var(--page-gutter) 0;
|
||||
text-align: center;
|
||||
}
|
||||
.masthead__issue {
|
||||
font-variant: small-caps;
|
||||
letter-spacing: 0.1em;
|
||||
}
|
||||
.masthead__name {
|
||||
font-family: var(--font-display);
|
||||
font-weight: 900;
|
||||
font-size: clamp(2.5rem, 8vw, 5rem);
|
||||
line-height: 0.9;
|
||||
letter-spacing: var(--tracking-display);
|
||||
text-decoration: none;
|
||||
margin: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.masthead__nav ul {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: var(--space-md) var(--space-lg);
|
||||
list-style: none;
|
||||
margin: var(--space-2xs) 0 0;
|
||||
padding: 0;
|
||||
}
|
||||
.masthead__nav a {
|
||||
font-family: var(--font-label);
|
||||
font-size: var(--text-sm);
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
text-decoration: none;
|
||||
color: var(--color-ink-2);
|
||||
padding: var(--space-2xs) 0;
|
||||
white-space: nowrap;
|
||||
background-image: linear-gradient(var(--color-accent), var(--color-accent));
|
||||
background-repeat: no-repeat;
|
||||
background-position: 0 100%;
|
||||
background-size: 0% var(--rule-fine);
|
||||
transition: background-size var(--dur-short) var(--ease-out), color var(--dur-short) var(--ease-out);
|
||||
}
|
||||
.masthead__nav a:hover { background-size: 100% var(--rule-fine); color: var(--color-ink); }
|
||||
.masthead__rule {
|
||||
width: 100%;
|
||||
height: 5px;
|
||||
border: 0;
|
||||
border-top: var(--rule-hair) solid var(--color-rule-2);
|
||||
border-bottom: var(--rule-hair) solid var(--color-rule-2);
|
||||
margin: var(--space-sm) 0 0;
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════
|
||||
HERO
|
||||
════════════════════════════════════════════════════════════ */
|
||||
.hero {
|
||||
max-width: var(--page-max);
|
||||
margin-inline: auto;
|
||||
padding: var(--space-2xl) var(--page-gutter) var(--space-3xl);
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr);
|
||||
gap: var(--space-2xl);
|
||||
align-items: center;
|
||||
min-height: clamp(58vh, 74dvh, 86dvh);
|
||||
}
|
||||
.hero__copy { max-width: 40rem; }
|
||||
.kicker { margin-bottom: var(--space-md); }
|
||||
.hero__title {
|
||||
font-family: var(--font-display);
|
||||
font-weight: var(--display-weight);
|
||||
font-size: var(--text-display);
|
||||
line-height: var(--lh-tight);
|
||||
letter-spacing: var(--tracking-display);
|
||||
margin: 0 0 var(--space-lg);
|
||||
overflow-wrap: anywhere;
|
||||
min-width: 0;
|
||||
}
|
||||
.hero__title .riso-mis { display: inline-block; }
|
||||
.hero__lede {
|
||||
font-size: var(--text-md);
|
||||
line-height: var(--lh-relaxed);
|
||||
color: var(--color-ink-2);
|
||||
max-width: var(--measure);
|
||||
margin: 0 0 var(--space-xl);
|
||||
}
|
||||
.hero__actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: var(--space-md) var(--space-xl);
|
||||
margin-bottom: var(--space-2xl);
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.btn {
|
||||
font-family: var(--font-label);
|
||||
font-size: var(--text-sm);
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-xs);
|
||||
white-space: nowrap;
|
||||
line-height: 1;
|
||||
min-height: 44px;
|
||||
cursor: pointer;
|
||||
border-radius: var(--radius-pill);
|
||||
}
|
||||
.btn:focus-visible { outline: var(--rule-fine) solid var(--color-focus); outline-offset: 3px; }
|
||||
.btn:disabled,
|
||||
.btn[aria-disabled="true"] { opacity: 0.55; cursor: not-allowed; transform: none; }
|
||||
.btn--solid {
|
||||
background: var(--color-accent-deep);
|
||||
color: var(--color-on-accent);
|
||||
border: var(--rule-fine) solid var(--color-accent-deep);
|
||||
padding: var(--space-sm) var(--space-lg);
|
||||
transition: transform var(--dur-micro) var(--ease-out), background-color var(--dur-short) var(--ease-out), border-color var(--dur-short) var(--ease-out);
|
||||
}
|
||||
.btn--solid:hover { background: var(--color-ink); border-color: var(--color-ink); transform: translateY(-1.5px); }
|
||||
.btn--solid:active { transform: translateY(1px); }
|
||||
.btn--solid:disabled:hover,
|
||||
.btn--solid[aria-disabled="true"]:hover { background: var(--color-accent-deep); border-color: var(--color-accent-deep); transform: none; }
|
||||
.btn--text {
|
||||
background: none;
|
||||
border: 0;
|
||||
color: var(--color-ink);
|
||||
padding: var(--space-sm) 0;
|
||||
background-image: linear-gradient(var(--color-accent), var(--color-accent));
|
||||
background-repeat: no-repeat;
|
||||
background-position: 0 100%;
|
||||
background-size: 100% var(--rule-hair);
|
||||
transition: background-size var(--dur-short) var(--ease-out);
|
||||
}
|
||||
.btn--text:hover { background-size: 100% var(--rule-fine); }
|
||||
|
||||
/* Hero facts — small, mono, dl */
|
||||
.hero__facts {
|
||||
display: grid;
|
||||
gap: var(--space-sm);
|
||||
margin: 0;
|
||||
padding-top: var(--space-lg);
|
||||
border-top: var(--rule-hair) solid var(--color-rule);
|
||||
}
|
||||
.fact { display: grid; grid-template-columns: 6rem 1fr; gap: var(--space-md); align-items: baseline; }
|
||||
.fact dt {
|
||||
font-family: var(--font-label);
|
||||
font-size: var(--text-xs);
|
||||
letter-spacing: var(--tracking-label);
|
||||
text-transform: uppercase;
|
||||
color: var(--color-accent-ink);
|
||||
}
|
||||
.fact dd { margin: 0; font-size: var(--text-sm); color: var(--color-ink-2); }
|
||||
|
||||
/* ── Hero CSS riso plate (Tier-A pure-CSS art) ──────────────── */
|
||||
.hero__art { margin: 0; display: grid; gap: var(--space-sm); justify-items: end; }
|
||||
.riso-plate {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 26rem;
|
||||
aspect-ratio: 4 / 5;
|
||||
isolation: isolate;
|
||||
background: var(--color-paper-2);
|
||||
border: var(--rule-card) solid var(--color-rule-2);
|
||||
}
|
||||
.riso-ink {
|
||||
position: absolute;
|
||||
mix-blend-mode: multiply;
|
||||
}
|
||||
/* yellow drum — a big rising sun-disc, bottom-anchored */
|
||||
.riso-ink--yellow {
|
||||
inset: 24% 14% -14% 12%;
|
||||
background: radial-gradient(circle at 50% 45%, var(--ink-yellow) 0 46%, transparent 47%);
|
||||
}
|
||||
/* cyan drum — a tilted bar of "type", thrown slightly up-left (the misregister) */
|
||||
.riso-ink--cyan {
|
||||
inset: 12% 18% 30% 16%;
|
||||
transform: translate(-3%, -2.5%) rotate(-4deg);
|
||||
background:
|
||||
linear-gradient(var(--ink-cyan), var(--ink-cyan)) 0 18% / 100% 9% no-repeat,
|
||||
linear-gradient(var(--ink-cyan), var(--ink-cyan)) 0 42% / 76% 9% no-repeat,
|
||||
linear-gradient(var(--ink-cyan), var(--ink-cyan)) 0 66% / 90% 9% no-repeat,
|
||||
linear-gradient(var(--ink-cyan), var(--ink-cyan)) 0 90% / 54% 9% no-repeat;
|
||||
}
|
||||
/* pink drum — an off-register ring, thrown down-right */
|
||||
.riso-ink--pink {
|
||||
inset: 30% 10% 22% 30%;
|
||||
transform: translate(3.5%, 3%) rotate(2deg);
|
||||
background:
|
||||
radial-gradient(circle at 60% 50%, transparent 0 30%,
|
||||
var(--ink-pink) 31% 41%, transparent 42%);
|
||||
}
|
||||
/* per-plate grain, denser than the page grain */
|
||||
.riso-grain {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: var(--z-raised);
|
||||
pointer-events: none;
|
||||
opacity: 0.55;
|
||||
mix-blend-mode: multiply;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='90' height='90'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='1.1' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='90' height='90' filter='url(%23g)' opacity='0.07'/%3E%3C/svg%3E");
|
||||
}
|
||||
.riso-plate__cap { align-self: end; }
|
||||
|
||||
/* ════════════════════════════════════════════════════════════
|
||||
BANDS (section heads — stacked single column, no eyebrow tags)
|
||||
════════════════════════════════════════════════════════════ */
|
||||
.band {
|
||||
max-width: var(--page-max);
|
||||
margin-inline: auto;
|
||||
padding-inline: var(--page-gutter);
|
||||
display: grid;
|
||||
gap: var(--space-xs);
|
||||
margin-bottom: var(--space-xl);
|
||||
}
|
||||
.band__title {
|
||||
font-family: var(--font-display);
|
||||
font-weight: var(--display-weight);
|
||||
font-size: var(--text-3xl);
|
||||
line-height: var(--lh-snug);
|
||||
letter-spacing: var(--tracking-tight);
|
||||
margin: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.band--invert {
|
||||
background: var(--color-ink);
|
||||
color: var(--color-paper);
|
||||
max-width: none;
|
||||
padding-block: var(--space-xl);
|
||||
}
|
||||
.band--invert .band__title { color: var(--color-paper); }
|
||||
.band--invert .band__count { color: var(--color-paper-3); }
|
||||
.band--invert > * { max-width: var(--page-max); margin-inline: auto; width: 100%; }
|
||||
|
||||
/* ════════════════════════════════════════════════════════════
|
||||
CATALOGUE
|
||||
════════════════════════════════════════════════════════════ */
|
||||
.catalogue {
|
||||
padding: var(--section-gap) 0;
|
||||
}
|
||||
|
||||
/* Filter chips */
|
||||
.filters {
|
||||
max-width: var(--page-max);
|
||||
margin: 0 auto var(--space-md);
|
||||
padding-inline: var(--page-gutter);
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
.chip {
|
||||
font-family: var(--font-label);
|
||||
font-size: var(--text-sm);
|
||||
letter-spacing: 0.04em;
|
||||
color: var(--color-ink-2);
|
||||
background: transparent;
|
||||
border: var(--rule-card) solid var(--color-rule-2);
|
||||
border-radius: var(--radius-pill);
|
||||
padding: var(--space-xs) var(--space-md);
|
||||
min-height: 44px;
|
||||
cursor: pointer;
|
||||
transition: background-color var(--dur-short) var(--ease-out), color var(--dur-short) var(--ease-out), border-color var(--dur-short) var(--ease-out);
|
||||
}
|
||||
.chip__n { color: var(--color-muted); font-size: var(--text-xs); }
|
||||
.chip:hover { border-color: var(--color-ink); color: var(--color-ink); }
|
||||
.chip:active { transform: translateY(1px); }
|
||||
.chip:disabled { opacity: 0.55; cursor: not-allowed; }
|
||||
.chip.is-active {
|
||||
background: var(--color-ink);
|
||||
color: var(--color-paper);
|
||||
border-color: var(--color-ink);
|
||||
}
|
||||
.chip.is-active .chip__n { color: var(--color-accent-2); }
|
||||
|
||||
.filters__status {
|
||||
max-width: var(--page-max);
|
||||
margin: 0 auto var(--space-xl);
|
||||
padding-inline: var(--page-gutter);
|
||||
font-family: var(--font-label);
|
||||
font-size: var(--text-xs);
|
||||
letter-spacing: 0.04em;
|
||||
color: var(--color-muted);
|
||||
}
|
||||
|
||||
/* Uniform card grid */
|
||||
.grid {
|
||||
max-width: var(--page-max);
|
||||
margin: 0 auto;
|
||||
padding-inline: var(--page-gutter);
|
||||
list-style: none;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: var(--space-2xl) var(--space-xl);
|
||||
}
|
||||
.card {
|
||||
display: grid;
|
||||
gap: var(--space-md);
|
||||
align-content: start;
|
||||
}
|
||||
.card.is-hidden { display: none; }
|
||||
.card__media {
|
||||
display: block;
|
||||
aspect-ratio: 4 / 3;
|
||||
position: relative;
|
||||
isolation: isolate;
|
||||
overflow: hidden;
|
||||
background: var(--color-paper-2);
|
||||
border: var(--rule-card) solid var(--color-rule);
|
||||
transition: transform var(--dur-short) var(--ease-out), border-color var(--dur-short) var(--ease-out);
|
||||
}
|
||||
.card__media:hover { transform: translateY(-3px); border-color: var(--color-ink); }
|
||||
.card__meta { display: grid; gap: var(--space-2xs); }
|
||||
.card__name {
|
||||
font-family: var(--font-display);
|
||||
font-weight: var(--display-weight);
|
||||
font-size: var(--text-xl);
|
||||
line-height: 1.05;
|
||||
letter-spacing: var(--tracking-tight);
|
||||
margin: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.card__line {
|
||||
font-size: var(--text-sm);
|
||||
line-height: var(--lh-normal);
|
||||
color: var(--color-ink-2);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* ── Per-card CSS "prints" — every card is a different riso composition.
|
||||
Each .print stacks 2–3 multiply-blended ink layers via background +
|
||||
a tiny misregister, then a grain overlay (::after). Hand-built, no photos. */
|
||||
.print { position: absolute; inset: 0; z-index: var(--z-base); }
|
||||
.print::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: var(--z-raised);
|
||||
pointer-events: none;
|
||||
opacity: 0.5;
|
||||
mix-blend-mode: multiply;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='80' height='80'%3E%3Cfilter id='c'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='1.05' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='80' height='80' filter='url(%23c)' opacity='0.06'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* 1 · concentric cyan rings, yellow disc behind, pink ghost — "working press" */
|
||||
.print--1 {
|
||||
background:
|
||||
radial-gradient(circle at 38% 56%, var(--ink-pink) 0 22%, transparent 23%),
|
||||
repeating-radial-gradient(circle at 44% 50%, var(--ink-cyan) 0 6%, transparent 6% 14%),
|
||||
radial-gradient(circle at 50% 50%, var(--ink-yellow) 0 60%, transparent 61%),
|
||||
var(--color-paper-2);
|
||||
background-blend-mode: multiply, multiply, multiply, normal;
|
||||
}
|
||||
/* 2 · stacked horizontal bars (a zine spread), cyan over yellow */
|
||||
.print--2 {
|
||||
background:
|
||||
linear-gradient(var(--ink-cyan), var(--ink-cyan)) 8% 22% / 60% 12% no-repeat,
|
||||
linear-gradient(var(--ink-cyan), var(--ink-cyan)) 8% 46% / 84% 12% no-repeat,
|
||||
linear-gradient(var(--ink-cyan), var(--ink-cyan)) 8% 70% / 40% 12% no-repeat,
|
||||
linear-gradient(135deg, var(--ink-yellow) 0 70%, transparent 70%),
|
||||
var(--color-paper-2);
|
||||
background-blend-mode: multiply, multiply, multiply, multiply, normal;
|
||||
}
|
||||
/* 3 · big pink triangle + cyan grid — gig poster */
|
||||
.print--3 {
|
||||
background:
|
||||
linear-gradient(var(--ink-cyan), var(--ink-cyan)) 0 0 / 100% 8% repeat-y,
|
||||
conic-gradient(from -30deg at 50% 88%, var(--ink-pink) 0 60deg, transparent 60deg),
|
||||
var(--color-accent-2);
|
||||
background-blend-mode: multiply, multiply, normal;
|
||||
background-size: 14% 100%, 100% 100%, auto;
|
||||
}
|
||||
/* 4 · panel grid (comic), yellow blocks + cyan gutter */
|
||||
.print--4 {
|
||||
background:
|
||||
linear-gradient(var(--ink-cyan), var(--ink-cyan)),
|
||||
repeating-linear-gradient(90deg, var(--ink-yellow) 0 31%, transparent 31% 34%),
|
||||
repeating-linear-gradient(0deg, var(--ink-yellow) 0 44%, transparent 44% 50%),
|
||||
var(--color-paper-2);
|
||||
background-blend-mode: multiply, multiply, multiply, normal;
|
||||
background-size: 100% 100%, 100% 100%, 100% 100%, auto;
|
||||
background-position: 0 0;
|
||||
opacity: 0.96;
|
||||
}
|
||||
/* 5 · botanical sprig — cyan stem, yellow leaves, teal-on-teal feel */
|
||||
.print--5 {
|
||||
background:
|
||||
radial-gradient(ellipse 14% 26% at 36% 40%, var(--ink-cyan) 0 70%, transparent 72%),
|
||||
radial-gradient(ellipse 14% 26% at 64% 58%, var(--ink-cyan) 0 70%, transparent 72%),
|
||||
linear-gradient(8deg, transparent 47%, var(--ink-cyan) 47% 53%, transparent 53%),
|
||||
radial-gradient(circle at 50% 50%, var(--ink-yellow) 0 64%, transparent 65%),
|
||||
var(--color-paper-2);
|
||||
background-blend-mode: multiply, multiply, multiply, multiply, normal;
|
||||
}
|
||||
/* 6 · halftone dot field, cyan over yellow — "big dots on purpose" */
|
||||
.print--6 {
|
||||
background:
|
||||
radial-gradient(var(--ink-cyan) 30%, transparent 32%),
|
||||
radial-gradient(var(--ink-yellow) 30%, transparent 32%),
|
||||
var(--color-paper-2);
|
||||
background-blend-mode: multiply, multiply, normal;
|
||||
background-size: 18% 18%, 18% 18%, auto;
|
||||
background-position: 0 0, 9% 9%, 0 0;
|
||||
}
|
||||
/* 7 · open book / pink spread with cyan spine — graphic novel */
|
||||
.print--7 {
|
||||
background:
|
||||
linear-gradient(var(--ink-cyan), var(--ink-cyan)) 49% 12% / 3% 76% no-repeat,
|
||||
linear-gradient(95deg, var(--ink-pink) 0 47%, transparent 47% 53%, var(--ink-pink) 53%),
|
||||
var(--color-paper-2);
|
||||
background-blend-mode: multiply, multiply, normal;
|
||||
}
|
||||
/* 8 · single bold letterform-ish mark — cyan slab + yellow counter */
|
||||
.print--8 {
|
||||
background:
|
||||
radial-gradient(circle at 52% 46%, var(--color-paper-2) 0 16%, transparent 17%),
|
||||
linear-gradient(var(--ink-cyan), var(--ink-cyan)) 30% 18% / 40% 64% no-repeat,
|
||||
radial-gradient(circle at 52% 78%, var(--ink-yellow) 0 26%, transparent 27%),
|
||||
var(--color-paper-2);
|
||||
background-blend-mode: normal, multiply, multiply, normal;
|
||||
}
|
||||
/* 9 · overlapping circles (anthology), pink + cyan venn over yellow */
|
||||
.print--9 {
|
||||
background:
|
||||
radial-gradient(circle at 40% 50%, var(--ink-pink) 0 32%, transparent 33%),
|
||||
radial-gradient(circle at 60% 50%, var(--ink-cyan) 0 32%, transparent 33%),
|
||||
radial-gradient(circle at 50% 38%, var(--ink-yellow) 0 30%, transparent 31%),
|
||||
var(--color-paper-2);
|
||||
background-blend-mode: multiply, multiply, multiply, normal;
|
||||
}
|
||||
|
||||
.catalogue__more {
|
||||
max-width: var(--page-max);
|
||||
margin: var(--space-2xl) auto 0;
|
||||
padding-inline: var(--page-gutter);
|
||||
font-size: var(--text-md);
|
||||
line-height: var(--lh-relaxed);
|
||||
color: var(--color-ink-2);
|
||||
max-width: 52ch;
|
||||
padding-inline: var(--page-gutter);
|
||||
}
|
||||
.catalogue__more em { font-style: italic; color: var(--color-ink); }
|
||||
|
||||
/* ════════════════════════════════════════════════════════════
|
||||
PROGRAMME (tabular)
|
||||
════════════════════════════════════════════════════════════ */
|
||||
.programme { padding-bottom: var(--section-gap); }
|
||||
.programme .band--invert { margin-bottom: var(--space-2xl); }
|
||||
.prog__day {
|
||||
max-width: var(--page-max);
|
||||
margin: 0 auto var(--space-2xl);
|
||||
padding-inline: var(--page-gutter);
|
||||
}
|
||||
.prog__heading {
|
||||
font-family: var(--font-display);
|
||||
font-weight: var(--display-weight);
|
||||
font-size: var(--text-2xl);
|
||||
letter-spacing: var(--tracking-tight);
|
||||
margin: 0 0 var(--space-md);
|
||||
}
|
||||
.prog__heading .riso-mis { display: inline-block; }
|
||||
.timetable { margin: 0; }
|
||||
.slot {
|
||||
display: grid;
|
||||
grid-template-columns: 7rem 1fr;
|
||||
gap: var(--space-lg);
|
||||
align-items: baseline;
|
||||
padding: var(--space-md) 0;
|
||||
border-top: var(--rule-hair) solid var(--color-rule);
|
||||
}
|
||||
.slot:last-child { border-bottom: var(--rule-hair) solid var(--color-rule); }
|
||||
.slot__time {
|
||||
font-family: var(--font-label);
|
||||
font-weight: 700;
|
||||
font-size: var(--text-md);
|
||||
font-variant-numeric: tabular-nums;
|
||||
color: var(--color-accent-ink);
|
||||
margin: 0;
|
||||
}
|
||||
.slot__body { display: flex; flex-wrap: wrap; gap: var(--space-xs) var(--space-md); align-items: baseline; justify-content: space-between; margin: 0; }
|
||||
.slot__title { font-size: var(--text-md); color: var(--color-ink); }
|
||||
|
||||
/* ════════════════════════════════════════════════════════════
|
||||
WORKSHOPS (varied list, not icon tiles)
|
||||
════════════════════════════════════════════════════════════ */
|
||||
.workshops { padding-bottom: var(--section-gap); }
|
||||
.work-list {
|
||||
max-width: var(--page-max);
|
||||
margin: 0 auto;
|
||||
padding-inline: var(--page-gutter);
|
||||
list-style: none;
|
||||
display: grid;
|
||||
gap: 0;
|
||||
}
|
||||
.work {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.4fr) minmax(0, 0.5fr);
|
||||
gap: var(--space-lg) var(--space-2xl);
|
||||
align-items: start;
|
||||
padding: var(--space-xl) 0;
|
||||
border-top: var(--rule-hair) solid var(--color-rule-2);
|
||||
}
|
||||
.work:last-child { border-bottom: var(--rule-hair) solid var(--color-rule-2); }
|
||||
.work__name {
|
||||
font-family: var(--font-display);
|
||||
font-weight: var(--display-weight);
|
||||
font-size: var(--text-xl);
|
||||
line-height: 1.08;
|
||||
letter-spacing: var(--tracking-tight);
|
||||
margin: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.work__body {
|
||||
font-size: var(--text-base);
|
||||
line-height: var(--lh-relaxed);
|
||||
color: var(--color-ink-2);
|
||||
margin: 0;
|
||||
max-width: var(--measure);
|
||||
}
|
||||
.work__cap { text-align: right; }
|
||||
|
||||
/* ════════════════════════════════════════════════════════════
|
||||
VISIT
|
||||
════════════════════════════════════════════════════════════ */
|
||||
.visit {
|
||||
max-width: var(--page-max);
|
||||
margin-inline: auto;
|
||||
padding: var(--section-gap) var(--page-gutter);
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.2fr) minmax(0, 0.8fr);
|
||||
gap: var(--space-3xl);
|
||||
align-items: start;
|
||||
}
|
||||
.visit__title {
|
||||
font-family: var(--font-display);
|
||||
font-weight: var(--display-weight);
|
||||
font-size: var(--text-3xl);
|
||||
line-height: var(--lh-snug);
|
||||
letter-spacing: var(--tracking-tight);
|
||||
margin: 0 0 var(--space-md);
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.visit__body {
|
||||
font-size: var(--text-md);
|
||||
line-height: var(--lh-relaxed);
|
||||
color: var(--color-ink-2);
|
||||
max-width: var(--measure);
|
||||
margin: 0 0 var(--space-xl);
|
||||
}
|
||||
|
||||
/* Signup (C2 inline form) */
|
||||
.signup { display: grid; gap: var(--space-xs); }
|
||||
.signup__label {
|
||||
font-family: var(--font-label);
|
||||
font-size: var(--text-xs);
|
||||
letter-spacing: var(--tracking-label);
|
||||
text-transform: uppercase;
|
||||
color: var(--color-accent-ink);
|
||||
}
|
||||
.signup__row { display: flex; flex-wrap: wrap; align-items: stretch; gap: var(--space-sm); }
|
||||
.signup__input {
|
||||
flex: 1 1 16rem;
|
||||
min-width: 0;
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--text-md);
|
||||
color: var(--color-ink);
|
||||
background: var(--color-paper-2);
|
||||
border: var(--rule-card) solid var(--color-rule-2);
|
||||
border-radius: var(--radius-input);
|
||||
padding: var(--space-sm) var(--space-md);
|
||||
min-height: 44px;
|
||||
transition: border-color var(--dur-short) var(--ease-out), background-color var(--dur-short) var(--ease-out);
|
||||
}
|
||||
.signup__input::placeholder { color: var(--color-muted); }
|
||||
.signup__input:hover { border-color: var(--color-rule-2); }
|
||||
.signup__input:focus-visible { border-color: var(--color-focus); background: var(--color-paper); }
|
||||
.signup__input[aria-invalid="true"] { border-color: var(--color-accent-3); background: var(--color-paper); }
|
||||
.signup__input:disabled { opacity: 0.55; cursor: not-allowed; }
|
||||
.signup__submit { flex: 0 0 auto; }
|
||||
.signup__help { margin: var(--space-2xs) 0 0; min-height: 1.5lh; font-size: var(--text-sm); color: var(--color-muted); }
|
||||
.signup__help[data-tone="error"] { color: var(--color-accent-3); font-family: var(--font-label); font-size: var(--text-xs); letter-spacing: 0.02em; }
|
||||
|
||||
/* Visit info */
|
||||
.visit__info {
|
||||
display: grid;
|
||||
gap: var(--space-lg);
|
||||
margin: 0;
|
||||
padding-top: var(--space-xs);
|
||||
}
|
||||
.info { display: grid; gap: var(--space-3xs); padding-bottom: var(--space-lg); border-bottom: var(--rule-hair) solid var(--color-rule); }
|
||||
.info:last-child { border-bottom: 0; padding-bottom: 0; }
|
||||
.info dt {
|
||||
font-family: var(--font-label);
|
||||
font-size: var(--text-xs);
|
||||
letter-spacing: var(--tracking-label);
|
||||
text-transform: uppercase;
|
||||
color: var(--color-muted);
|
||||
}
|
||||
.info dd { margin: 0; font-size: var(--text-base); line-height: var(--lh-normal); color: var(--color-ink); }
|
||||
.info a {
|
||||
color: var(--color-accent-ink);
|
||||
text-underline-offset: 3px;
|
||||
text-decoration-thickness: var(--rule-hair);
|
||||
}
|
||||
.info a:hover { text-decoration-thickness: var(--rule-fine); }
|
||||
|
||||
/* ════════════════════════════════════════════════════════════
|
||||
Ft4 · DENSE COLOPHON
|
||||
════════════════════════════════════════════════════════════ */
|
||||
.colophon {
|
||||
border-top: var(--rule-fine) solid var(--color-ink);
|
||||
padding: var(--space-2xl) var(--page-gutter) var(--space-3xl);
|
||||
}
|
||||
.colophon__body {
|
||||
max-width: 70ch;
|
||||
margin: 0 auto;
|
||||
font-family: var(--font-label);
|
||||
font-size: var(--text-sm);
|
||||
line-height: 1.75;
|
||||
color: var(--color-ink-2);
|
||||
}
|
||||
.colophon__mark {
|
||||
font-family: var(--font-display);
|
||||
font-weight: 900;
|
||||
letter-spacing: var(--tracking-tight);
|
||||
font-size: var(--text-md);
|
||||
display: inline-block;
|
||||
margin-right: var(--space-2xs);
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════
|
||||
MOTION · one orchestrated reveal on load (stagger by DOM index)
|
||||
════════════════════════════════════════════════════════════ */
|
||||
.reveal {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
animation: reveal var(--dur-long) var(--ease-out) forwards;
|
||||
animation-delay: calc(var(--i, 0) * 70ms);
|
||||
}
|
||||
@keyframes reveal { to { opacity: 1; transform: none; } }
|
||||
|
||||
/* JS-disabled / pre-JS: never leave content hidden */
|
||||
.no-js .reveal { opacity: 1; transform: none; animation: none; }
|
||||
|
||||
/* ════════════════════════════════════════════════════════════
|
||||
RESPONSIVE
|
||||
════════════════════════════════════════════════════════════ */
|
||||
@media (max-width: 60rem) {
|
||||
.hero {
|
||||
grid-template-columns: 1fr;
|
||||
gap: var(--space-2xl);
|
||||
min-height: 0;
|
||||
padding-block: var(--space-xl) var(--space-2xl);
|
||||
}
|
||||
.hero__art { justify-items: start; order: -1; }
|
||||
.riso-plate { max-width: 20rem; aspect-ratio: 4 / 3; }
|
||||
|
||||
.grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-xl) var(--space-lg); }
|
||||
|
||||
.visit { grid-template-columns: 1fr; gap: var(--space-2xl); }
|
||||
|
||||
.work { grid-template-columns: 1fr; gap: var(--space-sm); }
|
||||
.work__when { order: -1; }
|
||||
.work__cap { text-align: left; }
|
||||
}
|
||||
|
||||
@media (max-width: 40rem) {
|
||||
.masthead { padding-top: var(--space-md); }
|
||||
.masthead__name { font-size: clamp(2rem, 12vw, 3.25rem); }
|
||||
|
||||
.hero__title { font-size: var(--text-display-s); }
|
||||
.hero__actions { gap: var(--space-md); }
|
||||
.btn--solid, .btn--text { width: 100%; justify-content: center; }
|
||||
.fact { grid-template-columns: 1fr; gap: var(--space-3xs); }
|
||||
.fact dt { font-size: var(--text-xs); }
|
||||
|
||||
.grid { grid-template-columns: 1fr; gap: var(--space-xl); }
|
||||
.card__media { aspect-ratio: 16 / 10; }
|
||||
|
||||
.band__title { font-size: var(--text-2xl); }
|
||||
.visit__title { font-size: var(--text-2xl); }
|
||||
|
||||
.slot { grid-template-columns: 1fr; gap: var(--space-2xs); }
|
||||
.slot__body { justify-content: flex-start; }
|
||||
|
||||
.signup__row { flex-direction: column; }
|
||||
.signup__submit { width: 100%; justify-content: center; }
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════
|
||||
REDUCED MOTION
|
||||
════════════════════════════════════════════════════════════ */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*, *::before, *::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
.reveal { opacity: 1; transform: none; animation: none; }
|
||||
.card__media:hover { transform: none; }
|
||||
.btn--solid:hover, .btn--solid:active { transform: none; }
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/* Hallmark · tokens — RISO theme (catalog)
|
||||
Risograph print · warm peach paper · riso cyan + yellow accents.
|
||||
Public Sans 800 display, off-register CMYK misregistration via text-shadow.
|
||||
Newsreader body serif. Space Mono outlier for catalogue labels.
|
||||
Diversification axes — paper-band: light (L 91%) · display-style: risograph-bold · accent-hue: cool (cyan 220).
|
||||
Every colour + font in styles.css references a token here. No inline values. */
|
||||
|
||||
:root {
|
||||
/* ── Palette (riso) ───────────────────────────────────────── */
|
||||
--color-paper: oklch(91% 0.034 30);
|
||||
--color-paper-2: oklch(87% 0.040 30);
|
||||
--color-paper-3: oklch(82% 0.046 30);
|
||||
--color-rule: oklch(70% 0.040 30);
|
||||
--color-rule-2: oklch(56% 0.060 30);
|
||||
--color-muted: oklch(46% 0.060 30);
|
||||
--color-neutral: oklch(34% 0.060 30);
|
||||
--color-ink-2: oklch(22% 0.060 30);
|
||||
--color-ink: oklch(18% 0.060 30);
|
||||
--color-accent: oklch(58% 0.170 220); /* riso cyan / teal — hairlines, underlines, hovers */
|
||||
--color-accent-deep: oklch(48% 0.170 220); /* deeper cyan for filled buttons (AA text ≥ 5:1) */
|
||||
--color-accent-2: oklch(78% 0.180 95); /* riso yellow */
|
||||
--color-accent-3: oklch(60% 0.200 12); /* riso fluoro-pink (single moment) */
|
||||
--color-accent-ink: oklch(42% 0.135 224); /* deep cyan — AA text on peach paper (≥ 4.5:1) */
|
||||
--color-focus: oklch(48% 0.170 220); /* darker cyan — AA-contrast focus ring (≥ 3:1) */
|
||||
--color-on-accent: oklch(98% 0.012 30); /* paper-tint text on a cyan fill */
|
||||
--color-on-accent-2: oklch(18% 0.060 30); /* ink on a yellow fill */
|
||||
|
||||
/* overprint tints — used by the hand-built riso art (multiply blend) */
|
||||
--ink-cyan: oklch(58% 0.170 220 / 0.92);
|
||||
--ink-yellow: oklch(80% 0.180 95 / 0.90);
|
||||
--ink-pink: oklch(62% 0.200 12 / 0.88);
|
||||
|
||||
/* ── Type ─────────────────────────────────────────────────── */
|
||||
--font-display: "Public Sans", "Albert Sans", ui-sans-serif, system-ui, sans-serif;
|
||||
--font-body: "Newsreader", "Tiempos", ui-serif, Georgia, serif;
|
||||
--font-serif: "Newsreader", ui-serif, Georgia, serif;
|
||||
--font-label: "Space Mono", "Geist Mono", ui-monospace, monospace; /* catalogue labels / outlier */
|
||||
--font-mono: "Space Mono", "Geist Mono", ui-monospace, monospace;
|
||||
|
||||
--display-weight: 800;
|
||||
--display-style: normal;
|
||||
|
||||
--text-xs: 0.75rem;
|
||||
--text-sm: 0.875rem;
|
||||
--text-base: 1.0625rem;
|
||||
--text-md: 1.1875rem;
|
||||
--text-lg: 1.375rem;
|
||||
--text-xl: 1.75rem;
|
||||
--text-2xl: 2.25rem;
|
||||
--text-3xl: 2.875rem;
|
||||
--text-display: clamp(3rem, 4.6vw + 1rem, 5.25rem); /* capped for the 52-char hero — gravity, not drama */
|
||||
--text-display-s: clamp(2.25rem, 3.5vw + 1rem, 3.5rem);
|
||||
|
||||
--lh-tight: 0.92;
|
||||
--lh-snug: 1.10;
|
||||
--lh-normal: 1.55;
|
||||
--lh-relaxed: 1.65;
|
||||
|
||||
--tracking-display: -0.04em;
|
||||
--tracking-tight: -0.014em;
|
||||
--tracking-normal: 0;
|
||||
--tracking-label: 0.18em;
|
||||
|
||||
/* ── Space (4pt scale) ────────────────────────────────────── */
|
||||
--space-3xs: 0.125rem;
|
||||
--space-2xs: 0.25rem;
|
||||
--space-xs: 0.5rem;
|
||||
--space-sm: 0.75rem;
|
||||
--space-md: 1rem;
|
||||
--space-lg: 1.5rem;
|
||||
--space-xl: 2.5rem;
|
||||
--space-2xl: 4rem;
|
||||
--space-3xl: 6rem;
|
||||
--space-4xl: 9rem;
|
||||
|
||||
--section-gap: 6rem;
|
||||
--section-head-gap: 1.5rem;
|
||||
|
||||
--page-max: 78rem;
|
||||
--page-gutter: clamp(1.25rem, 4vw, 3rem);
|
||||
--measure: 62ch;
|
||||
|
||||
--rule-hair: 1px;
|
||||
--rule-fine: 2px;
|
||||
|
||||
/* ── Shape ────────────────────────────────────────────────── */
|
||||
--radius-card: 2px;
|
||||
--radius-pill: 2px;
|
||||
--radius-input: 2px;
|
||||
--rule-card: 1px;
|
||||
--shadow-card: none;
|
||||
|
||||
/* ── Motion ───────────────────────────────────────────────── */
|
||||
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
|
||||
--ease-in: cubic-bezier(0.7, 0, 0.84, 0);
|
||||
--ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
|
||||
--dur-micro: 120ms;
|
||||
--dur-short: 220ms;
|
||||
--dur-long: 420ms;
|
||||
|
||||
/* ── Z-index (named) ──────────────────────────────────────── */
|
||||
--z-base: 1;
|
||||
--z-raised: 10;
|
||||
--z-dropdown: 100;
|
||||
--z-sticky: 200;
|
||||
--z-sticky-nav: 300;
|
||||
--z-modal: 400;
|
||||
--z-toast: 500;
|
||||
--z-tooltip: 600;
|
||||
}
|
||||
@@ -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=22" />
|
||||
<link rel="stylesheet" href="css/base.css?v=22" />
|
||||
<link rel="stylesheet" href="css/components.css?v=22" />
|
||||
<link rel="stylesheet" href="css/sections.css?v=22" />
|
||||
<link rel="stylesheet" href="css/tokens.css?v=23" />
|
||||
<link rel="stylesheet" href="css/base.css?v=23" />
|
||||
<link rel="stylesheet" href="css/components.css?v=23" />
|
||||
<link rel="stylesheet" href="css/sections.css?v=23" />
|
||||
|
||||
<script>
|
||||
// Load theme before paint to avoid flash. Order: ?theme= → localStorage → default.
|
||||
@@ -242,28 +242,28 @@
|
||||
<video data-hover-play src="_tests/_thumbs/<slug>-scroll.mp4" poster="..."> once rendered. -->
|
||||
<a class="ex-card" style="--scroll-dur:2.5s" role="listitem" href="examples/cobalt-01/" target="_blank" rel="noopener" aria-label="Open the Distil example — content-extraction API, Cobalt — in a new tab">
|
||||
<div class="ex-card__thumb" aria-hidden="true">
|
||||
<img src="_tests/_thumbs/cobalt-01.jpg?v=22" alt="" loading="lazy" width="1600" height="1000" />
|
||||
<img src="_tests/_thumbs/cobalt-01.jpg?v=23" alt="" loading="lazy" width="1600" height="1000" />
|
||||
</div>
|
||||
<p class="ex-card__prompt"><span class="ex-card__prompt-mark">$</span> /hallmark build a content-extraction API, Cobalt</p>
|
||||
</a>
|
||||
|
||||
<a class="ex-card" style="--scroll-dur:2.7s" role="listitem" href="examples/carnival-01/" target="_blank" rel="noopener" aria-label="Open the Cold Snap example — record-label EP page, Carnival — in a new tab">
|
||||
<div class="ex-card__thumb" aria-hidden="true">
|
||||
<img src="_tests/_thumbs/carnival-01.jpg?v=22" alt="" loading="lazy" width="1600" height="1000" />
|
||||
<img src="_tests/_thumbs/carnival-01.jpg?v=23" alt="" loading="lazy" width="1600" height="1000" />
|
||||
</div>
|
||||
<p class="ex-card__prompt"><span class="ex-card__prompt-mark">$</span> /hallmark build a record-label EP page, Carnival</p>
|
||||
</a>
|
||||
|
||||
<a class="ex-card" style="--scroll-dur:3.1s" role="listitem" href="examples/lumen-01/" target="_blank" rel="noopener" aria-label="Open the Cinder example — AI reasoning tool, Lumen — in a new tab">
|
||||
<div class="ex-card__thumb" aria-hidden="true">
|
||||
<img src="_tests/_thumbs/lumen-01.jpg?v=22" alt="" loading="lazy" width="1600" height="1000" />
|
||||
<img src="_tests/_thumbs/lumen-01.jpg?v=23" alt="" loading="lazy" width="1600" height="1000" />
|
||||
</div>
|
||||
<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" style="--scroll-dur:3.3s" 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-07.jpg?v=22" alt="" loading="lazy" width="1600" height="1000" />
|
||||
<img src="_tests/_thumbs/hum-07.jpg?v=23" alt="" loading="lazy" width="1600" height="1000" />
|
||||
</div>
|
||||
<p class="ex-card__prompt"><span class="ex-card__prompt-mark">$</span> /hallmark build a guided sourdough app, Hum</p>
|
||||
</a>
|
||||
@@ -273,56 +273,70 @@
|
||||
once rendered, swap the <img> for <video data-hover-play src="..." poster="...">. -->
|
||||
<a class="ex-card" style="--scroll-dur:4s" 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?v=22" alt="" loading="lazy" width="1600" height="1000" />
|
||||
<img src="_tests/_thumbs/custom-04.jpg?v=23" 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" style="--scroll-dur:3.6s" role="listitem" href="examples/garden-01/" target="_blank" rel="noopener" aria-label="Open the Hollowback Apiary example, a small-batch honey farm, Garden, in a new tab">
|
||||
<div class="ex-card__thumb" aria-hidden="true">
|
||||
<img src="_tests/_thumbs/garden-01.jpg?v=23" alt="" loading="lazy" width="1600" height="1000" />
|
||||
</div>
|
||||
<p class="ex-card__prompt"><span class="ex-card__prompt-mark">$</span> /hallmark build a landing page for a small-batch honey farm</p>
|
||||
</a>
|
||||
|
||||
<a class="ex-card" style="--scroll-dur:4.6s" role="listitem" href="examples/riso-01/" target="_blank" rel="noopener" aria-label="Open the Off-Register example, an indie risograph print fair, Riso, in a new tab">
|
||||
<div class="ex-card__thumb" aria-hidden="true">
|
||||
<img src="_tests/_thumbs/riso-01.jpg?v=23" alt="" loading="lazy" width="1600" height="1000" />
|
||||
</div>
|
||||
<p class="ex-card__prompt"><span class="ex-card__prompt-mark">$</span> /hallmark build a page for an indie risograph print fair</p>
|
||||
</a>
|
||||
|
||||
<a class="ex-card" style="--scroll-dur:4.7s" role="listitem" href="examples/press-01/" target="_blank" rel="noopener" aria-label="Open the Press Quaternary example, an experimental type studio, in a new tab">
|
||||
<div class="ex-card__thumb" aria-hidden="true">
|
||||
<img src="_tests/_thumbs/press-01.jpg?v=23" alt="" loading="lazy" width="1600" height="1000" />
|
||||
</div>
|
||||
<p class="ex-card__prompt"><span class="ex-card__prompt-mark">$</span> /hallmark build a portfolio for an experimental typographer</p>
|
||||
</a>
|
||||
|
||||
<a class="ex-card" style="--scroll-dur:3.6s" role="listitem" href="examples/tally/" target="_blank" rel="noopener" aria-label="Open the Tally example, a SaaS product page, modern-minimal, in a new tab">
|
||||
<div class="ex-card__thumb" aria-hidden="true">
|
||||
<img src="_tests/_thumbs/tally.jpg?v=22" alt="" loading="lazy" width="1600" height="1000" />
|
||||
<img src="_tests/_thumbs/tally.jpg?v=23" alt="" loading="lazy" width="1600" height="1000" />
|
||||
</div>
|
||||
<p class="ex-card__prompt"><span class="ex-card__prompt-mark">$</span> /hallmark build a SaaS product page, modern-minimal</p>
|
||||
</a>
|
||||
|
||||
<a class="ex-card" style="--scroll-dur:3s" role="listitem" href="examples/wayfare/" target="_blank" rel="noopener" aria-label="Open the Wayfare example, a travel booking site, atmospheric, in a new tab">
|
||||
<div class="ex-card__thumb" aria-hidden="true">
|
||||
<img src="_tests/_thumbs/wayfare.jpg?v=22" alt="" loading="lazy" width="1600" height="1000" />
|
||||
<img src="_tests/_thumbs/wayfare.jpg?v=23" alt="" loading="lazy" width="1600" height="1000" />
|
||||
</div>
|
||||
<p class="ex-card__prompt"><span class="ex-card__prompt-mark">$</span> /hallmark build a travel booking site, atmospheric</p>
|
||||
</a>
|
||||
|
||||
<a class="ex-card" style="--scroll-dur:3.2s" role="listitem" href="examples/bananastudio/" target="_blank" rel="noopener" aria-label="Open the BananaStudio example, a creative studio with playful motion, in a new tab">
|
||||
<div class="ex-card__thumb" aria-hidden="true">
|
||||
<img src="_tests/_thumbs/bananastudio.jpg?v=22" alt="" loading="lazy" width="1600" height="1000" />
|
||||
<img src="_tests/_thumbs/bananastudio.jpg?v=23" alt="" loading="lazy" width="1600" height="1000" />
|
||||
</div>
|
||||
<p class="ex-card__prompt"><span class="ex-card__prompt-mark">$</span> /hallmark build a creative studio with playful motion</p>
|
||||
</a>
|
||||
|
||||
<a class="ex-card" style="--scroll-dur:1.8s" role="listitem" href="_tests/06-anya-portfolio/" target="_blank" rel="noopener" aria-label="Open the Anya Reis example, a software architect personal site, in a new tab">
|
||||
<div class="ex-card__thumb" aria-hidden="true">
|
||||
<img src="_tests/_thumbs/anya.jpg?v=22" alt="" loading="lazy" width="1600" height="1000" />
|
||||
<img src="_tests/_thumbs/anya.jpg?v=23" alt="" loading="lazy" width="1600" height="1000" />
|
||||
</div>
|
||||
<p class="ex-card__prompt"><span class="ex-card__prompt-mark">$</span> /hallmark build a software architect personal site</p>
|
||||
</a>
|
||||
|
||||
<a class="ex-card" style="--scroll-dur:2s" role="listitem" href="examples/najm/" target="_blank" rel="noopener" aria-label="Open the NAJM example, a Moroccan fashion brand landing page, in a new tab">
|
||||
<div class="ex-card__thumb" aria-hidden="true">
|
||||
<img src="_tests/_thumbs/najm.jpg?v=22" alt="" loading="lazy" width="1600" height="1000" />
|
||||
<img src="_tests/_thumbs/najm.jpg?v=23" alt="" loading="lazy" width="1600" height="1000" />
|
||||
</div>
|
||||
<p class="ex-card__prompt"><span class="ex-card__prompt-mark">$</span> /hallmark build a Moroccan fashion brand landing page</p>
|
||||
</a>
|
||||
|
||||
<a class="ex-card" style="--scroll-dur:1.7s" role="listitem" href="_tests/11-soroe-ceramics/" target="_blank" rel="noopener" aria-label="Open the Soroe example, a small ceramics studio site, in a new tab">
|
||||
<div class="ex-card__thumb" aria-hidden="true">
|
||||
<img src="_tests/_thumbs/soroe.jpg?v=22" alt="" loading="lazy" width="1600" height="1000" />
|
||||
</div>
|
||||
<p class="ex-card__prompt"><span class="ex-card__prompt-mark">$</span> /hallmark build a small ceramics studio site</p>
|
||||
</a>
|
||||
|
||||
<a class="ex-card" style="--scroll-dur:3.5s" role="listitem" href="examples/hyperlane/" target="_blank" rel="noopener" aria-label="Open the Hyperlane example, a developer infrastructure landing page, in a new tab">
|
||||
<div class="ex-card__thumb" aria-hidden="true">
|
||||
<img src="_tests/_thumbs/hyperlane.jpg?v=22" alt="" loading="lazy" width="1600" height="1000" />
|
||||
<img src="_tests/_thumbs/hyperlane.jpg?v=23" alt="" loading="lazy" width="1600" height="1000" />
|
||||
</div>
|
||||
<p class="ex-card__prompt"><span class="ex-card__prompt-mark">$</span> /hallmark build a developer infrastructure landing page</p>
|
||||
</a>
|
||||
@@ -1113,7 +1127,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script type="module" src="js/main.js?v=22"></script>
|
||||
<script type="module" src="js/main.js?v=23"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -130,6 +130,7 @@ The outline starts transparent at 2 px so when the focus ring appears, the box g
|
||||
## Modals and overlays
|
||||
|
||||
- Use the native `<dialog>` element. It handles focus trap, escape to close, and `::backdrop` styling for free.
|
||||
- **Centre it explicitly.** A `<dialog>` opened with `showModal()` centres on its own — but the moment you add custom positioning (a `margin-top`, a `top`, a stray `transform`) it can snap to the viewport's top-left corner. Pin it: `position: fixed; inset: 0; margin: auto; height: fit-content; max-height: min(80vh, 40rem);`. A non-`<dialog>` overlay centres via its container instead: `position: fixed; inset: 0; display: grid; place-items: center;`. Never ship a modal or command palette stuck in the corner.
|
||||
- Set `inert` on the page content behind a modal so tab order doesn't leak.
|
||||
- Close on: escape key, backdrop click, explicit close button.
|
||||
- First focus goes to the first interactive element, not the close button.
|
||||
|
||||
@@ -41,11 +41,12 @@ Layout is where "AI-generated" gets caught. Equal columns, everything centred, e
|
||||
|
||||
## Asymmetry techniques
|
||||
|
||||
- **Wide left margin.** Treat the left as a permanent negative space — narrow column of labels, wide column of content. **Labels must NOT be section eyebrows / numbers paired with the heading** — that's gate-66-banned. Reserve this technique for body-level micro-labels (caption, footnote, date) alongside body copy.
|
||||
- **Wide left margin.** Treat the left as a permanent negative space — narrow column of labels, wide column of content. **Labels must NOT be section eyebrows / numbers paired with the heading** — that's gate-54-banned. Reserve this technique for body-level micro-labels (caption, footnote, date) alongside body copy.
|
||||
- **Hanging headers.** ⚠️ **Opt-in only.** Section labels sit in the left margin; content flows to the right. Permitted only when the user explicitly asks for an editorial / hanging-header layout AND no eyebrow / number / chapter tag sits in the left margin. The eyebrow-left / heading-right pattern is banned by slop-test gate 54 — it's the most reliable templated-editorial AI tell. Default to a stacked single-column section head.
|
||||
- **Offset grids.** Odd columns wider than even. Or the other way.
|
||||
- **Grid-breaks.** One element that deliberately extends past a column boundary: a pull-quote, a photograph, a rule, a number.
|
||||
- **Generous top, tight bottom** (or vice-versa). Sections don't need to be evenly padded.
|
||||
- **Alignment coherence.** A section head's horizontal alignment should be a deliberate choice that *coheres* with the body it introduces — match it (left head over left-flush body; centred head over symmetric / centred body) or break from it on purpose (a head that escapes an asymmetric grid). What reads as an AI mistake is the *accidental* mismatch: a narrow head block auto-centred (`margin-inline: auto` plus a `max-width` / `ch` cap) left floating over full-width, left-flush content beneath it. Centred, hanging, bottom-aligned, and asymmetric heads all stay on the table — the guard is intentionality, not uniformity.
|
||||
|
||||
## Depth
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
The hero is a giant number — a metric, a count, a percentage. Everything that follows supports or qualifies it. Data is the narrative.
|
||||
|
||||
- **Heading:** numeric display (8–12 rem), tabular figures, small qualifier line below.
|
||||
- **Heading:** a large numeric display (8–12 rem, tabular figures) **paired with a worded headline** — the figure is the biggest element, but it is *never the hero's only words*. State what the number means right beside or beneath it as a real headline, not a tiny label. Read the figure and its words together (*"4 seconds. From the alert link to the slow span."*).
|
||||
- **Body:** sections each anchored by a supporting stat or chart.
|
||||
- **Divider:** hairline rules between stat blocks; tabular-nums everywhere.
|
||||
- **Button:** outlined chip aligned beneath the qualifier.
|
||||
@@ -13,6 +13,8 @@ Reach for it when the brief is "we have proof in numbers" — enterprise/B2B, fu
|
||||
|
||||
Avoid for products without a defensible single metric. A fake big number is worse than no number.
|
||||
|
||||
**The hero header always contains words.** The lead figure never stands alone as the headline — pair it with a worded line that completes it. A bare number as the dominant hero text fails slop-test gate 46.
|
||||
|
||||
Reference: Ahrefs, Stripe Sessions stat blocks, climate-impact dashboards, venture firm portfolio pages.
|
||||
|
||||
**Sample opening lines** (imitate the *specificity*, not the wording — the number does the work):
|
||||
|
||||
@@ -143,7 +143,7 @@ The CSS stamp at Step 6 should record the result alongside contrast: `· nav: N#
|
||||
|
||||
Universal — apply to every genre. The page must not invent facts about the user's product, team, or market.
|
||||
|
||||
46. **Invented metric.** Does the page contain any quantitative claim — "10× faster", "saves 5 hours per week", "trusted by 50,000+ teams", "99.9 % uptime", "+47 % conversion" — that the user did not supply, that has no source, and that the model fabricated to fill a stat-led layout, comparison row, or proof bar? If yes, fail. The fix is one of: replace the number with `—` and a labelled grey block, replace it with a question to the user ("metric to confirm"), or rebuild the section without the proof slot. Stat-led macrostructures are slop the moment their stats become decorative. *(See [anti-patterns.md § Invented metrics](anti-patterns.md).)*
|
||||
46. **Invented metric.** Does the page contain any quantitative claim — "10× faster", "saves 5 hours per week", "trusted by 50,000+ teams", "99.9 % uptime", "+47 % conversion" — that the user did not supply, that has no source, and that the model fabricated to fill a stat-led layout, comparison row, or proof bar? If yes, fail. The fix is one of: replace the number with `—` and a labelled grey block, replace it with a question to the user ("metric to confirm"), or rebuild the section without the proof slot. Stat-led macrostructures are slop the moment their stats become decorative. **A stat is also never the hero's *sole* headline** — a lead figure carries the hero only alongside a worded headline; a giant number with no words as the dominant hero text is a bare-number tell, so pair it with a line that says what it means. *(See [anti-patterns.md § Invented metrics](anti-patterns.md).)*
|
||||
|
||||
## Re-drawn UI chrome
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@ Where does a section's title live in space? Pick one per page.
|
||||
| **Numbered display** | ⚠️ **Opt-in only — never default.** "01." with a rule line and the heading right beside it. Procedural, sequenced. Banned for default SaaS / consumer / dev-tool pages by slop-test gate 54 (the tag-beside-heading pattern is a templated tell). Permitted only when the user explicitly asks for ordinal / chaptered numbering AND the macrostructure is Long Document, Manifesto, or Catalogue numbered. Even then, prefer the stacked variant: number on its own line above the heading. | Rauno Freiberg's portfolio — when the user explicitly invokes that voice. |
|
||||
| **Inline with body** | No section break — the heading emerges from the paragraph flow. Conversational. | Medium articles; long-form essays. |
|
||||
|
||||
**Coherence, not a fixed side.** Any of these placements is fair game — the head's alignment just has to *cohere* with the body it introduces rather than mismatch it by accident. A narrow centred head stranded over full-width, left-flush content (often a `margin-inline:auto` head above a wide grid) is the tell — not centring or left-flushing as such. See [`layout-and-space.md`](layout-and-space.md) § Asymmetry techniques.
|
||||
|
||||
### 2. Body composition
|
||||
|
||||
How does long-form content lay out beyond "single column at 65ch"?
|
||||
|
||||
@@ -42,11 +42,11 @@ Loud + neon. 90s zine, summer, electric. Lime against magenta — the hardest co
|
||||
- `--color-ink: oklch(20% 0.07 145)` — deep forest
|
||||
- `--color-ink-2: oklch(32% 0.05 140)`
|
||||
- `--color-muted: oklch(48% 0.04 130)`
|
||||
- `--color-rule: oklch(48% 0.24 350)` — magenta rules
|
||||
- `--color-rule: oklch(28% 0.28 350)` — deep magenta rules
|
||||
- `--color-accent: oklch(82% 0.20 130)` — chartreuse-lime
|
||||
- `--color-accent-2: oklch(50% 0.24 350)` — magenta
|
||||
- `--color-accent-2: oklch(28% 0.28 350)` — deep magenta (reads AA on paper, and as text)
|
||||
- `--color-accent-ink: oklch(20% 0.07 145)`
|
||||
- `--color-focus: oklch(50% 0.24 350)`
|
||||
- `--color-focus: oklch(28% 0.28 350)`
|
||||
|
||||
**When to pick:** zine collectives · summer drops · skate / DIY culture · briefs that want LOUD without referencing music.
|
||||
|
||||
@@ -78,11 +78,11 @@ Cool + cool. Dusk warmth, cyan + plum. Late-night booth, blue hour, podcast stud
|
||||
- `--color-ink: oklch(20% 0.05 270)` — deep navy-black
|
||||
- `--color-ink-2: oklch(32% 0.045 265)`
|
||||
- `--color-muted: oklch(48% 0.04 260)`
|
||||
- `--color-rule: oklch(42% 0.16 320)` — plum rules
|
||||
- `--color-rule: oklch(24% 0.18 320)` — deep plum rules
|
||||
- `--color-accent: oklch(78% 0.18 220)` — cyan
|
||||
- `--color-accent-2: oklch(42% 0.16 320)` — plum
|
||||
- `--color-accent-2: oklch(24% 0.18 320)` — deep plum (reads AA on paper, and as text)
|
||||
- `--color-accent-ink: oklch(20% 0.05 270)`
|
||||
- `--color-focus: oklch(42% 0.16 320)`
|
||||
- `--color-focus: oklch(24% 0.18 320)`
|
||||
|
||||
**When to pick:** late-night radio · podcasts about anything · music + atmosphere · briefs mentioning "late", "after dark", "blue hour", "moonlight".
|
||||
|
||||
@@ -96,11 +96,11 @@ Cool + warm. Turquoise against coral. Summer pool, motel sign, vacation.
|
||||
- `--color-ink: oklch(20% 0.06 200)` — deep teal
|
||||
- `--color-ink-2: oklch(32% 0.05 198)`
|
||||
- `--color-muted: oklch(48% 0.04 195)`
|
||||
- `--color-rule: oklch(60% 0.20 35)` — coral rules
|
||||
- `--color-rule: oklch(36% 0.24 35)` — deep coral rules
|
||||
- `--color-accent: oklch(72% 0.16 195)` — turquoise
|
||||
- `--color-accent-2: oklch(60% 0.20 35)` — coral
|
||||
- `--color-accent-2: oklch(36% 0.24 35)` — deep coral (reads AA on paper, and as text)
|
||||
- `--color-accent-ink: oklch(20% 0.06 200)`
|
||||
- `--color-focus: oklch(60% 0.20 35)`
|
||||
- `--color-focus: oklch(36% 0.24 35)`
|
||||
|
||||
**When to pick:** summer brands · vacation / hospitality · skate / surf / pool · briefs mentioning summer, beach, motel, sun.
|
||||
|
||||
@@ -126,6 +126,19 @@ Warm + cool. Amber-gold against slate-blue. 1950s journalism, print shop, broads
|
||||
|
||||
The diversification log (`/.hallmark/log.json`) records the drop alongside the theme: `"theme": "carnival", "drop": "studio-night"`. **A new Carnival build picks a drop that hasn't appeared in the last 3 entries.** If only Cold Snap is in the log, any of the other five is valid. If the brief contains a strong drop signal (see "when to pick" above), honour the signal even if it tightens diversification.
|
||||
|
||||
### Pick the drop by domain first
|
||||
|
||||
Match the brief's **domain** before reaching for the loudest palette:
|
||||
|
||||
- food / drink / hospitality / street market → **Diner Sign** (or Cold Snap)
|
||||
- independent music / labels / EPs → **Cold Snap** (or Pressroom)
|
||||
- late-night / radio / podcast / after-dark → **Studio Night**
|
||||
- summer / pool / beach / vacation → **Aqua Park**
|
||||
- zine / skate / DIY / deliberately chaotic → **Citrus Riot**
|
||||
- journalism / newsletter / editorial → **Pressroom**
|
||||
|
||||
Citrus Riot and Aqua Park are the highest-chroma drops — reach for them when the brief genuinely *wants* maximum loudness, not as a default. A food market reads better in Diner Sign than in lime-on-magenta.
|
||||
|
||||
### When to construct a custom drop instead
|
||||
|
||||
If the brief explicitly names a brand colour that doesn't fit any catalog drop — e.g. *"our brand is teal and beige"* — route to the **custom theme** branch (see `custom-theme.md`) instead of stretching a Carnival drop. Drops are curated, not infinite. Six is the right number for now.
|
||||
@@ -162,7 +175,7 @@ The theme's seven tics. A Carnival build should exhibit at least five of them.
|
||||
|
||||
4. **Hard-offset drop shadow.** Every card, CTA, and image gets `box-shadow: 4px 4px 0 var(--color-ink)`. No soft shadows ever. The shadow is **flat ink, offset**. This is the single most-recognisable Carnival move.
|
||||
|
||||
5. **All-caps headlines with Big Shoulders variable-width** pulled wide. `font-variation-settings: "wdth" 110, "wght" 800;` for hero headlines. Tracking 0.04em. Looks like a marquee poster.
|
||||
5. **All-caps headlines with Big Shoulders variable-width, tightly tracked.** `font-variation-settings: "wdth" 110, "wght" 800;`. Track them **tight**, not loose: `letter-spacing: -0.005em` on the hero word, `0.02em` on section heads. The loose `0.04em` look reads as AI-spread and is reserved **only** for the marquee banner (where horizontal spread is the point). Keep line-height tight too: `0.82` for single hero words, `0.92` for multi-line heads. The effect is a marquee poster set by a typographer, not a stretched default.
|
||||
|
||||
6. **Halftone pattern fills** in placeholder image regions. Pure CSS, no images:
|
||||
|
||||
@@ -196,6 +209,10 @@ Carnival's dense visual language has known traps. Read this list **before** writ
|
||||
|
||||
5. **Big Shoulders width axis used without `font-variation-settings` → no width variation.** Setting `font-stretch` on Google's Big Shoulders Display does not work — only `font-variation-settings: "wdth" 110` does. If you see a hero that's supposed to be width-110 but renders at width-100, this is the bug.
|
||||
|
||||
6. **Section head detached from, or mis-centred over, its body.** The section head (heading + any lede) sits **tight above** the content it names — about `1.25rem` (`--section-head-gap`) — and **shares that content's alignment**. Don't cap the head at a narrow `max-width` / `ch` and then `margin-inline: auto` it: that strands a centred head over a full-width, left-flush grid (the classic accidental mismatch). Left-flush grid → left-flush head. (See the alignment-coherence note in `layout-and-space.md`.)
|
||||
|
||||
7. **Newsletter / CTA button not aligned with the input beside it.** When a button sits next to a form input in one row, they must share the **same height, vertical padding, and border width**, and the row must use `align-items: center` — otherwise the button floats above the input's baseline. If a label is stacked above the input it makes that column taller; centre the row on the *control*, not the column (give the field `align-items: center` or pull the label out of the flex row).
|
||||
|
||||
## Macrostructure affinity
|
||||
|
||||
**Carnival loves these.**
|
||||
@@ -273,7 +290,7 @@ When emitting a Carnival page, the first 12 lines of CSS should establish the fo
|
||||
|
||||
```css
|
||||
body { background: var(--color-paper); color: var(--color-ink); font-family: var(--font-body); }
|
||||
h1, h2 { font-family: var(--font-display); font-weight: 800; font-variation-settings: "wdth" 110; letter-spacing: 0.04em; text-transform: uppercase; }
|
||||
h1, h2 { font-family: var(--font-display); font-weight: 800; font-variation-settings: "wdth" 110; letter-spacing: 0.02em; line-height: 0.92; text-transform: uppercase; } /* hero word goes tighter: letter-spacing: -0.005em; line-height: 0.82 */
|
||||
.card, .cta { box-shadow: 4px 4px 0 var(--color-ink); border: 2px solid var(--color-ink); }
|
||||
.section:nth-of-type(odd) { background: var(--color-accent); color: var(--color-accent-ink); }
|
||||
.section:nth-of-type(even) { background: var(--color-accent-2); color: var(--color-paper); }
|
||||
|
||||