Files
Youssef a5593bbca1 Slop test · consolidate 70 gates → 57 (merge redundant) and resync every reference
Merge the clear duplicates (gradient text into the gradient gate, macrostructure-
repeat into the structural-fingerprint gate, the overflow-x:clip mandate into the
horizontal-scroll gate, emoji-icon into the icon-libraries gate), collapse the five
contrast gates to two, the five input-state gates to one, and the four hero gates to
two. No coverage lost. Renumber 1–57 and update every gate-number and gate-total
reference across SKILL.md, the reference docs, the landing copy, and the example builds.
2026-06-02 18:34:47 +01:00

599 lines
21 KiB
CSS
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* Hallmark · route: custom · structure: corner-cropped broadsheet — masthead rule, off-axis fold slogan, five hand-numbered broken-grid manifesto planks, a skewed taped tally marquee, a printer's colophon · idea: "a poster you scroll through, printed in two off-register riso passes that snap into register on load" · paper: oklch(94% 0.012 85) · accent: oklch(58% 0.205 32) · display: Big Shoulders Display · body: Fraunces · gates: all-pass · v1.1 */
/* Hallmark · pre-emit critique: P5 H5 E5 S5 R4 V5 */
/* Hallmark · route: custom · axes: bone-newsprint / condensed-poster-grotesque / riso-vermilion 32°
* contrast: pass (46–50) · nav: bespoke masthead · footer: bespoke colophon · slop: pass (51–55)
* honest: pass (56) · chrome: pass (57) · tokens: pass (58) · responsive: pass (59) · icons: pass (60) · mobile: pass (36, 59, 61–69)
*/
@import url("./tokens.css");
/* ───────────────────────── Reset ───────────────────────── */
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; overflow-x: clip; }
html {
background: var(--color-paper);
color: var(--color-ink-2);
font-family: var(--font-body);
font-size: var(--text-base);
line-height: var(--lh-body);
font-optical-sizing: auto;
font-feature-settings: "kern", "liga", "onum";
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
img, svg { display: block; max-width: 100%; }
h1, h2, h3, p, figure, blockquote, ul, ol { margin: 0; }
a {
color: inherit;
text-decoration: none;
text-underline-offset: 3px;
text-decoration-thickness: 1px;
text-decoration-color: var(--color-accent);
}
a:hover { text-decoration: underline; }
button { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; }
::selection { background: var(--color-accent); color: var(--color-accent-ink); }
:focus-visible {
outline: 2px solid var(--color-focus);
outline-offset: 3px;
border-radius: var(--radius-xs);
}
/* Skip link — printed tab that drops in on focus */
.skip-link {
position: absolute;
left: var(--space-md);
top: -3rem;
z-index: 60;
background: var(--color-ink);
color: var(--color-paper);
font-family: var(--font-mono);
font-size: var(--text-xs);
letter-spacing: 0.04em;
text-transform: uppercase;
padding: var(--space-xs) var(--space-md);
transition: top var(--dur-fast) var(--ease-out);
}
.skip-link:focus-visible { top: var(--space-md); }
/* ─────────────── The sheet: paper grain + halftone dot ─────────────── */
/* Two fixed, decorative layers — pure CSS, no animation. Footprint is full
sheet but each is a faint single-ink texture, not an accent fill. */
body::before,
body::after {
content: "";
position: fixed;
inset: 0;
pointer-events: none;
z-index: 0;
}
body::before { /* halftone dot grid */
background-image: radial-gradient(var(--dot-ink) 0.9px, transparent 1px);
background-size: 6px 6px;
background-position: 0 0;
}
body::after { /* directional grain — fine diagonal hatch */
background-image:
repeating-linear-gradient(115deg, var(--grain-ink) 0 1px, transparent 1px 3px);
mix-blend-mode: multiply;
opacity: 0.7;
}
/* ─────────────── Corner crop marks + edition stamp (fixed) ─────────────── */
.press-marks { position: fixed; inset: 0; z-index: 40; pointer-events: none; }
.press-marks .crop {
position: absolute;
width: 26px;
height: 26px;
border-color: var(--color-ink);
border-style: solid;
}
.press-marks .crop--tl { top: var(--crop); left: var(--crop); border-width: 2px 0 0 2px; }
.press-marks .crop--tr { top: var(--crop); right: var(--crop); border-width: 2px 2px 0 0; }
.press-marks .crop--bl { bottom: var(--crop); left: var(--crop); border-width: 0 0 2px 2px; }
.press-marks .crop--br { bottom: var(--crop); right: var(--crop); border-width: 0 2px 2px 0; }
/* registration target — bottom-left, the printer's bullseye */
.reg-target {
position: fixed;
left: calc(var(--crop) + 34px);
bottom: calc(var(--crop) - 2px);
z-index: 40;
width: 22px; height: 22px;
pointer-events: none;
}
.reg-target::before,
.reg-target::after {
content: ""; position: absolute; background: var(--color-ink);
}
.reg-target::before { left: 50%; top: 0; bottom: 0; width: 1px; transform: translateX(-50%); }
.reg-target::after { top: 50%; left: 0; right: 0; height: 1px; transform: translateY(-50%); }
.reg-target span {
position: absolute; inset: 4px; border: 1px solid var(--color-accent-deep);
border-radius: 50%;
}
/* Hide fixed press furniture on small screens — it crowds the slogan */
@media (max-width: 40rem) {
.reg-target { display: none; }
.press-marks .crop { width: 16px; height: 16px; }
}
/* ───────────────────────── Shell ───────────────────────── */
.sheet {
position: relative;
z-index: 1;
width: 100%;
max-width: var(--sheet-max);
margin-inline: auto;
padding-inline: var(--sheet-pad);
}
/* ════════════════════════ Masthead (bespoke) ════════════════════════ */
.masthead {
position: relative;
z-index: 1;
border-bottom: var(--rule-ink);
margin-top: clamp(var(--space-lg), 4vw, var(--space-2xl));
}
.masthead__bar {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
align-items: end;
gap: var(--space-md) var(--space-lg);
padding-block: var(--space-sm) var(--space-md);
}
/* Narrow: stack wordmark over edition so neither column gets crushed */
@media (max-width: 34rem) {
.masthead__bar { grid-template-columns: minmax(0, 1fr); align-items: start; }
.masthead__edition { text-align: left; }
}
.masthead__mark {
font-family: var(--font-display);
font-weight: 800;
font-size: clamp(1.5rem, 6vw, 2.25rem); /* scales down so it never breaks per-glyph */
line-height: 1.02;
letter-spacing: 0.01em;
text-transform: uppercase;
color: var(--color-ink);
overflow-wrap: break-word; /* wraps at spaces; word-internal break only as last resort */
text-wrap: balance;
min-width: 0;
}
.masthead__mark .no {
color: var(--color-accent-deep);
font-family: var(--font-mono);
font-weight: 600;
font-size: 0.62em;
letter-spacing: 0;
vertical-align: 0.18em;
}
.masthead__edition {
font-family: var(--font-mono);
font-size: var(--text-xs);
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--color-ink-mute);
text-align: right;
line-height: 1.5;
}
.masthead__nav {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: clamp(var(--space-md), 3vw, var(--space-xl));
padding-block: var(--space-sm);
border-top: var(--rule-hair);
font-family: var(--font-mono);
font-size: var(--text-xs);
letter-spacing: 0.08em;
text-transform: uppercase;
}
.masthead__nav a { white-space: nowrap; line-height: 1; color: var(--color-ink); }
.masthead__nav a:hover { color: var(--color-accent-deep); }
.masthead__nav .dot { color: var(--color-accent-deep); }
/* Sub-line: the broadsheet strapline, runs under the rule */
.strapline {
font-family: var(--font-mono);
font-size: var(--text-xs);
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--color-ink-mute);
padding-block: var(--space-sm);
}
/* ════════════════════ The fold — hero slogan ════════════════════ */
.fold {
position: relative;
padding-block: clamp(var(--space-lg), 4vw, var(--space-2xl))
clamp(var(--space-3xl), 13vw, var(--space-5xl)); /* trimmed top so the slogan clears the fold; heavy bottom keeps the asymmetry */
}
.fold__inner {
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: var(--space-xl);
}
@media (min-width: 60rem) {
.fold__inner { grid-template-columns: minmax(0, 1.55fr) minmax(0, 0.9fr); align-items: end; }
}
.fold__slogan {
font-family: var(--font-display);
font-weight: 800;
font-size: var(--text-fold);
line-height: var(--lh-slab);
letter-spacing: -0.01em;
text-transform: uppercase;
color: var(--color-ink);
overflow-wrap: anywhere;
min-width: 0;
/* pull off the left margin into the crop edge */
margin-left: clamp(-0.5rem, -2vw, -1.75rem);
}
.fold__slogan .ln { display: block; }
.fold__slogan .ln--red { color: var(--color-accent); }
.fold__slogan .ln--indent { padding-left: clamp(1.5rem, 9vw, 7rem); } /* broken grid */
/* Off-register ghost: a teal/vermilion second pass set just behind the ink. */
.ghost { position: relative; }
.ghost > .ink { position: relative; z-index: 1; display: block; }
.ghost > .reg {
position: absolute;
inset: 0;
z-index: 0;
color: var(--color-accent);
opacity: 0.42;
transform: translate(var(--reg-shift), calc(var(--reg-shift) * 0.6));
pointer-events: none;
user-select: none;
}
.ghost > .reg.reg--teal { color: var(--color-teal); transform: translate(calc(var(--reg-shift) * -0.7), calc(var(--reg-shift) * 0.4)); }
.fold__lede {
font-size: var(--text-lg);
line-height: var(--lh-body);
color: var(--color-ink-2);
max-width: 38ch;
border-left: var(--rule-press);
padding-left: var(--space-md);
}
.fold__lede strong { color: var(--color-ink); font-weight: 600; }
.fold__lede .em { color: var(--color-accent-deep); font-style: normal; font-weight: 600; }
.fold__meta {
margin-top: var(--space-lg);
font-family: var(--font-mono);
font-size: var(--text-xs);
letter-spacing: 0.05em;
text-transform: uppercase;
color: var(--color-ink-mute);
display: flex;
gap: var(--space-lg);
flex-wrap: wrap;
}
/* ════════════════════ Manifesto spine — broken grid ════════════════════ */
.spine {
border-top: var(--rule-ink);
padding-top: clamp(var(--space-xl), 5vw, var(--space-3xl));
}
.spine__head {
display: block; /* gate 54 — eyebrow + heading single column */
margin-bottom: clamp(var(--space-xl), 6vw, var(--space-3xl));
}
.spine__eyebrow {
font-family: var(--font-mono);
font-size: var(--text-xs);
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--color-accent-deep);
}
.spine__title {
margin-top: var(--space-sm);
font-family: var(--font-display);
font-weight: 700;
font-size: var(--text-3xl);
line-height: var(--lh-tight);
letter-spacing: 0.005em;
text-transform: uppercase;
color: var(--color-ink);
overflow-wrap: anywhere;
min-width: 0;
max-width: 22ch;
}
.plank {
position: relative;
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: var(--space-md) clamp(var(--space-lg), 4vw, var(--space-2xl));
padding-block: clamp(var(--space-xl), 5vw, var(--space-3xl));
border-bottom: var(--rule-hair);
}
@media (min-width: 56rem) {
.plank { grid-template-columns: minmax(0, 6.2rem) minmax(0, 1fr); align-items: start; }
}
.plank__num {
font-family: var(--font-mono);
font-weight: 600;
font-size: var(--text-lg);
color: var(--color-accent-deep);
letter-spacing: 0.02em;
line-height: 1;
padding-top: 0.4em;
}
.plank__num::after { content: ""; display: block; width: 2.5rem; height: 2px; background: var(--color-rule); margin-top: var(--space-sm); }
.plank__slab {
font-family: var(--font-display);
font-weight: 800;
font-size: var(--text-slab);
line-height: var(--lh-slab);
letter-spacing: -0.005em;
text-transform: uppercase;
color: var(--color-ink);
overflow-wrap: anywhere;
min-width: 0;
}
.plank__slab .red { color: var(--color-accent); }
.plank__slab .balloon {
font-size: 1.32em; /* a numeral that balloons to fill the column */
letter-spacing: -0.02em;
}
/* per-plank broken-grid moves */
.plank--hang .plank__slab { margin-left: clamp(-0.4rem, -2.5vw, -2.25rem); }
.plank--drop .plank__slab .drop { display: inline-block; transform: translateY(0.16em); } /* a clause drops a baseline */
.plank--wide { }
.plank__gloss {
font-size: var(--text-base);
line-height: var(--lh-body);
color: var(--color-ink-2);
max-width: 52ch;
margin-top: var(--space-md);
}
.plank__gloss em { font-style: italic; color: var(--color-ink); } /* italic allowed in body */
/* ════════════════════ Tape marquee — the one persistent motion ════════════════════ */
.tally {
position: relative;
margin-block: clamp(var(--space-2xl), 7vw, var(--space-4xl));
background: var(--color-paper-3);
border-block: var(--rule-press);
/* skewed, taped-on fly-poster band */
transform: rotate(-1.4deg);
}
.tally__strip {
overflow: hidden;
padding-block: clamp(var(--space-md), 2.5vw, var(--space-xl));
/* Mask uses alpha only — the colour is irrelevant; tinted-ink keeps the
token discipline clean (no pure #000 anywhere on the page). */
-webkit-mask-image: linear-gradient(90deg, transparent, var(--mask-opaque) 7%, var(--mask-opaque) 93%, transparent);
mask-image: linear-gradient(90deg, transparent, var(--mask-opaque) 7%, var(--mask-opaque) 93%, transparent);
}
.tally__track {
display: flex;
gap: clamp(var(--space-lg), 4vw, var(--space-2xl));
width: max-content;
white-space: nowrap;
align-items: baseline;
animation: tape-scroll var(--dur-tape) linear infinite;
will-change: transform;
}
.tally__item {
display: inline-flex;
align-items: baseline;
gap: var(--space-sm);
font-family: var(--font-display);
font-weight: 700;
font-size: clamp(1.5rem, 5vw, 3rem);
line-height: 1;
text-transform: uppercase;
color: var(--color-ink);
}
.tally__item .n { color: var(--color-accent); font-family: var(--font-mono); font-weight: 600; }
.tally__item .n--teal { color: var(--color-teal); }
.tally__item .sep { color: var(--color-ink-mute); font-family: var(--font-mono); font-weight: 400; }
/* torn-tape ends */
.tally::before, .tally::after {
content: ""; position: absolute; top: -7px; bottom: -7px; width: clamp(48px, 9vw, 96px);
background: var(--color-paper-2);
opacity: 0.92;
transform: rotate(2.6deg);
z-index: 2;
}
.tally::before { left: -6px; }
.tally::after { right: -6px; transform: rotate(-2.6deg); }
.tally__cap {
position: absolute; z-index: 3; top: 50%; transform: translateY(-50%) rotate(1.4deg);
font-family: var(--font-mono); font-size: var(--text-xs); letter-spacing: 0.1em;
text-transform: uppercase; color: var(--color-ink-2);
}
.tally__cap--l { left: clamp(0.5rem, 2vw, 1.75rem); }
/* ════════════════════ The bill — what / bring / find ════════════════════ */
.bill {
border-top: var(--rule-ink);
padding-block: clamp(var(--space-2xl), 6vw, var(--space-3xl));
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: clamp(var(--space-lg), 4vw, var(--space-2xl));
}
@media (min-width: 52rem) {
.bill { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.bill__line { position: relative; }
.bill__k {
font-family: var(--font-mono);
font-size: var(--text-xs);
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--color-accent-deep);
display: block;
margin-bottom: var(--space-sm);
}
.bill__v {
font-family: var(--font-display);
font-weight: 700;
font-size: clamp(2rem, 5.5vw, 3.5rem);
line-height: var(--lh-tight);
text-transform: uppercase;
color: var(--color-ink);
overflow-wrap: anywhere;
min-width: 0;
}
.bill__v .red { color: var(--color-accent); }
.bill__note { margin-top: var(--space-sm); color: var(--color-ink-2); font-size: var(--text-base); max-width: 30ch; }
/* The pledge CTA — set as a printed call, not a pill farm */
.pledge {
border-top: var(--rule-ink);
border-bottom: var(--rule-ink);
padding-block: clamp(var(--space-2xl), 7vw, var(--space-4xl));
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: var(--space-xl);
}
@media (min-width: 58rem) {
.pledge { grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr); align-items: end; }
}
.pledge__call {
font-family: var(--font-display);
font-weight: 800;
font-size: var(--text-3xl);
line-height: var(--lh-tight);
letter-spacing: 0.005em;
text-transform: uppercase;
color: var(--color-ink);
overflow-wrap: anywhere;
min-width: 0;
}
.pledge__call .red { color: var(--color-accent); }
.pledge__action { justify-self: start; }
@media (min-width: 58rem) { .pledge__action { justify-self: end; } }
.btn {
display: inline-flex;
align-items: center;
gap: var(--space-sm);
min-height: 48px;
padding: 0 var(--space-lg);
background: var(--color-accent-btn);
color: var(--color-accent-ink);
font-family: var(--font-mono);
font-weight: 600;
font-size: var(--text-sm);
letter-spacing: 0.08em;
text-transform: uppercase;
line-height: 1;
border: 2px solid var(--color-accent-btn);
white-space: nowrap;
transition: background var(--dur-base) var(--ease-out),
color var(--dur-base) var(--ease-out),
transform var(--dur-fast) var(--ease-out);
}
.btn:hover { background: var(--color-ink); border-color: var(--color-ink); color: var(--color-paper); text-decoration: none; }
.btn:active { transform: translateY(1px); }
.btn:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 3px; }
.btn:disabled { opacity: 0.55; cursor: not-allowed; }
.btn .arw { font-family: var(--font-mono); }
.pledge__small {
margin-top: var(--space-md);
font-family: var(--font-mono);
font-size: var(--text-xs);
letter-spacing: 0.04em;
text-transform: uppercase;
color: var(--color-ink-mute);
}
/* ════════════════════ Colophon footer (bespoke) ════════════════════ */
.colophon {
position: relative;
z-index: 1;
padding-block: clamp(var(--space-xl), 5vw, var(--space-3xl));
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: var(--space-lg) var(--space-2xl);
}
@media (min-width: 50rem) {
.colophon { grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr); align-items: start; }
}
.colophon__mark {
font-family: var(--font-display);
font-weight: 800;
font-size: var(--text-xl);
line-height: 1;
text-transform: uppercase;
color: var(--color-ink);
}
.colophon__body {
margin-top: var(--space-md);
font-size: var(--text-base);
color: var(--color-ink-2);
max-width: 56ch;
}
.colophon__meta {
font-family: var(--font-mono);
font-size: var(--text-xs);
letter-spacing: 0.05em;
text-transform: uppercase;
color: var(--color-ink-mute);
line-height: 1.9;
}
.colophon__meta a:hover { color: var(--color-accent-deep); }
.colophon__rule { grid-column: 1 / -1; border-top: var(--rule-hair); padding-top: var(--space-md);
font-family: var(--font-mono); font-size: var(--text-xs); letter-spacing: 0.1em;
text-transform: uppercase; color: var(--color-ink-mute);
display: flex; justify-content: space-between; flex-wrap: wrap; gap: var(--space-md); }
/* ════════════════════ One orchestrated motion: registration sweep ════════════════════ */
/* THE PRINCIPLE: the base state is fully PRINTED (ink opaque, in register).
Motion is an *entrance* layered on top — a one-shot CSS animation that runs
the ink in from off-register on load and ends `forwards` in the same printed
state. No JS, no class toggle: if the animation never plays (no-JS, throttled
rAF, reduced-motion), the slogan is already visible. This is why the headline
can never get stranded invisible. Stagger comes from per-element --i delay. */
/* The INK (the readable text) is ALWAYS opaque and in register — it carries no
animation, so legibility can never depend on an animation clock that might be
paused, throttled, or pre-start. Only the coloured off-register GHOST animates:
on load it slides from a wider offset into its resting register-miss. If that
animation never advances, the only consequence is the ghost sits at its start
offset — the headline itself is fully readable regardless. */
.ghost > .ink { opacity: 1; }
.ghost > .reg { opacity: 0.42; }
@media (prefers-reduced-motion: no-preference) {
.ghost > .reg {
animation: reg-settle var(--dur-reg) var(--ease-snap) both;
animation-delay: calc(var(--i, 0) * 90ms);
}
}
/* The ghost prints first (further adrift, fainter) then settles toward the ink —
the two-pass registration "snap". Ends at the same resting offset the static
`.reg`/`.reg--teal` rules define, so motion and no-motion converge visually. */
@keyframes reg-settle {
from { opacity: 0.16; transform: translate(calc(var(--reg-shift) * 2.1), calc(var(--reg-shift) * 1.3)); }
}
/* Reduced-motion: no entrance — the printed base state (ink + resting ghost) stands. */
@media (prefers-reduced-motion: reduce) {
.tally__track { animation: none; }
}
@keyframes tape-scroll {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}
/* Utility */
.u-vh { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }