Cleanup — drop ~3.5 MB of orphan media + dead test pages
Orphan thumbs (referenced nowhere): 8 example PNGs, 5 hero JPGs, 4 verb-* PNGs, audit-slop.png, 2.2 MB crypto-example.mp4. Dead pages: refine verb test (verb removed earlier), v0 example specimens (_example-bakery, _example-cmdk), docs/img theme PNGs, skill/_tests/nav-footer-variety. Updated verbs/README and ROADMAP to drop the now-broken references. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@@ -165,7 +165,6 @@ For reference. Everything below is in current `0.4.0`.
|
||||
- ✓ Custom-craft canon: pure CSS art, hand-built SVG, declarative animation, View Transitions, Three.js when justified
|
||||
- ✓ Asset-source catalogue: Lucide / Phosphor / Heroicons (icons), Simple Icons / SVGL (logos), Nanobanana 2 / Recraft V4 (generated illustration), Storyset / Humaaans (library), Mixkit / Coverr (video), Unsplash / Nappy (photography)
|
||||
- ✓ Working install path: `cp skill/* ~/.claude/skills/hallmark/`
|
||||
- ✓ Two test specimens: bakery (`site/_example-bakery/`) and command palette (`site/_example-cmdk/`)
|
||||
- ✓ Slop test 33 questions: visual (8), structural (2), microinteractions (10), variety (3), implementation (6), hero enrichment (4)
|
||||
- ✓ View Transitions on theme switch, with reduced-motion fallback
|
||||
- ✓ Copy-to-clipboard with silent success on install code blocks
|
||||
|
||||
|
Before Width: | Height: | Size: 164 KiB |
|
Before Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 162 KiB |
@@ -1,764 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
||||
<meta name="color-scheme" content="light" />
|
||||
<title>Marlow Street Bread — opening 8 May</title>
|
||||
<meta name="description" content="A neighborhood sourdough bakery. Local flour, long ferment, a few loaves a day. Opening on Marlow Street in May." />
|
||||
|
||||
<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=Fraunces:ital,opsz,wght@0,9..144,300..500;1,9..144,300..500&family=IBM+Plex+Sans:wght@300;400;500&family=IBM+Plex+Mono:wght@400;500&display=swap" />
|
||||
|
||||
<style>
|
||||
/*
|
||||
Marlow Street Bread — a single-page announcement.
|
||||
Tone: quietly confident, editorial. Anchor hue: 45 (wheat / crust).
|
||||
*/
|
||||
|
||||
:root {
|
||||
/* Colour */
|
||||
--color-paper: oklch(96% 0.016 75);
|
||||
--color-paper-2: oklch(93% 0.020 72);
|
||||
--color-rule: oklch(82% 0.018 70);
|
||||
--color-rule-2: oklch(70% 0.022 65);
|
||||
--color-muted: oklch(52% 0.022 60);
|
||||
--color-ink-2: oklch(32% 0.025 55);
|
||||
--color-ink: oklch(22% 0.026 50);
|
||||
--color-accent: oklch(48% 0.15 45);
|
||||
--color-focus: oklch(48% 0.15 45);
|
||||
--color-error: oklch(45% 0.18 25);
|
||||
--color-success: oklch(42% 0.10 140);
|
||||
|
||||
/* Type */
|
||||
--font-display: "Fraunces", "Tiempos", ui-serif, Georgia, serif;
|
||||
--font-body: "IBM Plex Sans", "Söhne", ui-sans-serif, sans-serif;
|
||||
--font-serif: "Fraunces", ui-serif, Georgia, serif;
|
||||
--font-label: "IBM Plex Mono", ui-monospace, monospace;
|
||||
|
||||
--text-xs: 0.75rem;
|
||||
--text-sm: 0.875rem;
|
||||
--text-base: 1rem;
|
||||
--text-md: 1.125rem;
|
||||
--text-lg: 1.375rem;
|
||||
--text-xl: 1.75rem;
|
||||
--text-2xl: 2.25rem;
|
||||
--text-display: clamp(3rem, 7vw + 1rem, 7.25rem);
|
||||
--text-display-s: clamp(2rem, 3vw + 1rem, 3rem);
|
||||
|
||||
--lh-tight: 1.02;
|
||||
--lh-snug: 1.18;
|
||||
--lh-normal: 1.45;
|
||||
--lh-relaxed: 1.65;
|
||||
|
||||
--tracking-display: -0.028em;
|
||||
--tracking-tight: -0.012em;
|
||||
--tracking-label: 0.14em;
|
||||
|
||||
/* Spacing */
|
||||
--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: 4.5rem;
|
||||
--space-3xl: 7rem;
|
||||
--space-4xl: 11rem;
|
||||
|
||||
/* Motion */
|
||||
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
|
||||
--ease-in: cubic-bezier(0.7, 0, 0.84, 0);
|
||||
--dur-micro: 120ms;
|
||||
--dur-short: 220ms;
|
||||
--dur-long: 520ms;
|
||||
|
||||
--page-max: 64rem;
|
||||
--page-gutter: clamp(1.25rem, 4vw, 3rem);
|
||||
--measure: 58ch;
|
||||
}
|
||||
|
||||
*, *::before, *::after { box-sizing: border-box; }
|
||||
body, h1, h2, h3, p, figure, blockquote, dl, dd { margin: 0; }
|
||||
img, picture, svg { display: block; max-width: 100%; height: auto; }
|
||||
button, input, select { font: inherit; color: inherit; }
|
||||
button { background: none; border: 0; padding: 0; cursor: pointer; }
|
||||
a { color: inherit; text-decoration: none; }
|
||||
ul, ol { margin: 0; padding: 0; list-style: none; }
|
||||
|
||||
:focus { outline: none; }
|
||||
:focus-visible {
|
||||
outline: 2px solid var(--color-focus);
|
||||
outline-offset: 3px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
html { background: var(--color-paper); color: var(--color-ink); }
|
||||
|
||||
body {
|
||||
font-family: var(--font-body);
|
||||
font-weight: 400;
|
||||
font-size: var(--text-base);
|
||||
line-height: var(--lh-relaxed);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
min-height: 100dvh;
|
||||
padding-inline: max(var(--page-gutter), env(safe-area-inset-left));
|
||||
padding-block-end: env(safe-area-inset-bottom);
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
::selection { background: var(--color-accent); color: var(--color-paper); }
|
||||
|
||||
h1, h2, h3 {
|
||||
font-family: var(--font-display);
|
||||
font-weight: 300;
|
||||
line-height: var(--lh-tight);
|
||||
letter-spacing: var(--tracking-display);
|
||||
color: var(--color-ink);
|
||||
font-optical-sizing: auto;
|
||||
}
|
||||
|
||||
p { max-width: var(--measure); }
|
||||
|
||||
em { font-style: italic; }
|
||||
|
||||
code { font-family: var(--font-label); font-size: 0.9em; }
|
||||
|
||||
.page {
|
||||
max-width: var(--page-max);
|
||||
margin-inline: auto;
|
||||
display: grid;
|
||||
gap: var(--space-3xl);
|
||||
padding-block: var(--space-md) var(--space-3xl);
|
||||
}
|
||||
|
||||
/* Nav */
|
||||
.nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-block: var(--space-md);
|
||||
border-bottom: 0.5px solid var(--color-rule);
|
||||
}
|
||||
|
||||
.wordmark {
|
||||
font-family: var(--font-label);
|
||||
font-size: var(--text-xs);
|
||||
font-weight: 500;
|
||||
letter-spacing: var(--tracking-label);
|
||||
text-transform: uppercase;
|
||||
color: var(--color-ink);
|
||||
}
|
||||
|
||||
.nav__note {
|
||||
font-family: var(--font-label);
|
||||
font-size: var(--text-xs);
|
||||
letter-spacing: var(--tracking-label);
|
||||
text-transform: uppercase;
|
||||
color: var(--color-muted);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.55em;
|
||||
}
|
||||
|
||||
.mark {
|
||||
display: inline-block;
|
||||
width: 0.5em;
|
||||
height: 0.5em;
|
||||
background: var(--color-accent);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
/* Hero */
|
||||
.hero {
|
||||
display: grid;
|
||||
grid-template-columns: 9fr 3fr;
|
||||
gap: var(--space-2xl);
|
||||
align-items: end;
|
||||
padding-block: var(--space-xl) var(--space-md);
|
||||
}
|
||||
|
||||
@media (max-width: 58rem) {
|
||||
.hero { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
.hero__eyebrow {
|
||||
font-family: var(--font-label);
|
||||
font-size: var(--text-xs);
|
||||
letter-spacing: var(--tracking-label);
|
||||
text-transform: uppercase;
|
||||
color: var(--color-muted);
|
||||
margin-bottom: var(--space-xl);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.55em;
|
||||
}
|
||||
|
||||
.hero__title {
|
||||
font-family: var(--font-display);
|
||||
font-weight: 300;
|
||||
font-size: var(--text-display);
|
||||
line-height: 0.98;
|
||||
letter-spacing: -0.03em;
|
||||
font-variation-settings: "opsz" 144;
|
||||
color: var(--color-ink);
|
||||
max-width: 12ch;
|
||||
}
|
||||
|
||||
.hero__title em {
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.hero__lede {
|
||||
font-family: var(--font-serif);
|
||||
font-weight: 300;
|
||||
font-size: var(--text-md);
|
||||
line-height: var(--lh-normal);
|
||||
color: var(--color-ink-2);
|
||||
max-width: 38ch;
|
||||
margin-top: var(--space-xl);
|
||||
}
|
||||
|
||||
.hero__aside {
|
||||
display: grid;
|
||||
gap: var(--space-sm);
|
||||
padding-top: var(--space-xl);
|
||||
}
|
||||
|
||||
.hero__meta-row {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: var(--space-sm) var(--space-md);
|
||||
padding-block: var(--space-sm);
|
||||
border-block: 0.5px solid var(--color-rule);
|
||||
font-family: var(--font-label);
|
||||
font-size: var(--text-xs);
|
||||
letter-spacing: var(--tracking-label);
|
||||
text-transform: uppercase;
|
||||
color: var(--color-muted);
|
||||
}
|
||||
|
||||
.hero__meta-row dt { color: var(--color-muted); }
|
||||
.hero__meta-row dd { color: var(--color-ink); margin: 0; }
|
||||
|
||||
/* Section scaffolding */
|
||||
.section {
|
||||
display: grid;
|
||||
grid-template-columns: 10rem 1fr;
|
||||
gap: var(--space-xl);
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
@media (max-width: 50rem) {
|
||||
.section { grid-template-columns: 1fr; gap: var(--space-md); }
|
||||
}
|
||||
|
||||
.section__label {
|
||||
font-family: var(--font-label);
|
||||
font-size: var(--text-xs);
|
||||
letter-spacing: var(--tracking-label);
|
||||
text-transform: uppercase;
|
||||
color: var(--color-muted);
|
||||
padding-top: 0.35rem;
|
||||
}
|
||||
|
||||
.section__label .num { color: var(--color-ink); font-weight: 500; }
|
||||
|
||||
.section__body {
|
||||
display: grid;
|
||||
gap: var(--space-lg);
|
||||
}
|
||||
|
||||
.section__title {
|
||||
font-family: var(--font-display);
|
||||
font-weight: 300;
|
||||
font-style: italic;
|
||||
font-size: var(--text-2xl);
|
||||
line-height: var(--lh-snug);
|
||||
letter-spacing: var(--tracking-tight);
|
||||
color: var(--color-ink);
|
||||
max-width: 22ch;
|
||||
font-variation-settings: "opsz" 72;
|
||||
}
|
||||
|
||||
.section__prose {
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--text-md);
|
||||
line-height: var(--lh-normal);
|
||||
color: var(--color-ink-2);
|
||||
max-width: var(--measure);
|
||||
}
|
||||
|
||||
/* Loaves list */
|
||||
.loaves {
|
||||
display: grid;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.loaf {
|
||||
display: grid;
|
||||
grid-template-columns: 2rem 10rem 1fr auto;
|
||||
gap: var(--space-md);
|
||||
align-items: baseline;
|
||||
padding-block: var(--space-md);
|
||||
border-top: 0.5px solid var(--color-rule);
|
||||
}
|
||||
|
||||
.loaf:last-child { border-bottom: 0.5px solid var(--color-rule); }
|
||||
|
||||
@media (max-width: 50rem) {
|
||||
.loaf {
|
||||
grid-template-columns: 2rem 1fr;
|
||||
grid-template-rows: auto auto;
|
||||
}
|
||||
.loaf__desc { grid-column: 1 / -1; padding-left: 2.75rem; }
|
||||
.loaf__days { grid-column: 1 / -1; padding-left: 2.75rem; color: var(--color-muted); }
|
||||
}
|
||||
|
||||
.loaf__num {
|
||||
font-family: var(--font-label);
|
||||
font-size: var(--text-xs);
|
||||
letter-spacing: var(--tracking-label);
|
||||
color: var(--color-muted);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.loaf__name {
|
||||
font-family: var(--font-display);
|
||||
font-weight: 400;
|
||||
font-size: var(--text-lg);
|
||||
line-height: 1;
|
||||
letter-spacing: -0.01em;
|
||||
color: var(--color-ink);
|
||||
font-variation-settings: "opsz" 48;
|
||||
}
|
||||
|
||||
.loaf__desc {
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--text-sm);
|
||||
line-height: var(--lh-normal);
|
||||
color: var(--color-ink-2);
|
||||
max-width: 52ch;
|
||||
}
|
||||
|
||||
.loaf__days {
|
||||
font-family: var(--font-label);
|
||||
font-size: var(--text-xs);
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-muted);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Pull quote */
|
||||
.pull {
|
||||
font-family: var(--font-display);
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
font-size: clamp(1.75rem, 3vw + 1rem, 2.75rem);
|
||||
line-height: 1.15;
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--color-ink);
|
||||
max-width: 26ch;
|
||||
padding-block: var(--space-lg);
|
||||
font-variation-settings: "opsz" 96;
|
||||
}
|
||||
|
||||
.pull__attr {
|
||||
display: block;
|
||||
margin-top: var(--space-md);
|
||||
font-family: var(--font-label);
|
||||
font-style: normal;
|
||||
font-size: var(--text-xs);
|
||||
font-weight: 400;
|
||||
letter-spacing: var(--tracking-label);
|
||||
text-transform: uppercase;
|
||||
color: var(--color-muted);
|
||||
}
|
||||
|
||||
/* Where */
|
||||
.where {
|
||||
display: grid;
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
|
||||
.where dt {
|
||||
font-family: var(--font-label);
|
||||
font-size: var(--text-xs);
|
||||
letter-spacing: var(--tracking-label);
|
||||
text-transform: uppercase;
|
||||
color: var(--color-muted);
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.where dd {
|
||||
font-family: var(--font-serif);
|
||||
font-weight: 400;
|
||||
font-size: var(--text-md);
|
||||
color: var(--color-ink);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.where .row {
|
||||
display: grid;
|
||||
grid-template-columns: 8rem 1fr;
|
||||
gap: var(--space-md);
|
||||
align-items: baseline;
|
||||
padding-block: var(--space-sm);
|
||||
border-top: 0.5px solid var(--color-rule);
|
||||
}
|
||||
.where .row:last-of-type { border-bottom: 0.5px solid var(--color-rule); }
|
||||
|
||||
/* Signup form */
|
||||
.signup {
|
||||
background: var(--color-paper-2);
|
||||
padding: var(--space-xl) var(--space-xl) var(--space-2xl);
|
||||
border: 0.5px solid var(--color-rule);
|
||||
}
|
||||
|
||||
.signup__head {
|
||||
font-family: var(--font-display);
|
||||
font-weight: 300;
|
||||
font-style: italic;
|
||||
font-size: var(--text-2xl);
|
||||
line-height: var(--lh-snug);
|
||||
letter-spacing: var(--tracking-tight);
|
||||
color: var(--color-ink);
|
||||
max-width: 20ch;
|
||||
font-variation-settings: "opsz" 72;
|
||||
}
|
||||
|
||||
.signup__note {
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--text-sm);
|
||||
line-height: var(--lh-normal);
|
||||
color: var(--color-ink-2);
|
||||
max-width: 46ch;
|
||||
margin-top: var(--space-md);
|
||||
margin-bottom: var(--space-lg);
|
||||
}
|
||||
|
||||
.form {
|
||||
display: grid;
|
||||
gap: var(--space-sm);
|
||||
max-width: 32rem;
|
||||
}
|
||||
|
||||
.form__label {
|
||||
font-family: var(--font-label);
|
||||
font-size: var(--text-xs);
|
||||
letter-spacing: var(--tracking-label);
|
||||
text-transform: uppercase;
|
||||
color: var(--color-muted);
|
||||
}
|
||||
|
||||
.form__row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
gap: 0;
|
||||
border-bottom: 1px solid var(--color-ink);
|
||||
}
|
||||
|
||||
.form__input {
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--text-md);
|
||||
padding: var(--space-sm) 0;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
color: var(--color-ink);
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
.form__input::placeholder { color: var(--color-muted); }
|
||||
.form__input:focus-visible { outline: none; }
|
||||
|
||||
.form__row:focus-within { border-bottom-color: var(--color-accent); }
|
||||
|
||||
.form__submit {
|
||||
font-family: var(--font-label);
|
||||
font-size: var(--text-xs);
|
||||
letter-spacing: var(--tracking-label);
|
||||
text-transform: uppercase;
|
||||
color: var(--color-ink);
|
||||
padding: 0 var(--space-md);
|
||||
min-height: 44px;
|
||||
align-self: stretch;
|
||||
transition: color var(--dur-micro) var(--ease-out);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
.form__submit:hover { color: var(--color-accent); }
|
||||
}
|
||||
|
||||
.form__submit:disabled { color: var(--color-muted); cursor: not-allowed; }
|
||||
|
||||
.form__hint {
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--text-xs);
|
||||
color: var(--color-muted);
|
||||
margin-top: var(--space-xs);
|
||||
line-height: var(--lh-normal);
|
||||
}
|
||||
|
||||
.form__hint--error { color: var(--color-error); }
|
||||
.form__hint--success { color: var(--color-success); }
|
||||
|
||||
.form[data-state="error"] .form__row { border-bottom-color: var(--color-error); }
|
||||
.form[data-state="success"] .form__row { border-bottom-color: var(--color-success); }
|
||||
|
||||
/* Footer */
|
||||
.foot {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
gap: var(--space-lg);
|
||||
padding-top: var(--space-xl);
|
||||
border-top: 1px solid var(--color-ink);
|
||||
font-family: var(--font-label);
|
||||
font-size: var(--text-xs);
|
||||
letter-spacing: 0.04em;
|
||||
color: var(--color-muted);
|
||||
align-items: end;
|
||||
}
|
||||
|
||||
.foot__address { line-height: 1.7; }
|
||||
.foot__credit { text-align: right; }
|
||||
.foot__credit a { color: var(--color-ink); }
|
||||
|
||||
/* Reveals — one orchestrated entrance */
|
||||
.reveal {
|
||||
opacity: 0;
|
||||
transform: translateY(8px);
|
||||
animation: reveal var(--dur-long) var(--ease-out) forwards;
|
||||
animation-delay: calc(var(--i, 0) * 80ms);
|
||||
}
|
||||
|
||||
@keyframes reveal {
|
||||
to { opacity: 1; transform: none; }
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.reveal {
|
||||
transform: none;
|
||||
animation-duration: 150ms;
|
||||
}
|
||||
@keyframes reveal { to { opacity: 1; } }
|
||||
*, *::before, *::after {
|
||||
animation-duration: 150ms !important;
|
||||
transition-duration: 150ms !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<main class="page">
|
||||
|
||||
<!-- Nav -->
|
||||
<nav class="nav reveal" style="--i:0" aria-label="Primary">
|
||||
<span class="wordmark">Marlow Street Bread</span>
|
||||
<span class="nav__note"><span class="mark" aria-hidden="true"></span>Opens 8 May</span>
|
||||
</nav>
|
||||
|
||||
<!-- Hero -->
|
||||
<header class="hero reveal" style="--i:1">
|
||||
<div>
|
||||
<p class="hero__eyebrow">
|
||||
<span class="mark" aria-hidden="true"></span>
|
||||
Opening · 8 May 2026
|
||||
</p>
|
||||
<h1 class="hero__title">
|
||||
Bread, when it’s <em>ready.</em>
|
||||
</h1>
|
||||
<p class="hero__lede">
|
||||
A naturally leavened loaf, made with local flour and a thirty-six hour ferment. A few of them, baked daily. When they’re gone, we close.
|
||||
</p>
|
||||
</div>
|
||||
<aside class="hero__aside">
|
||||
<dl class="hero__meta-row">
|
||||
<dt>Kind</dt>
|
||||
<dd>Neighborhood bakery</dd>
|
||||
<dt>Where</dt>
|
||||
<dd>137 Marlow St</dd>
|
||||
<dt>Open</dt>
|
||||
<dd>Tue – Sat, 8 a.m.</dd>
|
||||
</dl>
|
||||
</aside>
|
||||
</header>
|
||||
|
||||
<!-- What we bake -->
|
||||
<section class="section reveal" style="--i:2" aria-labelledby="s-loaves">
|
||||
<div class="section__label">
|
||||
<span class="num">01</span> ⁄ What we bake
|
||||
</div>
|
||||
<div class="section__body">
|
||||
<h2 class="section__title" id="s-loaves">
|
||||
A short list, done properly.
|
||||
</h2>
|
||||
<ul class="loaves">
|
||||
<li class="loaf">
|
||||
<span class="loaf__num">01</span>
|
||||
<span class="loaf__name">Country</span>
|
||||
<span class="loaf__desc">Stone-milled wheat, a little rye, a long cold ferment. Our everyday loaf.</span>
|
||||
<span class="loaf__days">Daily</span>
|
||||
</li>
|
||||
<li class="loaf">
|
||||
<span class="loaf__num">02</span>
|
||||
<span class="loaf__name">Seeded</span>
|
||||
<span class="loaf__desc">Pumpkin, sesame, poppy. The country base with a denser crumb.</span>
|
||||
<span class="loaf__days">Daily</span>
|
||||
</li>
|
||||
<li class="loaf">
|
||||
<span class="loaf__num">03</span>
|
||||
<span class="loaf__name">Miche</span>
|
||||
<span class="loaf__desc">A broad round, 1.4 kg. Keeps four days on the counter, longer under cloth.</span>
|
||||
<span class="loaf__days">Fri & Sat</span>
|
||||
</li>
|
||||
<li class="loaf">
|
||||
<span class="loaf__num">04</span>
|
||||
<span class="loaf__name">Rye</span>
|
||||
<span class="loaf__desc">Seventy percent rye, caraway, a little molasses. Dense, dark, patient.</span>
|
||||
<span class="loaf__days">Thursdays</span>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="section__prose">
|
||||
We bake in small numbers because we’d rather bake four loaves well than forty in a hurry. If a loaf is sold out by ten, that’s how it goes.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Pull quote -->
|
||||
<blockquote class="section reveal" style="--i:3">
|
||||
<div class="section__label">
|
||||
<span class="num">02</span> ⁄ Why
|
||||
</div>
|
||||
<p class="pull">
|
||||
“Good bread is mostly time — a long fermentation, a patient oven, and flour that wasn’t rushed.”
|
||||
<cite class="pull__attr">— from the opening letter</cite>
|
||||
</p>
|
||||
</blockquote>
|
||||
|
||||
<!-- Flour & farmers -->
|
||||
<section class="section reveal" style="--i:4" aria-labelledby="s-flour">
|
||||
<div class="section__label">
|
||||
<span class="num">03</span> ⁄ Flour & farmers
|
||||
</div>
|
||||
<div class="section__body">
|
||||
<h2 class="section__title" id="s-flour">
|
||||
Milled in Paxton, grown twenty-three miles away.
|
||||
</h2>
|
||||
<p class="section__prose">
|
||||
Our flour is stone-milled at the Paxton mill, north of town. The wheat comes from two farms — Linnea Berg’s in the valley and the Holdfast Co-op on the ridge. We work with what they grow, when they grow it, which means the flour shifts a little with the seasons. We think that’s a feature.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Where -->
|
||||
<section class="section reveal" style="--i:5" aria-labelledby="s-where">
|
||||
<div class="section__label">
|
||||
<span class="num">04</span> ⁄ When & where
|
||||
</div>
|
||||
<div class="section__body">
|
||||
<h2 class="section__title" id="s-where">
|
||||
Marlow Street, off Fifth.
|
||||
</h2>
|
||||
<dl class="where">
|
||||
<div class="row"><dt>Address</dt><dd>137 Marlow Street</dd></div>
|
||||
<div class="row"><dt>Opening</dt><dd>8 May 2026, 8 a.m.</dd></div>
|
||||
<div class="row"><dt>Hours</dt><dd>Tuesday — Saturday, 8 a.m. until sold out (usually by 1 p.m.)</dd></div>
|
||||
<div class="row"><dt>Closed</dt><dd>Sunday & Monday</dd></div>
|
||||
</dl>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Signup -->
|
||||
<section class="section reveal" style="--i:6" aria-labelledby="s-signup">
|
||||
<div class="section__label">
|
||||
<span class="num">05</span> ⁄ The notice
|
||||
</div>
|
||||
<div class="signup">
|
||||
<h2 class="signup__head" id="s-signup">
|
||||
We’ll write when the doors open.
|
||||
</h2>
|
||||
<p class="signup__note">
|
||||
One email on opening day, and one a week later with the first weekend’s hours. Nothing else. Your address stays with us.
|
||||
</p>
|
||||
|
||||
<form class="form" id="signup-form" novalidate>
|
||||
<label class="form__label" for="email">Email address</label>
|
||||
<div class="form__row">
|
||||
<input
|
||||
class="form__input"
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
autocomplete="email"
|
||||
required
|
||||
placeholder="you@example.com"
|
||||
aria-describedby="email-hint"
|
||||
/>
|
||||
<button class="form__submit" type="submit">Send me the notice →</button>
|
||||
</div>
|
||||
<p class="form__hint" id="email-hint">We'll send two emails. No list-sharing.</p>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="foot reveal" style="--i:7" role="contentinfo">
|
||||
<div class="foot__address">
|
||||
Marlow Street Bread · 137 Marlow Street · Tue – Sat from 8 a.m.
|
||||
</div>
|
||||
<div class="foot__credit">
|
||||
Designed with <a href="/" class="link">Hallmark</a>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
</main>
|
||||
|
||||
<script type="module">
|
||||
const form = document.getElementById("signup-form");
|
||||
const input = document.getElementById("email");
|
||||
const hint = document.getElementById("email-hint");
|
||||
const submit = form.querySelector(".form__submit");
|
||||
|
||||
form.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
const value = input.value.trim();
|
||||
const valid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
|
||||
|
||||
if (!valid) {
|
||||
form.dataset.state = "error";
|
||||
input.setAttribute("aria-invalid", "true");
|
||||
hint.className = "form__hint form__hint--error";
|
||||
hint.textContent = "That doesn’t look like an email address. Try again?";
|
||||
return;
|
||||
}
|
||||
|
||||
submit.disabled = true;
|
||||
submit.textContent = "Sending…";
|
||||
|
||||
// Demo only — pretend we sent it.
|
||||
setTimeout(() => {
|
||||
form.dataset.state = "success";
|
||||
input.removeAttribute("aria-invalid");
|
||||
hint.className = "form__hint form__hint--success";
|
||||
hint.textContent = "On the list. We’ll be in touch on 8 May.";
|
||||
submit.textContent = "Sent";
|
||||
}, 350);
|
||||
});
|
||||
|
||||
input.addEventListener("input", () => {
|
||||
if (form.dataset.state === "error") {
|
||||
delete form.dataset.state;
|
||||
input.removeAttribute("aria-invalid");
|
||||
hint.className = "form__hint";
|
||||
hint.textContent = "We'll send two emails. No list-sharing.";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,886 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
||||
<meta name="color-scheme" content="light" />
|
||||
<title>Lectern — a personal library</title>
|
||||
<meta name="description" content="A keyboard-first command palette for a personal reading list. Press ⌘K to find any book in seconds." />
|
||||
|
||||
<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=IBM+Plex+Sans:wght@300;400;500;600&family=JetBrains+Mono:wght@400;500&display=swap" />
|
||||
|
||||
<style>
|
||||
/*
|
||||
Lectern — keyboard-first book library.
|
||||
Tone: technical, calm. Anchor hue: 240 (cool slate).
|
||||
Structure: single column, hairline rules, typographic-only CTAs, no reveals.
|
||||
*/
|
||||
|
||||
:root {
|
||||
--color-paper: oklch(97% 0.005 250);
|
||||
--color-paper-2: oklch(94% 0.007 250);
|
||||
--color-paper-3: oklch(91% 0.008 250);
|
||||
--color-rule: oklch(86% 0.008 245);
|
||||
--color-rule-2: oklch(74% 0.010 240);
|
||||
--color-muted: oklch(54% 0.012 245);
|
||||
--color-neutral: oklch(40% 0.014 245);
|
||||
--color-ink-2: oklch(28% 0.014 245);
|
||||
--color-ink: oklch(18% 0.016 245);
|
||||
--color-accent: oklch(48% 0.16 240);
|
||||
--color-focus: oklch(48% 0.16 240);
|
||||
--color-mark-bg: oklch(92% 0.06 95);
|
||||
--color-row-hover: oklch(94% 0.012 245);
|
||||
--color-row-active: oklch(88% 0.030 240);
|
||||
|
||||
--font-sans: "IBM Plex Sans", "Söhne", ui-sans-serif, sans-serif;
|
||||
--font-mono: "JetBrains Mono", "IBM Plex Mono", ui-monospace, monospace;
|
||||
|
||||
--text-xs: 0.75rem;
|
||||
--text-sm: 0.8125rem;
|
||||
--text-base: 0.9375rem;
|
||||
--text-md: 1.0625rem;
|
||||
--text-lg: 1.25rem;
|
||||
--text-xl: 1.5rem;
|
||||
|
||||
--lh-tight: 1.1;
|
||||
--lh-snug: 1.3;
|
||||
--lh-normal: 1.5;
|
||||
--lh-relaxed: 1.65;
|
||||
|
||||
--tracking-label: 0.10em;
|
||||
|
||||
--space-2xs: 0.25rem;
|
||||
--space-xs: 0.5rem;
|
||||
--space-sm: 0.75rem;
|
||||
--space-md: 1rem;
|
||||
--space-lg: 1.5rem;
|
||||
--space-xl: 2rem;
|
||||
--space-2xl: 3rem;
|
||||
--space-3xl: 4.5rem;
|
||||
|
||||
--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: 300ms;
|
||||
|
||||
--page-max: 58rem;
|
||||
--page-gutter: clamp(1rem, 4vw, 2.5rem);
|
||||
}
|
||||
|
||||
*, *::before, *::after { box-sizing: border-box; }
|
||||
body, h1, h2, h3, p, dl, dd { margin: 0; }
|
||||
button, input { font: inherit; color: inherit; }
|
||||
button { background: none; border: 0; padding: 0; cursor: pointer; }
|
||||
a { color: inherit; text-decoration: none; }
|
||||
ul, ol { margin: 0; padding: 0; list-style: none; }
|
||||
|
||||
/* Focus rings — instant, never animated in/out */
|
||||
:focus { outline: none; }
|
||||
:focus-visible {
|
||||
outline: 2px solid var(--color-focus);
|
||||
outline-offset: 2px;
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
html {
|
||||
background: var(--color-paper);
|
||||
color: var(--color-ink);
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-sans);
|
||||
font-weight: 400;
|
||||
font-size: var(--text-base);
|
||||
line-height: var(--lh-relaxed);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
min-height: 100dvh;
|
||||
padding-inline: max(var(--page-gutter), env(safe-area-inset-left));
|
||||
padding-block-end: env(safe-area-inset-bottom);
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: var(--color-accent);
|
||||
color: var(--color-paper);
|
||||
}
|
||||
|
||||
/* Page scaffold */
|
||||
.page {
|
||||
max-width: var(--page-max);
|
||||
margin-inline: auto;
|
||||
padding-block: var(--space-xl) var(--space-3xl);
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
gap: var(--space-md);
|
||||
padding-block: var(--space-md);
|
||||
border-bottom: 1px solid var(--color-ink);
|
||||
margin-bottom: var(--space-xl);
|
||||
}
|
||||
|
||||
.wordmark {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-sm);
|
||||
font-weight: 500;
|
||||
letter-spacing: var(--tracking-label);
|
||||
text-transform: uppercase;
|
||||
color: var(--color-ink);
|
||||
}
|
||||
|
||||
.wordmark .count {
|
||||
color: var(--color-muted);
|
||||
font-weight: 400;
|
||||
margin-left: 0.6rem;
|
||||
}
|
||||
|
||||
.meta {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-xs);
|
||||
color: var(--color-muted);
|
||||
letter-spacing: var(--tracking-label);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
/* Status legend */
|
||||
.legend {
|
||||
display: flex;
|
||||
gap: var(--space-lg);
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-xs);
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--color-muted);
|
||||
padding-bottom: var(--space-md);
|
||||
margin-bottom: var(--space-xs);
|
||||
}
|
||||
.legend__item { display: inline-flex; align-items: center; gap: 0.4em; }
|
||||
|
||||
/* Book list (ambient) */
|
||||
.books { display: grid; gap: 0; }
|
||||
|
||||
.book {
|
||||
display: grid;
|
||||
grid-template-columns: 4rem 1fr 1.25rem;
|
||||
gap: var(--space-md);
|
||||
padding: 0.6rem 0.4rem;
|
||||
align-items: baseline;
|
||||
border-top: 0.5px solid var(--color-rule);
|
||||
transition: background-color var(--dur-micro) var(--ease-out);
|
||||
}
|
||||
.book:last-child { border-bottom: 0.5px solid var(--color-rule); }
|
||||
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
.book:hover { background-color: var(--color-row-hover); }
|
||||
}
|
||||
|
||||
.book__year {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-xs);
|
||||
color: var(--color-muted);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.book__title {
|
||||
font-size: var(--text-md);
|
||||
font-weight: 500;
|
||||
color: var(--color-ink);
|
||||
letter-spacing: -0.005em;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.book__author {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--color-neutral);
|
||||
margin-left: 0.6rem;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.book__status {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-sm);
|
||||
text-align: center;
|
||||
color: var(--color-muted);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.book__status[data-state="reading"] { color: var(--color-accent); }
|
||||
.book__status[data-state="read"] { color: var(--color-muted); }
|
||||
.book__status[data-state="queued"] { color: var(--color-rule-2); }
|
||||
|
||||
/* ⌘K invitation — visible, persistent, never animated */
|
||||
.invite {
|
||||
position: fixed;
|
||||
bottom: max(1rem, env(safe-area-inset-bottom));
|
||||
right: max(1rem, env(safe-area-inset-right));
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
padding: 0.6rem 0.85rem;
|
||||
background: var(--color-paper);
|
||||
border: 1px solid var(--color-ink);
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-xs);
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--color-ink);
|
||||
cursor: pointer;
|
||||
min-height: 36px;
|
||||
transition: background-color var(--dur-micro) var(--ease-out),
|
||||
color var(--dur-micro) var(--ease-out);
|
||||
}
|
||||
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
.invite:hover { background: var(--color-ink); color: var(--color-paper); }
|
||||
}
|
||||
|
||||
.invite:active { transition-duration: 60ms; }
|
||||
|
||||
.kbd {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.2em;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.6875rem;
|
||||
letter-spacing: 0;
|
||||
padding: 0.05rem 0.4rem;
|
||||
border: 1px solid currentColor;
|
||||
opacity: 0.7;
|
||||
font-weight: 500;
|
||||
min-height: 1.2em;
|
||||
}
|
||||
|
||||
/* Footer hint */
|
||||
.foot {
|
||||
margin-top: var(--space-3xl);
|
||||
padding-top: var(--space-md);
|
||||
border-top: 1px solid var(--color-ink);
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-xs);
|
||||
letter-spacing: 0.04em;
|
||||
color: var(--color-muted);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-md);
|
||||
}
|
||||
|
||||
/* ─────────────────────────────────────────────────────────
|
||||
Command palette — the heart of this demo
|
||||
───────────────────────────────────────────────────────── */
|
||||
|
||||
/* Native <dialog> as palette */
|
||||
dialog.palette {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
max-width: 36rem;
|
||||
width: calc(100% - 2rem);
|
||||
margin-top: 12vh;
|
||||
color: var(--color-ink);
|
||||
}
|
||||
|
||||
dialog.palette::backdrop {
|
||||
background-color: oklch(20% 0.012 245 / 0.45);
|
||||
backdrop-filter: blur(4px);
|
||||
-webkit-backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
/* Entrance animation — content + backdrop, ease-out, 300ms */
|
||||
dialog.palette[open] {
|
||||
animation: palette-in var(--dur-long) var(--ease-out) forwards;
|
||||
}
|
||||
dialog.palette[open]::backdrop {
|
||||
animation: backdrop-in var(--dur-long) var(--ease-out) forwards;
|
||||
}
|
||||
|
||||
@keyframes palette-in {
|
||||
from { opacity: 0; transform: scale(0.96); }
|
||||
to { opacity: 1; transform: scale(1); }
|
||||
}
|
||||
@keyframes backdrop-in {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
/* Exit — applied via .closing class for the brief moment before close */
|
||||
dialog.palette.closing {
|
||||
animation: palette-out 220ms var(--ease-in) forwards;
|
||||
}
|
||||
dialog.palette.closing::backdrop {
|
||||
animation: backdrop-out 220ms var(--ease-in) forwards;
|
||||
}
|
||||
@keyframes palette-out {
|
||||
to { opacity: 0; transform: scale(0.98); }
|
||||
}
|
||||
@keyframes backdrop-out {
|
||||
to { opacity: 0; }
|
||||
}
|
||||
|
||||
.pal {
|
||||
background: var(--color-paper);
|
||||
border: 1px solid var(--color-rule-2);
|
||||
box-shadow: 0 24px 60px -20px oklch(20% 0.05 245 / 0.25);
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr auto;
|
||||
max-height: 70vh;
|
||||
}
|
||||
|
||||
/* Input row */
|
||||
.pal__input-row {
|
||||
display: grid;
|
||||
grid-template-columns: 2.25rem 1fr auto;
|
||||
align-items: center;
|
||||
gap: var(--space-sm);
|
||||
padding: 0.85rem 1rem;
|
||||
border-bottom: 1px solid var(--color-rule);
|
||||
}
|
||||
|
||||
.pal__leading {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-md);
|
||||
color: var(--color-muted);
|
||||
text-align: center;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.pal__input {
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
padding: 0.4rem 0;
|
||||
font-family: var(--font-sans);
|
||||
font-size: var(--text-md);
|
||||
color: var(--color-ink);
|
||||
letter-spacing: -0.005em;
|
||||
min-height: 32px;
|
||||
}
|
||||
|
||||
.pal__input:focus { outline: none; }
|
||||
.pal__input::placeholder { color: var(--color-muted); }
|
||||
|
||||
.pal__count {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-xs);
|
||||
color: var(--color-muted);
|
||||
letter-spacing: 0.06em;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
/* Results scroll area */
|
||||
.pal__results {
|
||||
position: relative;
|
||||
overflow-y: auto;
|
||||
padding: 0.4rem 0;
|
||||
scroll-padding-block: 0.4rem;
|
||||
scrollbar-color: var(--color-rule-2) transparent;
|
||||
}
|
||||
|
||||
.pal__results::-webkit-scrollbar { width: 6px; }
|
||||
.pal__results::-webkit-scrollbar-track { background: transparent; }
|
||||
.pal__results::-webkit-scrollbar-thumb { background: var(--color-rule-2); }
|
||||
|
||||
/* The selection tracker — single element, transitions translateY between rows.
|
||||
Linear / Raycast / Vercel pattern: items don't move, the tracker does. */
|
||||
.pal__tracker {
|
||||
position: absolute;
|
||||
left: 0.4rem;
|
||||
right: 0.4rem;
|
||||
height: 2.4rem;
|
||||
background: var(--color-row-active);
|
||||
pointer-events: none;
|
||||
transform: translateY(0);
|
||||
transition: transform 120ms var(--ease-out),
|
||||
opacity var(--dur-micro) var(--ease-out);
|
||||
}
|
||||
|
||||
.pal__results[data-empty="true"] .pal__tracker { opacity: 0; }
|
||||
|
||||
/* Result row */
|
||||
.pal__row {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: 3.25rem 1fr auto 1.5rem;
|
||||
gap: var(--space-sm);
|
||||
align-items: baseline;
|
||||
padding: 0.55rem 1rem;
|
||||
cursor: pointer;
|
||||
height: 2.4rem;
|
||||
transition: background-color 0ms; /* hover is instant */
|
||||
}
|
||||
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
.pal__row:hover { background-color: var(--color-row-hover); }
|
||||
}
|
||||
|
||||
.pal__row[aria-selected="true"] { background: transparent; /* tracker shows selection */ }
|
||||
|
||||
.pal__row__year {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-xs);
|
||||
color: var(--color-muted);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.pal__row__title {
|
||||
font-size: var(--text-base);
|
||||
color: var(--color-ink);
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.003em;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.pal__row__author {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--color-neutral);
|
||||
font-weight: 400;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.pal__row__status {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-sm);
|
||||
text-align: center;
|
||||
color: var(--color-muted);
|
||||
}
|
||||
|
||||
.pal__row__status[data-state="reading"] { color: var(--color-accent); }
|
||||
.pal__row__status[data-state="queued"] { color: var(--color-rule-2); }
|
||||
|
||||
/* Match highlighting */
|
||||
.pal__row mark {
|
||||
background: var(--color-mark-bg);
|
||||
color: inherit;
|
||||
padding: 0;
|
||||
border-radius: 1px;
|
||||
box-shadow: 0 -1px 0 var(--color-accent) inset;
|
||||
}
|
||||
|
||||
/* Empty state */
|
||||
.pal__empty {
|
||||
padding: 2rem 1rem;
|
||||
text-align: center;
|
||||
font-family: var(--font-sans);
|
||||
color: var(--color-muted);
|
||||
font-size: var(--text-sm);
|
||||
line-height: var(--lh-relaxed);
|
||||
}
|
||||
.pal__empty .q {
|
||||
color: var(--color-ink);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Footer (kbd hints) */
|
||||
.pal__foot {
|
||||
display: flex;
|
||||
gap: var(--space-md);
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
padding: 0.55rem 1rem;
|
||||
border-top: 1px solid var(--color-rule);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.6875rem;
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--color-muted);
|
||||
}
|
||||
|
||||
.pal__hint { display: inline-flex; align-items: center; gap: 0.4rem; }
|
||||
.pal__hint .kbd { color: var(--color-ink-2); border-color: var(--color-rule-2); opacity: 1; }
|
||||
|
||||
/* Reduced motion: collapse spatial motion to opacity */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
dialog.palette[open],
|
||||
dialog.palette[open]::backdrop,
|
||||
dialog.palette.closing,
|
||||
dialog.palette.closing::backdrop {
|
||||
animation-duration: 150ms !important;
|
||||
}
|
||||
@keyframes palette-in { from { opacity: 0; } to { opacity: 1; } }
|
||||
@keyframes palette-out { to { opacity: 0; } }
|
||||
|
||||
.pal__tracker { transition: none; }
|
||||
*, *::before, *::after {
|
||||
animation-duration: 150ms !important;
|
||||
transition-duration: 150ms !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Mobile palette tweaks */
|
||||
@media (max-width: 36rem) {
|
||||
dialog.palette { margin-top: 4vh; max-height: 92vh; }
|
||||
.pal { max-height: 90vh; }
|
||||
.pal__row { grid-template-columns: 2.5rem 1fr 1.5rem; }
|
||||
.pal__row__author { display: block; margin-left: 0; }
|
||||
.pal__row__status { display: none; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<main class="page">
|
||||
|
||||
<header class="head">
|
||||
<h1 class="wordmark">
|
||||
Lectern
|
||||
<span class="count" id="library-count"></span>
|
||||
</h1>
|
||||
<p class="meta">A personal library</p>
|
||||
</header>
|
||||
|
||||
<div class="legend" aria-label="Status legend">
|
||||
<span class="legend__item"><span style="color: var(--color-accent)">●</span> reading</span>
|
||||
<span class="legend__item"><span style="color: var(--color-muted)">✓</span> read</span>
|
||||
<span class="legend__item"><span style="color: var(--color-rule-2)">○</span> queued</span>
|
||||
</div>
|
||||
|
||||
<ul class="books" id="library" role="list" aria-label="Books"></ul>
|
||||
|
||||
<footer class="foot">
|
||||
<span>Press <span class="kbd">⌘K</span> to find a book</span>
|
||||
<span>esc to close · ↵ to open</span>
|
||||
</footer>
|
||||
|
||||
</main>
|
||||
|
||||
<button class="invite" type="button" id="invite" aria-label="Open command palette">
|
||||
<span>Find a book</span>
|
||||
<span class="kbd">⌘K</span>
|
||||
</button>
|
||||
|
||||
<dialog class="palette" id="palette" aria-label="Command palette">
|
||||
<div class="pal">
|
||||
|
||||
<div class="pal__input-row">
|
||||
<span class="pal__leading" aria-hidden="true">/</span>
|
||||
<input
|
||||
class="pal__input"
|
||||
id="pal-input"
|
||||
type="search"
|
||||
autocomplete="off"
|
||||
autocorrect="off"
|
||||
autocapitalize="off"
|
||||
spellcheck="false"
|
||||
placeholder="Search 31 books"
|
||||
aria-label="Search books"
|
||||
aria-controls="pal-results"
|
||||
aria-activedescendant=""
|
||||
/>
|
||||
<span class="pal__count" id="pal-count">31</span>
|
||||
</div>
|
||||
|
||||
<div class="pal__results" id="pal-results" role="listbox" aria-label="Search results">
|
||||
<div class="pal__tracker" aria-hidden="true"></div>
|
||||
</div>
|
||||
|
||||
<div class="pal__foot">
|
||||
<span class="pal__hint"><span class="kbd">↑</span><span class="kbd">↓</span> navigate</span>
|
||||
<span class="pal__hint"><span class="kbd">↵</span> open</span>
|
||||
<span class="pal__hint"><span class="kbd">esc</span> close</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<script type="module">
|
||||
/* ─── Data: ~31 books a developer might keep on a personal reading list ── */
|
||||
const BOOKS = [
|
||||
{ y: 1968, t: "The Art of Computer Programming", a: "Donald E. Knuth", s: "queued" },
|
||||
{ y: 1974, t: "Zen and the Art of Motorcycle Maintenance", a: "Robert M. Pirsig", s: "read" },
|
||||
{ y: 1975, t: "The Mythical Man-Month", a: "Frederick P. Brooks Jr.", s: "read" },
|
||||
{ y: 1977, t: "A Pattern Language", a: "Christopher Alexander", s: "read" },
|
||||
{ y: 1979, t: "Gödel, Escher, Bach", a: "Douglas R. Hofstadter", s: "reading" },
|
||||
{ y: 1981, t: "The Soul of a New Machine", a: "Tracy Kidder", s: "read" },
|
||||
{ y: 1983, t: "The Visual Display of Quantitative Information", a: "Edward R. Tufte", s: "read" },
|
||||
{ y: 1985, t: "Structure and Interpretation of Computer Programs", a: "Abelson & Sussman", s: "queued" },
|
||||
{ y: 1985, t: "Tools for Thought", a: "Howard Rheingold", s: "read" },
|
||||
{ y: 1988, t: "The Design of Everyday Things", a: "Don Norman", s: "read" },
|
||||
{ y: 1989, t: "The Cuckoo's Egg", a: "Cliff Stoll", s: "read" },
|
||||
{ y: 1990, t: "Envisioning Information", a: "Edward R. Tufte", s: "read" },
|
||||
{ y: 1992, t: "The Elements of Typographic Style", a: "Robert Bringhurst", s: "read" },
|
||||
{ y: 1994, t: "Out of Control", a: "Kevin Kelly", s: "queued" },
|
||||
{ y: 1996, t: "Where Wizards Stay Up Late", a: "Hafner & Lyon", s: "read" },
|
||||
{ y: 1997, t: "The Innovator's Dilemma", a: "Clayton M. Christensen", s: "read" },
|
||||
{ y: 1998, t: "Seeing Like a State", a: "James C. Scott", s: "reading" },
|
||||
{ y: 1999, t: "Code", a: "Charles Petzold", s: "read" },
|
||||
{ y: 1999, t: "The Pragmatic Programmer", a: "Hunt & Thomas", s: "read" },
|
||||
{ y: 1999, t: "Refactoring", a: "Martin Fowler", s: "read" },
|
||||
{ y: 2000, t: "Don't Make Me Think", a: "Steve Krug", s: "read" },
|
||||
{ y: 2003, t: "Domain-Driven Design", a: "Eric Evans", s: "read" },
|
||||
{ y: 2003, t: "Universal Principles of Design", a: "Lidwell, Holden & Butler", s: "queued" },
|
||||
{ y: 2004, t: "Hackers and Painters", a: "Paul Graham", s: "read" },
|
||||
{ y: 2004, t: "Working Effectively with Legacy Code", a: "Michael Feathers", s: "read" },
|
||||
{ y: 2008, t: "Clean Code", a: "Robert C. Martin", s: "read" },
|
||||
{ y: 2011, t: "The Information", a: "James Gleick", s: "read" },
|
||||
{ y: 2017, t: "Designing Data-Intensive Applications", a: "Martin Kleppmann", s: "reading" },
|
||||
{ y: 2019, t: "Software Engineering at Google", a: "Winters, Manshreck & Wright", s: "queued" },
|
||||
{ y: 2021, t: "A Philosophy of Software Design", a: "John Ousterhout", s: "read" },
|
||||
{ y: 2024, t: "Tidy First?", a: "Kent Beck", s: "queued" }
|
||||
];
|
||||
|
||||
/* ─── Render ambient library list ── */
|
||||
const STATUS_GLYPH = { reading: "●", read: "✓", queued: "○" };
|
||||
|
||||
const lib = document.getElementById("library");
|
||||
const libraryCount = document.getElementById("library-count");
|
||||
libraryCount.textContent = `· ${BOOKS.length} books`;
|
||||
|
||||
function renderLibrary() {
|
||||
const sorted = [...BOOKS].sort((a, b) => b.y - a.y);
|
||||
lib.innerHTML = sorted.map((b) => `
|
||||
<li class="book" role="listitem">
|
||||
<span class="book__year">${b.y}</span>
|
||||
<span><span class="book__title">${escape(b.t)}</span><span class="book__author">${escape(b.a)}</span></span>
|
||||
<span class="book__status" data-state="${b.s}" title="${b.s}" aria-label="${b.s}">${STATUS_GLYPH[b.s]}</span>
|
||||
</li>
|
||||
`).join("");
|
||||
}
|
||||
|
||||
function escape(s) {
|
||||
return s.replace(/[&<>"']/g, (c) => ({ "&":"&", "<":"<", ">":">", '"':""", "'":"'" }[c]));
|
||||
}
|
||||
|
||||
renderLibrary();
|
||||
|
||||
/* ─── Fuzzy match: in-order character match, score by spread + length ── */
|
||||
function fuzzyMatch(query, text) {
|
||||
if (!query) return { match: true, score: 0, positions: [] };
|
||||
const q = query.toLowerCase();
|
||||
const t = text.toLowerCase();
|
||||
const positions = [];
|
||||
let qi = 0;
|
||||
for (let ti = 0; ti < t.length && qi < q.length; ti++) {
|
||||
if (t[ti] === q[qi]) {
|
||||
positions.push(ti);
|
||||
qi++;
|
||||
}
|
||||
}
|
||||
if (qi !== q.length) return { match: false };
|
||||
const spread = positions[positions.length - 1] - positions[0];
|
||||
// Lower score is better
|
||||
const score = spread + text.length * 0.2;
|
||||
return { match: true, score, positions };
|
||||
}
|
||||
|
||||
function highlight(text, positions) {
|
||||
if (!positions.length) return escape(text);
|
||||
const set = new Set(positions);
|
||||
let out = "";
|
||||
let inMark = false;
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
if (set.has(i) && !inMark) { out += "<mark>"; inMark = true; }
|
||||
if (!set.has(i) && inMark) { out += "</mark>"; inMark = false; }
|
||||
const c = text[i];
|
||||
out += ({ "&":"&", "<":"<", ">":">", '"':""", "'":"'" }[c]) || c;
|
||||
}
|
||||
if (inMark) out += "</mark>";
|
||||
return out;
|
||||
}
|
||||
|
||||
/* ─── Palette state ── */
|
||||
const dlg = document.getElementById("palette");
|
||||
const input = document.getElementById("pal-input");
|
||||
const results = document.getElementById("pal-results");
|
||||
const tracker = results.querySelector(".pal__tracker");
|
||||
const count = document.getElementById("pal-count");
|
||||
const invite = document.getElementById("invite");
|
||||
|
||||
let currentResults = [];
|
||||
let selectedIndex = 0;
|
||||
let debounce = null;
|
||||
|
||||
function search(query) {
|
||||
const matches = BOOKS
|
||||
.map((b, i) => {
|
||||
const titleHit = fuzzyMatch(query, b.t);
|
||||
const authorHit = fuzzyMatch(query, b.a);
|
||||
// Best of (title, author); prefer title hits
|
||||
if (titleHit.match) return { book: b, on: "title", hit: titleHit, score: titleHit.score };
|
||||
if (authorHit.match) return { book: b, on: "author", hit: authorHit, score: authorHit.score + 100 };
|
||||
return null;
|
||||
})
|
||||
.filter(Boolean)
|
||||
.sort((a, z) => a.score - z.score);
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
function render() {
|
||||
const q = input.value.trim();
|
||||
currentResults = search(q);
|
||||
count.textContent = String(currentResults.length);
|
||||
|
||||
// clamp selection
|
||||
if (selectedIndex >= currentResults.length) selectedIndex = currentResults.length - 1;
|
||||
if (selectedIndex < 0) selectedIndex = 0;
|
||||
|
||||
// Empty state
|
||||
if (!currentResults.length) {
|
||||
results.dataset.empty = "true";
|
||||
// Preserve the tracker element when re-rendering
|
||||
results.innerHTML = "";
|
||||
results.appendChild(tracker);
|
||||
const empty = document.createElement("div");
|
||||
empty.className = "pal__empty";
|
||||
empty.innerHTML = q
|
||||
? `Nothing matches <span class="q">“${escape(q)}”</span>. Try a shorter query, or check the year.`
|
||||
: `Start typing to search.`;
|
||||
results.appendChild(empty);
|
||||
input.setAttribute("aria-activedescendant", "");
|
||||
return;
|
||||
}
|
||||
|
||||
results.dataset.empty = "false";
|
||||
const html = currentResults.map((m, i) => {
|
||||
const { book, on, hit } = m;
|
||||
const titleHtml = on === "title" ? highlight(book.t, hit.positions) : escape(book.t);
|
||||
const authorHtml = on === "author" ? highlight(book.a, hit.positions) : escape(book.a);
|
||||
return `
|
||||
<div class="pal__row" role="option" id="pal-row-${i}" data-index="${i}" aria-selected="${i === selectedIndex}">
|
||||
<span class="pal__row__year">${book.y}</span>
|
||||
<span><span class="pal__row__title">${titleHtml}</span><span class="pal__row__author">${authorHtml}</span></span>
|
||||
<span></span>
|
||||
<span class="pal__row__status" data-state="${book.s}" aria-label="${book.s}" title="${book.s}">${STATUS_GLYPH[book.s]}</span>
|
||||
</div>
|
||||
`;
|
||||
}).join("");
|
||||
|
||||
results.innerHTML = "";
|
||||
results.appendChild(tracker);
|
||||
const wrap = document.createElement("div");
|
||||
wrap.innerHTML = html;
|
||||
while (wrap.firstChild) results.appendChild(wrap.firstChild);
|
||||
|
||||
input.setAttribute("aria-activedescendant", `pal-row-${selectedIndex}`);
|
||||
moveTracker();
|
||||
}
|
||||
|
||||
function moveTracker() {
|
||||
const row = results.querySelector(`[data-index="${selectedIndex}"]`);
|
||||
if (!row) { tracker.style.opacity = "0"; return; }
|
||||
tracker.style.opacity = "1";
|
||||
// Position the tracker over the selected row
|
||||
const rowOffset = row.offsetTop;
|
||||
tracker.style.transform = `translateY(${rowOffset}px)`;
|
||||
// Ensure visible in scroll area
|
||||
row.scrollIntoView({ block: "nearest", behavior: "instant" });
|
||||
}
|
||||
|
||||
function move(delta) {
|
||||
if (!currentResults.length) return;
|
||||
selectedIndex = (selectedIndex + delta + currentResults.length) % currentResults.length;
|
||||
results.querySelectorAll(".pal__row").forEach((el) => {
|
||||
el.setAttribute("aria-selected", el.dataset.index == selectedIndex ? "true" : "false");
|
||||
});
|
||||
input.setAttribute("aria-activedescendant", `pal-row-${selectedIndex}`);
|
||||
moveTracker();
|
||||
}
|
||||
|
||||
function commit() {
|
||||
const m = currentResults[selectedIndex];
|
||||
if (!m) return;
|
||||
// "Open the book" — silent success: just close the palette.
|
||||
// In a real app, this would route to /book/<slug>.
|
||||
console.log("Open:", m.book.t);
|
||||
close();
|
||||
}
|
||||
|
||||
function open() {
|
||||
input.value = "";
|
||||
selectedIndex = 0;
|
||||
render();
|
||||
dlg.showModal();
|
||||
// Auto-focus input — already handled by autofocus+ showModal
|
||||
requestAnimationFrame(() => input.focus());
|
||||
}
|
||||
|
||||
function close() {
|
||||
// Apply a brief exit animation then close
|
||||
const reduced = matchMedia("(prefers-reduced-motion: reduce)").matches;
|
||||
if (reduced) {
|
||||
dlg.close();
|
||||
return;
|
||||
}
|
||||
dlg.classList.add("closing");
|
||||
const onEnd = () => {
|
||||
dlg.classList.remove("closing");
|
||||
dlg.close();
|
||||
dlg.removeEventListener("animationend", onEnd);
|
||||
};
|
||||
dlg.addEventListener("animationend", onEnd);
|
||||
}
|
||||
|
||||
/* ─── Event wiring ── */
|
||||
|
||||
// ⌘K / Ctrl+K to open
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if ((e.metaKey || e.ctrlKey) && (e.key === "k" || e.key === "K")) {
|
||||
e.preventDefault();
|
||||
if (dlg.open) close(); else open();
|
||||
}
|
||||
});
|
||||
|
||||
invite.addEventListener("click", open);
|
||||
|
||||
// Click outside to close (the dialog backdrop)
|
||||
dlg.addEventListener("click", (e) => {
|
||||
const rect = dlg.getBoundingClientRect();
|
||||
const inDialog = e.clientX >= rect.left && e.clientX <= rect.right
|
||||
&& e.clientY >= rect.top && e.clientY <= rect.bottom;
|
||||
if (!inDialog) close();
|
||||
});
|
||||
|
||||
// Cancel (Escape) — let dialog handle its native close, but use our exit animation
|
||||
dlg.addEventListener("cancel", (e) => {
|
||||
e.preventDefault();
|
||||
close();
|
||||
});
|
||||
|
||||
// Input — search-as-you-type, debounced 180ms
|
||||
input.addEventListener("input", () => {
|
||||
clearTimeout(debounce);
|
||||
debounce = setTimeout(render, 180);
|
||||
// Also reset selection to top on each keystroke
|
||||
selectedIndex = 0;
|
||||
});
|
||||
|
||||
// Keyboard nav on the input
|
||||
input.addEventListener("keydown", (e) => {
|
||||
if (e.key === "ArrowDown") { e.preventDefault(); move(1); }
|
||||
else if (e.key === "ArrowUp") { e.preventDefault(); move(-1); }
|
||||
else if (e.key === "Home") { e.preventDefault(); selectedIndex = 0; move(0); }
|
||||
else if (e.key === "End") { e.preventDefault(); selectedIndex = currentResults.length - 1; move(0); }
|
||||
else if (e.key === "Enter") { e.preventDefault(); commit(); }
|
||||
});
|
||||
|
||||
// Click a result to select + open
|
||||
results.addEventListener("click", (e) => {
|
||||
const row = e.target.closest(".pal__row");
|
||||
if (!row) return;
|
||||
selectedIndex = Number(row.dataset.index);
|
||||
commit();
|
||||
});
|
||||
|
||||
// Hover updates selection visually (matches Linear / Raycast behaviour)
|
||||
results.addEventListener("mousemove", (e) => {
|
||||
const row = e.target.closest(".pal__row");
|
||||
if (!row) return;
|
||||
const idx = Number(row.dataset.index);
|
||||
if (idx !== selectedIndex) {
|
||||
selectedIndex = idx;
|
||||
results.querySelectorAll(".pal__row").forEach((el) => {
|
||||
el.setAttribute("aria-selected", el.dataset.index == selectedIndex ? "true" : "false");
|
||||
});
|
||||
input.setAttribute("aria-activedescendant", `pal-row-${selectedIndex}`);
|
||||
moveTracker();
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
Before Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 220 KiB |
|
Before Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 375 KiB |
|
Before Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 31 KiB |
@@ -5,14 +5,13 @@ Each verb demonstrated on a tiny, realistic input. Folders contain the input, th
|
||||
| Verb | Folder | Input | What the verb does |
|
||||
| --- | --- | --- | --- |
|
||||
| `audit` | [`audit/`](audit/) | An AI-templated landing fragment | Read it, score against the anti-pattern list, return a ranked punch list. **Does not edit.** |
|
||||
| `refine` | [`refine/`](refine/) | A structurally-OK landing with bad colour / spacing / easings | Apply the ruleset with the smallest possible diff. **Does not redesign.** |
|
||||
| `redesign` | [`redesign/`](redesign/) | A page with the AI structural template (centered hero · 3 cards · CTA · footer) | Throw out the structure, rebuild with a different fingerprint. **Preserves copy + IA.** |
|
||||
| `study` | [`study/`](study/) | A described screenshot of a Pentagram-style portfolio | Extract DNA — macrostructure, archetypes, type-pairing role, accent — return a diagnosis report, then rebuild user content with that DNA. **Never copies pixels.** |
|
||||
|
||||
Each folder holds:
|
||||
|
||||
- **`input.html`** (or `input-description.md` for `study`) — what the user paste / attach.
|
||||
- **`output.*`** — what the verb produces. For `audit` and `study`, this is a Markdown report. For `refine` and `redesign`, this is a finished `index.html` + `style.css`.
|
||||
- **`output.*`** — what the verb produces. For `audit` and `study`, this is a Markdown report. For `redesign`, this is a finished `index.html` + `style.css`.
|
||||
- **`notes.md`** — one page explaining what fired, what reference files loaded, and what the durable artifact is.
|
||||
|
||||
The four verb tests together exercise every load path in the skill that the eight default-flow tests don't.
|
||||
The three verb tests together exercise every load path in the skill that the default-flow tests don't.
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Codeline — changelog</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
background: #ffffff;
|
||||
color: #222222;
|
||||
line-height: 1.5;
|
||||
max-width: 720px;
|
||||
margin: 60px auto;
|
||||
padding: 0 30px;
|
||||
}
|
||||
h1 {
|
||||
font-size: 38px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.lede {
|
||||
color: #777777;
|
||||
margin-bottom: 50px;
|
||||
font-size: 16px;
|
||||
}
|
||||
.release {
|
||||
margin-bottom: 50px;
|
||||
padding-bottom: 30px;
|
||||
border-bottom: 1px solid #eaeaea;
|
||||
}
|
||||
.release:last-child { border-bottom: none; }
|
||||
.release h2 {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.release time {
|
||||
color: #999999;
|
||||
font-size: 13px;
|
||||
display: block;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.release ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.release li {
|
||||
padding: 8px 0 8px 17px;
|
||||
position: relative;
|
||||
color: #444444;
|
||||
}
|
||||
.release li::before {
|
||||
content: "—";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: #cccccc;
|
||||
}
|
||||
.release li.added::before { color: #1aab30; content: "+"; }
|
||||
.release li.fixed::before { color: #b08017; content: "·"; }
|
||||
.release li.removed::before { color: #b53d3d; content: "−"; }
|
||||
.release li code {
|
||||
font-family: monospace;
|
||||
background: #f4f4f4;
|
||||
padding: 1px 5px;
|
||||
border-radius: 3px;
|
||||
font-size: 13px;
|
||||
}
|
||||
.release a {
|
||||
color: #2050b8;
|
||||
text-decoration: none;
|
||||
transition: all 250ms cubic-bezier(0.4, 0.0, 0.2, 1);
|
||||
}
|
||||
.release a:hover { color: #4070d8; text-decoration: underline; }
|
||||
footer {
|
||||
margin-top: 80px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #eaeaea;
|
||||
color: #aaaaaa;
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Codeline · changelog</h1>
|
||||
<p class="lede">A small dev-tools shop. Releases ship Tuesday mornings, twice a month.</p>
|
||||
|
||||
<article class="release">
|
||||
<h2>v0.7.4</h2>
|
||||
<time>2026-04-29</time>
|
||||
<ul>
|
||||
<li class="added">Add <code>--watch</code> flag to <code>codeline build</code> for incremental rebuilds (closes <a href="#">#412</a>).</li>
|
||||
<li class="added">Add JSON output mode to <code>codeline lint</code>; pipe-friendly for CI.</li>
|
||||
<li class="fixed">Fix <code>tsconfig.json</code> resolution failing when <code>extends</code> points outside the workspace root.</li>
|
||||
<li class="fixed">Fix incorrect exit code (<code>0</code> instead of <code>1</code>) when <code>codeline lint</code> finds errors but no warnings.</li>
|
||||
<li class="removed">Deprecate <code>--legacy-loader</code>; will be removed in v0.9.</li>
|
||||
</ul>
|
||||
</article>
|
||||
|
||||
<article class="release">
|
||||
<h2>v0.7.3</h2>
|
||||
<time>2026-04-15</time>
|
||||
<ul>
|
||||
<li class="fixed">Fix race condition in the file-watcher when many small files change simultaneously (e.g., during a <code>git checkout</code>).</li>
|
||||
<li class="fixed">Fix the loader silently swallowing parse errors in <code>postcss</code> plugin chains.</li>
|
||||
<li class="added">Add <code>codeline doctor</code> — a diagnostic command that checks node version, lockfile parity, and config validity.</li>
|
||||
</ul>
|
||||
</article>
|
||||
|
||||
<article class="release">
|
||||
<h2>v0.7.2</h2>
|
||||
<time>2026-04-01</time>
|
||||
<ul>
|
||||
<li class="added">Initial public release. Read the <a href="#">getting-started guide</a>.</li>
|
||||
</ul>
|
||||
</article>
|
||||
|
||||
<footer>
|
||||
Codeline · MIT licensed · maintained by two engineers in Lisbon.
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,49 +0,0 @@
|
||||
# `refine` verb · what changed and why
|
||||
|
||||
## Input
|
||||
|
||||
`input.html` — a 95-line single-file changelog page for a fictional dev-tools shop called Codeline. **The structure is correct** — a Long Document macrostructure with three release entries, a header, and a footer. The user is happy with the shape; they want it polished.
|
||||
|
||||
## What `refine` does
|
||||
|
||||
> *"Apply the ruleset to polish the target in place. Preserve layout structure. Smallest possible diff. No redesign."*
|
||||
|
||||
The DOM is **identical**. The CSS rules are rewritten to use OKLCH tokens, named easings, the project's font stack, and the eight-states discipline. No element moved. No element added or removed. No `class` renamed (except where necessary).
|
||||
|
||||
## Diff list — what changed and which reference file prompted it
|
||||
|
||||
| Change | From | To | Reference |
|
||||
| --- | --- | --- | --- |
|
||||
| **1.** Replaced all hex / rgb colours with OKLCH tokens at `:root` | `#222`, `#777`, `#999`, `#444`, `#aaa`, `#eaeaea`, `#1aab30`, `#b08017`, `#b53d3d`, `#2050b8` | `--color-ink`, `--color-ink-soft`, `--color-rule`, `--color-accent`, etc. | [`color.md`](../../../skill/references/color.md) — palette in OKLCH, named tokens by role |
|
||||
| **2.** Switched paper from pure `#fff` to a tinted near-white (`oklch(98% 0.005 240)`) | `background: #ffffff` | `background: var(--color-paper)` | [`color.md`](../../../skill/references/color.md) — never pure-white as a base |
|
||||
| **3.** Replaced system font stack with a paired Geist + Geist Mono | `-apple-system, BlinkMacSystemFont, sans-serif` | Geist (display + body), Geist Mono (code) | [`typography.md`](../../../skill/references/typography.md) — display + body pairing, mono for code |
|
||||
| **4.** Replaced `transition: all 250ms cubic-bezier(0.4, 0.0, 0.2, 1)` on links with explicit-property + named easing | `transition: all 250ms cubic-bezier(0.4, 0.0, 0.2, 1)` | `transition: text-decoration-color var(--dur-micro) var(--ease-out)` | [`microinteractions.md`](../../../skill/references/microinteractions.md) — never `all`; specify the property |
|
||||
| **5.** Added a 4-pt spacing scale at `:root` (`--space-2xs` through `--space-2xl`) and replaced raw px values | `60px`, `30px`, `8px`, `50px`, `18px`, `17px` | `var(--space-2xl)`, `var(--space-md)`, `var(--space-2xs)`, etc. | [`layout-and-space.md`](../../../skill/references/layout-and-space.md) — 4-pt scale, semantic tokens |
|
||||
| **6.** Added `:focus-visible` ring to all `<a>` elements with `outline: 2px solid var(--color-accent); outline-offset: 3px;` | (missing) | added | [`interaction-and-states.md`](../../../skill/references/interaction-and-states.md) — eight states; gate 28 |
|
||||
| **7.** Added `@media (prefers-reduced-motion: reduce)` block to disable transitions | (missing) | added | [`motion.md`](../../../skill/references/motion.md) — gate 29 |
|
||||
| **8.** Switched typography on the `time` element to monospaced for tabular numerics | sans default | `font-family: var(--font-mono); font-variant-numeric: tabular-nums;` | [`typography.md`](../../../skill/references/typography.md) — mono for dates / data |
|
||||
| **9.** Switched units to `rem`, fluid type via `clamp()` where appropriate | px throughout | rem + clamp | [`responsive.md`](../../../skill/references/responsive.md) — accessibility-first sizing |
|
||||
| **10.** Added `html, body { overflow-x: clip; }` for layout-safety | (missing) | added | [`layout-and-space.md`](../../../skill/references/layout-and-space.md) § page-edge clipping; gate 36 |
|
||||
| **11.** Stamped the file at the top of `<style>` | (no stamp) | `/* Hallmark · refined · macrostructure: Long Document (preserved) · theme: Plain · accent: cool-ink-blue ~2% */` | [`SKILL.md`](../../../skill/SKILL.md) Step 6 — stamp the output |
|
||||
| **12.** Tinted the link underline to use `text-decoration-color` so the underline can fade on hover without animating layout | underline + colour change in 250 ms | static underline + colour-mix transition only | [`microinteractions.md`](../../../skill/references/microinteractions.md) — animate paint, not layout |
|
||||
|
||||
## Slop test
|
||||
|
||||
| Before | After |
|
||||
| --- | --- |
|
||||
| 12 / 38 ✓ (failed gates 1, 4 (no token system), 9 (acceptable), 11, 16, 24, 26, 28, 29, 36) | 38 / 38 ✓ |
|
||||
|
||||
## Durable artifact
|
||||
|
||||
The `output.html` is what the user keeps. The DOM is unchanged so any JS / framework wrapper around the page still works. Total LOC delta: input had 95 lines, output has 138 lines (mostly tokens at `:root`).
|
||||
|
||||
## What `refine` *did not* do
|
||||
|
||||
- Did not add a hero
|
||||
- Did not add a CTA
|
||||
- Did not change the section order
|
||||
- Did not insert any `<svg>`, illustration, or marketing copy
|
||||
- Did not change the brand voice
|
||||
- Did not remove the colour-coded list bullets (the user clearly wants them; the change is to use proper OKLCH tokens, not abandon the pattern)
|
||||
|
||||
That's the discipline. `refine` is a polish-in-place verb. Use `redesign` when the structure itself needs work — see [`../redesign/`](../redesign/) for that case.
|
||||
@@ -1,193 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Codeline — changelog</title>
|
||||
<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=Geist+Mono:wght@400;500&family=Geist:wght@400;500;600&display=swap" />
|
||||
<style>
|
||||
/* Hallmark · refined · macrostructure: Long Document (preserved) · theme: Plain · accent: cool-ink-blue ~2% */
|
||||
:root {
|
||||
--color-paper: oklch(98% 0.005 240);
|
||||
--color-paper-2: oklch(95% 0.006 240);
|
||||
--color-rule: oklch(88% 0.008 240);
|
||||
--color-ink: oklch(22% 0.012 245);
|
||||
--color-ink-soft: oklch(48% 0.014 245);
|
||||
--color-ink-muted: oklch(68% 0.014 245);
|
||||
--color-accent: oklch(36% 0.16 248);
|
||||
--color-added: oklch(50% 0.16 145);
|
||||
--color-fixed: oklch(58% 0.13 70);
|
||||
--color-removed: oklch(54% 0.18 28);
|
||||
|
||||
--font-display: "Geist", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
--font-body: "Geist", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
--font-mono: "Geist Mono", "JetBrains Mono", ui-monospace, SFMono-Regular, monospace;
|
||||
|
||||
--space-2xs: 0.5rem;
|
||||
--space-xs: 0.75rem;
|
||||
--space-sm: 1rem;
|
||||
--space-md: 1.5rem;
|
||||
--space-lg: 2rem;
|
||||
--space-xl: 3rem;
|
||||
--space-2xl: 4.5rem;
|
||||
|
||||
--ease-out: cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
--dur-micro: 120ms;
|
||||
--dur-short: 200ms;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
html, body { overflow-x: clip; }
|
||||
body {
|
||||
font-family: var(--font-body);
|
||||
background: var(--color-paper);
|
||||
color: var(--color-ink);
|
||||
line-height: 1.55;
|
||||
max-width: 45rem;
|
||||
margin: var(--space-2xl) auto;
|
||||
padding: 0 var(--space-md);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: var(--font-display);
|
||||
font-size: 2.25rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.02em;
|
||||
line-height: 1.1;
|
||||
margin-block-end: var(--space-2xs);
|
||||
}
|
||||
h1 .dot { color: var(--color-accent); }
|
||||
|
||||
.lede {
|
||||
color: var(--color-ink-soft);
|
||||
margin-block-end: var(--space-2xl);
|
||||
font-size: 1rem;
|
||||
max-width: 56ch;
|
||||
}
|
||||
|
||||
.release {
|
||||
margin-block-end: var(--space-2xl);
|
||||
padding-block-end: var(--space-lg);
|
||||
border-block-end: 1px solid var(--color-rule);
|
||||
}
|
||||
.release:last-of-type { border-block-end: none; }
|
||||
|
||||
.release h2 {
|
||||
font-family: var(--font-display);
|
||||
font-size: 1.375rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.01em;
|
||||
margin-block-end: 2px;
|
||||
}
|
||||
|
||||
.release time {
|
||||
color: var(--color-ink-muted);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: 0.04em;
|
||||
display: block;
|
||||
margin-block-end: var(--space-md);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.release ul { list-style: none; padding: 0; margin: 0; }
|
||||
|
||||
.release li {
|
||||
padding: var(--space-2xs) 0 var(--space-2xs) 1.25rem;
|
||||
position: relative;
|
||||
color: var(--color-ink);
|
||||
font-size: 0.9375rem;
|
||||
}
|
||||
|
||||
.release li::before {
|
||||
content: "—";
|
||||
position: absolute;
|
||||
inset-inline-start: 0;
|
||||
color: var(--color-ink-muted);
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
.release li.added::before { color: var(--color-added); content: "+"; }
|
||||
.release li.fixed::before { color: var(--color-fixed); content: "·"; }
|
||||
.release li.removed::before { color: var(--color-removed); content: "−"; }
|
||||
|
||||
.release li code {
|
||||
font-family: var(--font-mono);
|
||||
background: var(--color-paper-2);
|
||||
padding: 1px 5px;
|
||||
border-radius: 2px;
|
||||
font-size: 0.8125rem;
|
||||
color: var(--color-ink);
|
||||
}
|
||||
|
||||
.release a {
|
||||
color: var(--color-accent);
|
||||
text-decoration: underline;
|
||||
text-decoration-color: oklch(88% 0.012 240);
|
||||
text-underline-offset: 3px;
|
||||
transition: text-decoration-color var(--dur-micro) var(--ease-out);
|
||||
}
|
||||
|
||||
.release a:hover { text-decoration-color: var(--color-accent); }
|
||||
.release a:focus-visible {
|
||||
outline: 2px solid var(--color-accent);
|
||||
outline-offset: 3px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-block-start: var(--space-2xl);
|
||||
padding-block-start: var(--space-md);
|
||||
border-block-start: 1px solid var(--color-rule);
|
||||
color: var(--color-ink-muted);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.release a { transition: none; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Codeline<span class="dot">·</span> changelog</h1>
|
||||
<p class="lede">A small dev-tools shop. Releases ship Tuesday mornings, twice a month.</p>
|
||||
|
||||
<article class="release">
|
||||
<h2>v0.7.4</h2>
|
||||
<time>2026-04-29</time>
|
||||
<ul>
|
||||
<li class="added">Add <code>--watch</code> flag to <code>codeline build</code> for incremental rebuilds (closes <a href="#">#412</a>).</li>
|
||||
<li class="added">Add JSON output mode to <code>codeline lint</code>; pipe-friendly for CI.</li>
|
||||
<li class="fixed">Fix <code>tsconfig.json</code> resolution failing when <code>extends</code> points outside the workspace root.</li>
|
||||
<li class="fixed">Fix incorrect exit code (<code>0</code> instead of <code>1</code>) when <code>codeline lint</code> finds errors but no warnings.</li>
|
||||
<li class="removed">Deprecate <code>--legacy-loader</code>; will be removed in v0.9.</li>
|
||||
</ul>
|
||||
</article>
|
||||
|
||||
<article class="release">
|
||||
<h2>v0.7.3</h2>
|
||||
<time>2026-04-15</time>
|
||||
<ul>
|
||||
<li class="fixed">Fix race condition in the file-watcher when many small files change simultaneously (e.g., during a <code>git checkout</code>).</li>
|
||||
<li class="fixed">Fix the loader silently swallowing parse errors in <code>postcss</code> plugin chains.</li>
|
||||
<li class="added">Add <code>codeline doctor</code> — a diagnostic command that checks node version, lockfile parity, and config validity.</li>
|
||||
</ul>
|
||||
</article>
|
||||
|
||||
<article class="release">
|
||||
<h2>v0.7.2</h2>
|
||||
<time>2026-04-01</time>
|
||||
<ul>
|
||||
<li class="added">Initial public release. Read the <a href="#">getting-started guide</a>.</li>
|
||||
</ul>
|
||||
</article>
|
||||
|
||||
<footer>
|
||||
Codeline · MIT licensed · maintained by two engineers in Lisbon.
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,132 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Tide — letters from the studio</title>
|
||||
<style>
|
||||
/* Hallmark · genre: editorial · macrostructure: Letter
|
||||
* theme: Salon · accent: warm cinnamon · paper: warm cream
|
||||
* nav: N6 Newspaper masthead · footer: Ft6 Letter close
|
||||
* polish: HP4 Decorative-numeral (issue 04 · means "fourth letter")
|
||||
* slop: pass (51–55) · contrast: pass (46–50)
|
||||
* brief: Tide — indie podcast for solo hosts. Warm, editorial.
|
||||
*/
|
||||
|
||||
@font-face { font-family: "FraunSerif"; src: local("Fraunces"), local("Source Serif 4"); font-display: swap; }
|
||||
|
||||
:root {
|
||||
--color-paper: oklch(96% 0.020 70);
|
||||
--color-paper-2: oklch(94% 0.022 65);
|
||||
--color-ink: oklch(20% 0.030 50);
|
||||
--color-ink-2: oklch(40% 0.030 55);
|
||||
--color-rule: oklch(78% 0.020 60);
|
||||
--color-accent: oklch(48% 0.140 40);
|
||||
--color-accent-ink: oklch(98% 0.005 70);
|
||||
--color-focus: oklch(58% 0.140 40);
|
||||
|
||||
--font-display: "FraunSerif", "Fraunces", "Source Serif 4", Georgia, serif;
|
||||
--font-body: "FraunSerif", "Fraunces", "Source Serif 4", Georgia, serif;
|
||||
|
||||
--space-2xs: 0.5rem; --space-xs: 0.75rem; --space-sm: 1rem;
|
||||
--space-md: 1.5rem; --space-lg: 2rem; --space-xl: 3rem;
|
||||
--space-2xl: 4.5rem; --space-3xl: 7rem;
|
||||
|
||||
--text-xs: 0.75rem; --text-sm: 0.875rem; --text-md: 1.125rem;
|
||||
--text-lg: 1.375rem; --text-xl: 1.75rem; --text-2xl: 2.25rem;
|
||||
|
||||
--rule-hair: 0.5px;
|
||||
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
|
||||
--dur-short: 220ms;
|
||||
|
||||
--page-gutter: clamp(1rem, 4vw, 2.5rem);
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
html, body { margin: 0; }
|
||||
body { font-family: var(--font-body); background: var(--color-paper); color: var(--color-ink); font-size: var(--text-md); line-height: 1.55; font-feature-settings: "onum"; }
|
||||
|
||||
a { color: inherit; }
|
||||
|
||||
/* N6 Newspaper masthead */
|
||||
.nav-mast { display: grid; gap: var(--space-2xs); padding: var(--space-md) var(--page-gutter) 0; text-align: center; }
|
||||
.mast-line { font-variant: small-caps; letter-spacing: 0.12em; font-size: var(--text-xs); color: var(--color-ink-2); margin: 0; }
|
||||
.mast-name { font-family: var(--font-display); font-size: clamp(2.5rem, 6vw, 4rem); letter-spacing: -0.01em; line-height: 0.95; font-style: italic; font-weight: 600; margin: 0; }
|
||||
.mast-nav ul { display: inline-flex; gap: var(--space-md); list-style: none; padding: 0; margin: var(--space-2xs) 0 0; flex-wrap: wrap; justify-content: center; }
|
||||
.mast-nav a { text-decoration: none; font-size: var(--text-sm); }
|
||||
.mast-nav a:hover { color: var(--color-accent); }
|
||||
.mast-rule { border: 0; border-top: var(--rule-hair) solid var(--color-rule); border-bottom: var(--rule-hair) solid var(--color-rule); height: 4px; margin: var(--space-sm) 0 0; }
|
||||
|
||||
/* Hero — Letter macro + HP4 decorative numeral */
|
||||
.hero { position: relative; padding: var(--space-2xl) var(--page-gutter) var(--space-3xl); max-width: 56ch; margin: 0 auto; overflow: hidden; }
|
||||
.hero__eyebrow { font-variant: small-caps; letter-spacing: 0.12em; font-size: var(--text-xs); color: var(--color-ink-2); margin: 0 0 var(--space-md); }
|
||||
.hero__salutation { font-style: italic; font-size: var(--text-lg); margin: 0 0 var(--space-md); }
|
||||
.hero__body { font-size: var(--text-md); line-height: 1.7; margin: 0 0 var(--space-md); }
|
||||
.hero__body em { color: var(--color-accent); font-style: italic; }
|
||||
.hero__sign { font-style: italic; }
|
||||
.hero__sign strong { font-style: normal; font-weight: 600; }
|
||||
.hero__num { position: absolute; right: -0.05em; bottom: -0.18em; font-family: var(--font-display); font-style: italic; font-weight: 600; font-size: clamp(8rem, 22vw, 16rem); line-height: 1; color: color-mix(in oklch, var(--color-ink) 7%, transparent); pointer-events: none; user-select: none; }
|
||||
|
||||
/* Section — single block of prose to show editorial rhythm */
|
||||
.note { padding: var(--space-2xl) var(--page-gutter); max-width: 56ch; margin: 0 auto; border-top: var(--rule-hair) solid var(--color-rule); }
|
||||
.note__head { font-family: var(--font-display); font-style: italic; font-size: var(--text-xl); margin: 0 0 var(--space-sm); }
|
||||
.note p { margin: 0 0 var(--space-sm); }
|
||||
|
||||
/* Ft6 Letter close */
|
||||
.foot-letter { padding: var(--space-2xl) var(--page-gutter); max-width: 56ch; margin: 0 auto; border-top: var(--rule-hair) solid var(--color-rule); }
|
||||
.foot-letter__close { font-family: var(--font-display); font-style: italic; font-size: var(--text-lg); line-height: 1.4; margin: 0 0 var(--space-md); }
|
||||
.foot-letter__sign { font-style: normal; font-weight: 600; }
|
||||
.foot-letter__ps { font-size: var(--text-sm); color: var(--color-ink-2); margin: 0; }
|
||||
.foot-letter__ps a { color: var(--color-accent); }
|
||||
|
||||
a:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; }
|
||||
|
||||
@media (max-width: 60rem) {
|
||||
.mast-name { font-size: clamp(2rem, 9vw, 3rem); }
|
||||
.hero__num { font-size: clamp(5rem, 26vw, 9rem); }
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) { * { animation: none !important; transition: none !important; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header class="nav-mast">
|
||||
<p class="mast-line">No 04 · Spring 2026 · Tide</p>
|
||||
<h1 class="mast-name">Tide</h1>
|
||||
<nav class="mast-nav" aria-label="Primary">
|
||||
<ul>
|
||||
<li><a href="#listen">Listen</a></li>
|
||||
<li><a href="#letters">Letters</a></li>
|
||||
<li><a href="#about">About</a></li>
|
||||
<li><a href="#subscribe">Subscribe</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<hr class="mast-rule" aria-hidden="true">
|
||||
</header>
|
||||
|
||||
<main>
|
||||
|
||||
<header class="hero">
|
||||
<p class="hero__eyebrow">A letter to the listeners · Sunday, 12 April</p>
|
||||
<p class="hero__salutation">Dear Tide listener,</p>
|
||||
<p class="hero__body">This is the fourth season. We've kept the format short on purpose — one host, one guest, one idea, forty minutes. <em>The shape works.</em> We'd rather record fewer episodes that earn their place than fill a feed.</p>
|
||||
<p class="hero__body">If you've subscribed since season three, this letter is yours too. The archive is open, the whole forty-three episodes, no paywall, no ads inside the audio.</p>
|
||||
<p class="hero__sign">Yours,<br><strong>— Tide</strong></p>
|
||||
<span class="hero__num" aria-hidden="true">04</span>
|
||||
</header>
|
||||
|
||||
<article class="note" id="letters">
|
||||
<h2 class="note__head">What's in this season.</h2>
|
||||
<p>Six conversations with people who've quietly shaped how we work — a typeface designer, a brewer, a high-school English teacher, a back-office accountant who runs a 15-person bakery, and two more we're still recording. Each episode opens with a letter — five paragraphs, written by hand the morning before — and closes with one question we'd like the next listener to bring to dinner.</p>
|
||||
<p>We publish on the second Sunday of every month. The next letter is due on the eleventh.</p>
|
||||
</article>
|
||||
|
||||
</main>
|
||||
|
||||
<footer class="foot-letter">
|
||||
<p class="foot-letter__close">Yours,<br><span class="foot-letter__sign">— Tide</span></p>
|
||||
<p class="foot-letter__ps">P.S. — letters back welcome at <a href="mailto:hello@tide.fm">hello@tide.fm</a>. We read them on Sunday mornings.</p>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,133 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>streampipe — open-source stream processing for the terminal</title>
|
||||
<style>
|
||||
/* Hallmark · genre: modern-minimal/terminal · macrostructure: Workbench
|
||||
* theme: Terminal · accent: phosphor green · paper: near-black
|
||||
* nav: N8 Terminal command (cursor INSIDE the prompt — purposeful)
|
||||
* footer: Ft4 Dense colophon
|
||||
* polish: none (terminal genre disallows decorative graphics — see structure.md)
|
||||
* slop: pass (51–55) · contrast: pass (46–50)
|
||||
* brief: streampipe — open-source CLI for stream processing. Terse, dev-tool.
|
||||
*/
|
||||
|
||||
@font-face { font-family: "Mono"; src: local("JetBrains Mono"), local("Commit Mono"), local("ui-monospace"); font-display: swap; }
|
||||
|
||||
:root {
|
||||
--color-paper: oklch(13% 0.020 145);
|
||||
--color-paper-2: oklch(17% 0.020 145);
|
||||
--color-ink: oklch(94% 0.010 140);
|
||||
--color-ink-2: oklch(70% 0.010 140);
|
||||
--color-rule: oklch(28% 0.020 140);
|
||||
--color-accent: oklch(78% 0.190 138);
|
||||
--color-accent-ink: oklch(13% 0.020 145);
|
||||
--color-focus: oklch(78% 0.190 138);
|
||||
|
||||
--font-body: "Mono", ui-monospace, "JetBrains Mono", "SF Mono", Menlo, monospace;
|
||||
--font-display: var(--font-body);
|
||||
|
||||
--space-2xs: 0.5rem; --space-xs: 0.75rem; --space-sm: 1rem;
|
||||
--space-md: 1.5rem; --space-lg: 2rem; --space-xl: 3rem;
|
||||
--space-2xl: 4.5rem;
|
||||
|
||||
--text-xs: 0.75rem; --text-sm: 0.875rem; --text-md: 1rem;
|
||||
--text-lg: 1.25rem; --text-xl: 1.625rem; --text-2xl: 2.25rem;
|
||||
|
||||
--rule-hair: 1px;
|
||||
--page-gutter: clamp(1rem, 4vw, 2.5rem);
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
html, body { margin: 0; }
|
||||
body { font-family: var(--font-body); background: var(--color-paper); color: var(--color-ink); font-size: var(--text-md); line-height: 1.6; }
|
||||
a { color: var(--color-accent); text-underline-offset: 2px; }
|
||||
a:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; }
|
||||
|
||||
/* N8 Terminal command nav — cursor lives INSIDE the prompt line */
|
||||
.nav-term { padding: var(--space-sm) var(--page-gutter); border-bottom: var(--rule-hair) solid var(--color-rule); background: var(--color-paper); }
|
||||
.nav-term__line { font-family: var(--font-body); font-size: var(--text-sm); margin: 0; white-space: pre-wrap; }
|
||||
.nav-term__line .prompt { color: var(--color-accent); padding-right: 0.4ch; }
|
||||
.nav-term__line a { color: var(--color-ink); text-decoration: underline; text-underline-offset: 3px; }
|
||||
.caret { display: inline-block; width: 1ch; color: var(--color-accent); animation: blink 1.05s steps(2) infinite; }
|
||||
@keyframes blink { 50% { opacity: 0; } }
|
||||
|
||||
/* Hero — Workbench: title + lede + install command (the install code is the only place the cursor reappears) */
|
||||
.hero { padding: var(--space-xl) var(--page-gutter) var(--space-2xl); max-width: 90ch; margin: 0 auto; }
|
||||
.hero__eyebrow { color: var(--color-accent); font-size: var(--text-sm); margin: 0 0 var(--space-md); }
|
||||
.hero__display { font-size: clamp(1.75rem, 4vw, 2.5rem); line-height: 1.1; letter-spacing: -0.01em; margin: 0 0 var(--space-md); font-weight: 600; }
|
||||
.hero__lede { color: var(--color-ink-2); max-width: 60ch; margin: 0 0 var(--space-lg); }
|
||||
.hero__cta-label { font-size: var(--text-xs); color: var(--color-ink-2); text-transform: uppercase; letter-spacing: 0.08em; margin: 0 0 var(--space-2xs); }
|
||||
.code { background: var(--color-paper-2); border: var(--rule-hair) solid var(--color-rule); padding: var(--space-sm) var(--space-md); border-radius: 0; display: inline-flex; align-items: center; gap: 0.6ch; font-size: var(--text-sm); margin: 0; }
|
||||
.code .prompt { color: var(--color-accent); }
|
||||
.code .caret { vertical-align: baseline; }
|
||||
|
||||
/* Section — Workbench: function reference table */
|
||||
.section { padding: var(--space-2xl) var(--page-gutter); max-width: 90ch; margin: 0 auto; border-top: var(--rule-hair) solid var(--color-rule); }
|
||||
.section__num { color: var(--color-accent); font-size: var(--text-sm); margin: 0 0 var(--space-2xs); }
|
||||
.section__head { font-size: var(--text-lg); font-weight: 600; margin: 0 0 var(--space-md); }
|
||||
.spec { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }
|
||||
.spec th, .spec td { padding: var(--space-2xs) var(--space-sm); border-bottom: var(--rule-hair) solid var(--color-rule); text-align: left; vertical-align: top; }
|
||||
.spec th { color: var(--color-ink-2); font-weight: 400; font-size: var(--text-xs); text-transform: uppercase; letter-spacing: 0.06em; }
|
||||
.spec td.k { color: var(--color-accent); }
|
||||
|
||||
/* Ft4 Dense colophon */
|
||||
.foot-dense { padding: var(--space-xl) var(--page-gutter); border-top: var(--rule-hair) solid var(--color-rule); font-size: var(--text-xs); color: var(--color-ink-2); }
|
||||
.foot-dense p { margin: 0; max-width: 90ch; line-height: 1.6; }
|
||||
.foot-dense a { color: var(--color-accent); }
|
||||
|
||||
@media (prefers-reduced-motion: reduce) { .caret { animation: none; opacity: 1; } * { animation: none !important; transition: none !important; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header class="nav-term">
|
||||
<pre class="nav-term__line"><span class="prompt">></span> streampipe <a href="#install">--install</a> <a href="#docs">--docs</a> <a href="#examples">--examples</a> <a href="https://github.com/streampipe/streampipe">--source</a><span class="caret" aria-hidden="true">▮</span></pre>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
|
||||
<section class="hero">
|
||||
<p class="hero__eyebrow">v2.4.0 · MIT · 12k stars</p>
|
||||
<h1 class="hero__display">Stream processing for the terminal.</h1>
|
||||
<p class="hero__lede">streampipe is a single-binary tool that runs SQL over Kafka, Redis Streams, NATS, and stdin. No daemon, no broker, no JVM. Composes with your existing UNIX pipeline.</p>
|
||||
<p class="hero__cta-label">Install</p>
|
||||
<pre class="code"><span class="prompt">$</span><span>brew install streampipe</span><span class="caret" aria-hidden="true">▮</span></pre>
|
||||
</section>
|
||||
|
||||
<section class="section" id="examples">
|
||||
<p class="section__num">01 · Examples</p>
|
||||
<h2 class="section__head">Three lines that read a Kafka topic and emit JSON.</h2>
|
||||
<pre class="code" style="display:block; padding: var(--space-md); white-space: pre;"><span class="prompt">$</span> streampipe \
|
||||
--source kafka://localhost:9092/orders \
|
||||
--sql "select customer, sum(total) as gross from orders group by customer" \
|
||||
--sink stdout:json</pre>
|
||||
</section>
|
||||
|
||||
<section class="section" id="docs">
|
||||
<p class="section__num">02 · Sources, sinks, formats</p>
|
||||
<h2 class="section__head">Reference</h2>
|
||||
<table class="spec">
|
||||
<thead>
|
||||
<tr><th style="width:18ch">Flag</th><th>Description</th><th style="width:24ch">Example</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td class="k">--source</td><td>Stream input. Accepts kafka://, redis://, nats://, file://, stdin.</td><td>kafka://broker:9092/topic</td></tr>
|
||||
<tr><td class="k">--sink</td><td>Stream output. Accepts the same protocols + stdout.</td><td>stdout:json</td></tr>
|
||||
<tr><td class="k">--sql</td><td>SQL transformation. Standard select/where/group-by/join.</td><td>"select * where amount > 100"</td></tr>
|
||||
<tr><td class="k">--watermark</td><td>Event-time watermark. Default: 30s.</td><td>--watermark 60s</td></tr>
|
||||
<tr><td class="k">--checkpoint</td><td>Path for state. Required for stateful queries.</td><td>./state.db</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
<footer class="foot-dense">
|
||||
<p>streampipe v2.4.0 · MIT licensed · built in Go · 12k stars on GitHub · zero runtime dependencies · macOS · Linux · Windows · single static binary · 8 MB · <a href="https://github.com/streampipe/streampipe">source</a> · <a href="/docs">docs</a> · <a href="/changelog">changelog</a> · maintained by 4 people · 137 contributors · last release 2026-04-12 · sha 3a94f · powered by goroutines and a shoestring budget</p>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,161 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Nimbus — atmospheric trading desk</title>
|
||||
<style>
|
||||
/* Hallmark · genre: atmospheric · macrostructure: Stat-Led
|
||||
* theme: Bloom · accent: amber · paper: dark-warm
|
||||
* nav: N5 Floating pill (blur backdrop sells the atmosphere)
|
||||
* footer: Ft5 Statement (closes with a sentence, not a sitemap)
|
||||
* polish: HP3 Cursor-spotlight (scoped to hero only, reduced-motion fallback)
|
||||
* slop: pass (51–55) · contrast: pass (46–50)
|
||||
* brief: Nimbus — crypto exchange dashboard. Atmospheric, stat-led.
|
||||
*/
|
||||
|
||||
@font-face { font-family: "Geist"; src: local("Geist"), local("Inter"); font-display: swap; }
|
||||
|
||||
:root {
|
||||
--color-paper: oklch(11% 0.022 35);
|
||||
--color-paper-2: oklch(15% 0.022 35);
|
||||
--color-paper-3: oklch(19% 0.022 35);
|
||||
--color-ink: oklch(96% 0.010 60);
|
||||
--color-ink-2: oklch(72% 0.012 50);
|
||||
--color-rule: oklch(28% 0.020 35);
|
||||
--color-accent: oklch(72% 0.190 45);
|
||||
--color-accent-ink: oklch(11% 0.022 35);
|
||||
--color-focus: oklch(78% 0.190 45);
|
||||
|
||||
--font-display: "Geist", "Inter", system-ui, sans-serif;
|
||||
--font-body: "Geist", "Inter", system-ui, sans-serif;
|
||||
|
||||
--space-2xs: 0.5rem; --space-xs: 0.75rem; --space-sm: 1rem;
|
||||
--space-md: 1.5rem; --space-lg: 2rem; --space-xl: 3rem;
|
||||
--space-2xl: 4.5rem; --space-3xl: 7rem;
|
||||
|
||||
--text-xs: 0.75rem; --text-sm: 0.875rem; --text-md: 1rem;
|
||||
--text-lg: 1.375rem; --text-xl: 1.75rem; --text-2xl: 2.25rem;
|
||||
|
||||
--rule-hair: 1px;
|
||||
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
|
||||
--dur-short: 220ms;
|
||||
--page-gutter: clamp(1rem, 4vw, 2.5rem);
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
html, body { margin: 0; }
|
||||
body { font-family: var(--font-body); background: var(--color-paper); color: var(--color-ink); font-size: var(--text-md); line-height: 1.55; font-feature-settings: "tnum"; }
|
||||
a { color: inherit; text-underline-offset: 2px; }
|
||||
a:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; }
|
||||
|
||||
/* N5 Floating pill */
|
||||
.nav-pill { position: fixed; inset: var(--space-md) auto auto 50%; transform: translateX(-50%); display: inline-flex; align-items: center; gap: var(--space-md); padding: 0.5rem 0.875rem; background: color-mix(in oklch, var(--color-paper) 65%, transparent); backdrop-filter: blur(14px) saturate(140%); -webkit-backdrop-filter: blur(14px) saturate(140%); border: var(--rule-hair) solid var(--color-rule); border-radius: 999px; box-shadow: 0 8px 24px -12px oklch(0% 0 0 / 0.40); z-index: 20; }
|
||||
.nav-pill .wordmark { font-weight: 700; font-size: var(--text-sm); letter-spacing: -0.01em; text-decoration: none; }
|
||||
.nav-pill ul { display: inline-flex; gap: var(--space-md); list-style: none; padding: 0; margin: 0; }
|
||||
.nav-pill a { font-size: var(--text-sm); color: var(--color-ink-2); text-decoration: none; }
|
||||
.nav-pill a:hover { color: var(--color-ink); }
|
||||
.cta-fill { padding: 0.4rem 0.875rem; border-radius: 999px; background: var(--color-accent); color: var(--color-accent-ink); font-size: var(--text-sm); font-weight: 600; text-decoration: none; }
|
||||
|
||||
/* HP3 Cursor-spotlight scoped to hero */
|
||||
.hero { position: relative; isolation: isolate; padding: var(--space-2xl) var(--page-gutter) var(--space-3xl); overflow: hidden; min-height: clamp(60vh, 75dvh, 88dvh); display: grid; align-content: center; }
|
||||
.hero__spotlight { position: absolute; inset: 0; z-index: -1; background: radial-gradient(560px circle at var(--mx, 50%) var(--my, 35%), color-mix(in oklch, var(--color-accent) 28%, transparent), transparent 65%); transition: background 200ms var(--ease-out); pointer-events: none; }
|
||||
.hero__copy { max-width: 64ch; }
|
||||
.hero__eyebrow { color: var(--color-accent); font-size: var(--text-sm); letter-spacing: 0.04em; margin: 0 0 var(--space-md); }
|
||||
.hero__stat { font-family: var(--font-display); font-size: clamp(4rem, 12vw, 9rem); line-height: 0.95; letter-spacing: -0.04em; font-weight: 700; font-feature-settings: "tnum"; margin: 0 0 var(--space-sm); }
|
||||
.hero__stat .unit { font-size: 0.45em; color: var(--color-ink-2); margin-left: 0.2em; }
|
||||
.hero__qualifier { font-size: var(--text-lg); max-width: 48ch; margin: 0 0 var(--space-md); }
|
||||
.hero__lede { color: var(--color-ink-2); max-width: 56ch; margin: 0 0 var(--space-lg); }
|
||||
.hero__link { color: var(--color-accent); text-decoration: underline; }
|
||||
|
||||
/* Stats row */
|
||||
.stats { padding: var(--space-2xl) var(--page-gutter); display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-2xl); border-top: var(--rule-hair) solid var(--color-rule); max-width: 90rem; margin: 0 auto; }
|
||||
.stat__num { font-size: clamp(2rem, 4vw, 3rem); letter-spacing: -0.02em; font-weight: 700; margin: 0; }
|
||||
.stat__label { color: var(--color-ink-2); font-size: var(--text-sm); margin: 0; }
|
||||
|
||||
/* Ft5 Statement footer */
|
||||
.foot-stmt { padding: var(--space-3xl) var(--page-gutter) var(--space-xl); display: grid; gap: var(--space-lg); max-width: 90rem; margin: 0 auto; }
|
||||
.foot-stmt__line { font-family: var(--font-display); font-size: clamp(2rem, 5vw, 3.5rem); line-height: 1.0; letter-spacing: -0.02em; max-width: 26ch; margin: 0; font-weight: 600; }
|
||||
.foot-stmt__meta { display: flex; justify-content: space-between; align-items: baseline; padding-block-start: var(--space-md); border-top: var(--rule-hair) solid var(--color-rule); color: var(--color-ink-2); font-size: var(--text-sm); }
|
||||
.foot-stmt__meta .wordmark { color: var(--color-ink); font-weight: 700; }
|
||||
|
||||
@media (max-width: 60rem) {
|
||||
.nav-pill ul { display: none; }
|
||||
.stats { grid-template-columns: 1fr 1fr; gap: var(--space-md); }
|
||||
.hero { padding-top: var(--space-2xl); }
|
||||
}
|
||||
@media (max-width: 40rem) {
|
||||
.stats { grid-template-columns: 1fr; }
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.hero__spotlight { transition: none; --mx: 50%; --my: 35%; }
|
||||
* { animation: none !important; transition: none !important; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav class="nav-pill" aria-label="Primary">
|
||||
<a class="wordmark" href="#top">Nimbus</a>
|
||||
<ul>
|
||||
<li><a href="#markets">Markets</a></li>
|
||||
<li><a href="#api">API</a></li>
|
||||
<li><a href="#fees">Fees</a></li>
|
||||
</ul>
|
||||
<a class="cta-fill" href="#trade">Trade →</a>
|
||||
</nav>
|
||||
|
||||
<main>
|
||||
|
||||
<header class="hero" id="top">
|
||||
<div class="hero__spotlight" aria-hidden="true"></div>
|
||||
<div class="hero__copy">
|
||||
<p class="hero__eyebrow">Spot · Margin · Perpetuals · Active in 84 markets</p>
|
||||
<span class="hero__stat">$2.4<span class="unit">B</span></span>
|
||||
<p class="hero__qualifier">notional volume cleared in the last 24 hours, with a single API and one balance.</p>
|
||||
<p class="hero__lede">Nimbus is a non-custodial trading desk for the calm trader. Open positions in spot, margin, and perpetuals from one account. Trades clear in 380 ms p99. We don't run leveraged retail ads.</p>
|
||||
<a class="hero__link" href="#markets">See live markets →</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="stats" id="markets">
|
||||
<div>
|
||||
<p class="stat__num">380<span style="color:var(--color-ink-2)">ms</span></p>
|
||||
<p class="stat__label">order-to-fill, 99th percentile, last 7 days</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="stat__num">84</p>
|
||||
<p class="stat__label">spot + perpetual markets, audited monthly</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="stat__num">0.02<span style="color:var(--color-ink-2)">%</span></p>
|
||||
<p class="stat__label">maker fee on the deepest tier, paid by inventory</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
<footer class="foot-stmt">
|
||||
<p class="foot-stmt__line">Calm markets. Honest fees. No retail ads.</p>
|
||||
<div class="foot-stmt__meta">
|
||||
<span class="wordmark">Nimbus</span>
|
||||
<span>© 2026 · Audited by ChainSec · Reg-D</span>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
// HP3 Cursor-spotlight — scope to hero only, never page-wide
|
||||
(function () {
|
||||
var hero = document.querySelector('.hero');
|
||||
if (!hero) return;
|
||||
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
|
||||
hero.addEventListener('pointermove', function (e) {
|
||||
var r = hero.getBoundingClientRect();
|
||||
hero.style.setProperty('--mx', (e.clientX - r.left) + 'px');
|
||||
hero.style.setProperty('--my', (e.clientY - r.top) + 'px');
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,143 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Marlow & Co. — wedding photography</title>
|
||||
<style>
|
||||
/* Hallmark · genre: editorial · macrostructure: Photographic
|
||||
* theme: Atelier · accent: warm cinnamon · paper: warm-cream
|
||||
* nav: N9 Edge-aligned minimal (the absence is the design)
|
||||
* footer: Ft7 Newsletter-first (the studio publishes a quarterly letter — the page mentioned it above the fold)
|
||||
* polish: HP1 Vertical-rail (studio name + plate numerals run vertically)
|
||||
* slop: pass (51–55) · contrast: pass (46–50)
|
||||
* brief: Marlow & Co. — wedding photographer portfolio. Salon, restrained.
|
||||
*/
|
||||
|
||||
@font-face { font-family: "FraunSerif"; src: local("Fraunces"), local("Source Serif 4"); font-display: swap; }
|
||||
|
||||
:root {
|
||||
--color-paper: oklch(95% 0.012 70);
|
||||
--color-paper-2: oklch(92% 0.014 65);
|
||||
--color-ink: oklch(22% 0.020 50);
|
||||
--color-ink-2: oklch(45% 0.020 55);
|
||||
--color-rule: oklch(78% 0.014 60);
|
||||
--color-accent: oklch(48% 0.130 50);
|
||||
--color-accent-ink: oklch(98% 0.005 70);
|
||||
--color-focus: oklch(58% 0.130 50);
|
||||
|
||||
--font-display: "FraunSerif", "Fraunces", "Source Serif 4", Georgia, serif;
|
||||
--font-body: "FraunSerif", "Fraunces", "Source Serif 4", Georgia, serif;
|
||||
|
||||
--space-2xs: 0.5rem; --space-xs: 0.75rem; --space-sm: 1rem;
|
||||
--space-md: 1.5rem; --space-lg: 2rem; --space-xl: 3rem;
|
||||
--space-2xl: 4.5rem; --space-3xl: 7rem;
|
||||
|
||||
--text-xs: 0.75rem; --text-sm: 0.875rem; --text-md: 1.125rem;
|
||||
--text-lg: 1.375rem; --text-xl: 1.75rem; --text-2xl: 2.25rem;
|
||||
|
||||
--rule-hair: 0.5px;
|
||||
--page-gutter: clamp(1rem, 4vw, 2.5rem);
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
html, body { margin: 0; }
|
||||
body { font-family: var(--font-body); background: var(--color-paper); color: var(--color-ink); font-size: var(--text-md); line-height: 1.55; font-feature-settings: "onum"; }
|
||||
a { color: inherit; text-underline-offset: 3px; }
|
||||
a:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; }
|
||||
|
||||
/* N9 Edge-aligned minimal nav */
|
||||
.nav-edge { display: flex; justify-content: space-between; align-items: center; padding: var(--space-md) var(--page-gutter); }
|
||||
.nav-edge .wordmark { font-family: var(--font-display); font-style: italic; font-size: var(--text-md); letter-spacing: -0.005em; text-decoration: none; }
|
||||
.cta-outline { padding: 0.45rem 0.95rem; border: 1px solid var(--color-ink); color: var(--color-ink); text-decoration: none; font-size: var(--text-sm); border-radius: 999px; min-height: 44px; display: inline-flex; align-items: center; }
|
||||
|
||||
/* HP1 Vertical-rail title — the studio name runs vertically alongside the photo */
|
||||
.hero { display: grid; grid-template-columns: auto 1fr; gap: var(--space-2xl); padding: var(--space-2xl) var(--page-gutter) var(--space-3xl); align-items: end; }
|
||||
.hero__rail { writing-mode: vertical-rl; text-orientation: mixed; font-family: var(--font-display); font-size: var(--text-sm); letter-spacing: 0.18em; color: var(--color-ink-2); margin: 0; padding-block-end: var(--space-md); }
|
||||
.hero__body { display: grid; gap: var(--space-md); max-width: 60ch; }
|
||||
.hero__eyebrow { font-variant: small-caps; letter-spacing: 0.12em; font-size: var(--text-xs); color: var(--color-ink-2); margin: 0; }
|
||||
.hero__display { font-family: var(--font-display); font-style: italic; font-weight: 500; font-size: clamp(2rem, 5vw, 3.25rem); line-height: 1.05; letter-spacing: -0.01em; margin: 0; }
|
||||
.hero__lede { color: var(--color-ink); font-size: var(--text-md); margin: 0; max-width: 50ch; }
|
||||
|
||||
/* Photographic centerpiece — H6 archetype */
|
||||
.frame { padding: 0 var(--page-gutter) var(--space-2xl); }
|
||||
.frame__photo { width: 100%; aspect-ratio: 16/9; background: linear-gradient(135deg, color-mix(in oklch, var(--color-paper-2) 100%, var(--color-accent) 6%), color-mix(in oklch, var(--color-paper) 100%, var(--color-ink) 8%)); border: var(--rule-hair) solid var(--color-rule); position: relative; overflow: hidden; }
|
||||
.frame__photo::after { content: ""; position: absolute; inset: 0; background: radial-gradient(60% 40% at 30% 60%, oklch(40% 0.03 50 / 0.25), transparent 70%), radial-gradient(40% 30% at 75% 35%, oklch(70% 0.05 60 / 0.30), transparent 70%); }
|
||||
.frame__caption { display: flex; justify-content: space-between; padding-block-start: var(--space-2xs); font-size: var(--text-xs); font-variant: small-caps; letter-spacing: 0.10em; color: var(--color-ink-2); }
|
||||
|
||||
/* Body — selected work */
|
||||
.work { padding: var(--space-2xl) var(--page-gutter); border-top: var(--rule-hair) solid var(--color-rule); max-width: 64rem; margin: 0 auto; }
|
||||
.work__head { font-family: var(--font-display); font-style: italic; font-size: var(--text-xl); margin: 0 0 var(--space-lg); }
|
||||
.work__list { list-style: none; padding: 0; margin: 0; display: grid; gap: var(--space-md); }
|
||||
.work__item { display: grid; grid-template-columns: 4ch 1fr auto; gap: var(--space-md); align-items: baseline; padding-block: var(--space-2xs); border-bottom: var(--rule-hair) solid var(--color-rule); }
|
||||
.work__num { font-variant: small-caps; letter-spacing: 0.12em; font-size: var(--text-xs); color: var(--color-ink-2); }
|
||||
.work__title { font-style: italic; }
|
||||
.work__date { color: var(--color-ink-2); font-size: var(--text-sm); }
|
||||
|
||||
/* Ft7 Newsletter-first */
|
||||
.foot-news { padding: var(--space-2xl) var(--page-gutter); display: grid; gap: var(--space-lg); max-width: 56ch; margin: 0 auto; border-top: var(--rule-hair) solid var(--color-rule); }
|
||||
.foot-news__form label { display: block; font-family: var(--font-display); font-style: italic; font-size: var(--text-lg); margin-block-end: var(--space-sm); }
|
||||
.foot-news__row { display: flex; gap: var(--space-2xs); }
|
||||
.foot-news__row input { flex: 1; min-height: 44px; padding-inline: var(--space-sm); border: 1px solid var(--color-rule); background: var(--color-paper); font: inherit; color: inherit; border-radius: 0; }
|
||||
.foot-news__row input:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 1px; border-color: transparent; }
|
||||
.foot-news__row button { min-height: 44px; padding-inline: var(--space-md); border: 0; background: var(--color-ink); color: var(--color-paper); font: inherit; font-weight: 600; cursor: pointer; border-radius: 0; }
|
||||
.foot-news__meta { font-size: var(--text-xs); color: var(--color-ink-2); margin: 0; }
|
||||
|
||||
@media (max-width: 60rem) {
|
||||
.hero { grid-template-columns: 1fr; }
|
||||
.hero__rail { writing-mode: horizontal-tb; font-size: var(--text-xs); padding-block-end: 0; }
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) { * { animation: none !important; transition: none !important; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header class="nav-edge">
|
||||
<a class="wordmark" href="#top">Marlow & Co.</a>
|
||||
<a class="cta-outline" href="#enquire">Enquire →</a>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
|
||||
<header class="hero" id="top">
|
||||
<p class="hero__rail" aria-hidden="true">MARLOW · PLATE 04 · 2026</p>
|
||||
<div class="hero__body">
|
||||
<p class="hero__eyebrow">Selected work · 2026 · Plate 04</p>
|
||||
<h1 class="hero__display">Photographs that look like the day actually was.</h1>
|
||||
<p class="hero__lede">A small studio for weddings, family portraits, and the quiet days after. Twelve commissions a year, photographed on film, scanned, printed, archived. We're booked into autumn 2026; the spring 2027 list opens in October.</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<figure class="frame">
|
||||
<div class="frame__photo" aria-label="A bride and groom in profile, late afternoon, garden ceremony"></div>
|
||||
<figcaption class="frame__caption">
|
||||
<span>Plate 04 · Anna & Sam · 12 April 2026</span>
|
||||
<span>Hasselblad 500CM · Portra 400 · Garden ceremony</span>
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
<section class="work">
|
||||
<h2 class="work__head">A few of last year's commissions.</h2>
|
||||
<ol class="work__list">
|
||||
<li class="work__item"><span class="work__num">01</span><span class="work__title">Anna & Sam — garden ceremony, Tuscany</span><span class="work__date">April 2026</span></li>
|
||||
<li class="work__item"><span class="work__num">02</span><span class="work__title">The Okonkwo family — winter portraits</span><span class="work__date">February 2026</span></li>
|
||||
<li class="work__item"><span class="work__num">03</span><span class="work__title">Maya & Jonas — small civil ceremony, Lisbon</span><span class="work__date">November 2025</span></li>
|
||||
<li class="work__item"><span class="work__num">04</span><span class="work__title">Rivers — first-year archive</span><span class="work__date">Ongoing</span></li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
<footer class="foot-news">
|
||||
<form class="foot-news__form" action="/subscribe" method="post">
|
||||
<label for="m-email">A quarterly letter from the studio. Plates, prints, and what we're learning.</label>
|
||||
<div class="foot-news__row">
|
||||
<input id="m-email" name="email" type="email" required placeholder="you@domain" autocomplete="email">
|
||||
<button type="submit">Subscribe</button>
|
||||
</div>
|
||||
</form>
|
||||
<p class="foot-news__meta">Marlow & Co. · 12 Marlow Street · © 2026 · No tracking, no third-party email lists.</p>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,170 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Helix — feature flags for grown-up teams</title>
|
||||
<style>
|
||||
/* Hallmark · genre: modern-minimal · macrostructure: Bento Grid
|
||||
* theme: Specimen → modern-minimal flavour · accent: indigo · paper: cool-near-white
|
||||
* nav: N7 Brutal slab (because the brand voice is loud-and-confident — playful sub-cluster)
|
||||
* ↑ chosen as a *deliberate* deviation from the modern-minimal default (N5)
|
||||
* to demonstrate that variety wins over routing-defaults when the brief justifies it.
|
||||
* Compare against the four other tests: each picks a different nav archetype.
|
||||
* footer: Ft1 Mast-headed
|
||||
* polish: HP2 Marquee-overflow (declarative title bleeds past the viewport edge)
|
||||
* slop: pass (51–55) · contrast: pass (46–50)
|
||||
* brief: Helix — B2B SaaS marketing landing. Trust SaaS but not generic.
|
||||
*/
|
||||
|
||||
@font-face { font-family: "Geist"; src: local("Geist"), local("Inter"); font-display: swap; }
|
||||
|
||||
:root {
|
||||
--color-paper: oklch(98% 0.005 250);
|
||||
--color-paper-2: oklch(95% 0.008 250);
|
||||
--color-paper-3: oklch(92% 0.010 250);
|
||||
--color-ink: oklch(15% 0.012 270);
|
||||
--color-ink-2: oklch(40% 0.012 270);
|
||||
--color-rule: oklch(82% 0.010 260);
|
||||
--color-accent: oklch(48% 0.180 270);
|
||||
--color-accent-ink: oklch(98% 0.005 250);
|
||||
--color-focus: oklch(58% 0.180 270);
|
||||
|
||||
--font-display: "Geist", "Inter", system-ui, sans-serif;
|
||||
--font-body: "Geist", "Inter", system-ui, sans-serif;
|
||||
|
||||
--space-2xs: 0.5rem; --space-xs: 0.75rem; --space-sm: 1rem;
|
||||
--space-md: 1.5rem; --space-lg: 2rem; --space-xl: 3rem;
|
||||
--space-2xl: 4.5rem; --space-3xl: 7rem;
|
||||
|
||||
--text-xs: 0.75rem; --text-sm: 0.875rem; --text-md: 1rem;
|
||||
--text-lg: 1.375rem; --text-xl: 1.75rem; --text-2xl: 2.25rem;
|
||||
|
||||
--rule-hair: 1px;
|
||||
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
|
||||
--dur-short: 220ms;
|
||||
--page-gutter: clamp(1rem, 4vw, 2.5rem);
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
html, body { margin: 0; }
|
||||
body { font-family: var(--font-body); background: var(--color-paper); color: var(--color-ink); font-size: var(--text-md); line-height: 1.55; }
|
||||
a { color: inherit; text-underline-offset: 2px; }
|
||||
a:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; }
|
||||
|
||||
/* N7 Brutal slab nav */
|
||||
.nav-slab { display: flex; align-items: center; gap: var(--space-md); padding: var(--space-sm) var(--page-gutter); border-bottom: 2px solid var(--color-ink); background: var(--color-paper); position: sticky; top: 0; z-index: 10; }
|
||||
.slab-mark { font-family: var(--font-display); font-weight: 800; letter-spacing: 0.04em; text-decoration: none; }
|
||||
.slab-nav { margin-left: auto; }
|
||||
.slab-nav ul { display: flex; gap: var(--space-md); list-style: none; padding: 0; margin: 0; }
|
||||
.slab-nav a { text-transform: uppercase; letter-spacing: 0.08em; font-size: var(--text-sm); font-weight: 600; text-decoration: none; }
|
||||
.slab-nav a:hover { color: var(--color-accent); }
|
||||
.cta-fill-slab { padding: 0.5rem 0.95rem; background: var(--color-ink); color: var(--color-paper); font-weight: 700; font-size: var(--text-sm); text-decoration: none; min-height: 44px; display: inline-flex; align-items: center; }
|
||||
|
||||
/* Hero — Bento Grid + HP2 Marquee-overflow */
|
||||
.hero { overflow-x: clip; padding: var(--space-2xl) var(--page-gutter) var(--space-3xl); }
|
||||
.hero__display { font-family: var(--font-display); font-weight: 800; font-size: clamp(3rem, 11vw, 9.5rem); line-height: 0.92; letter-spacing: -0.04em; margin: 0 0 var(--space-md); white-space: nowrap; }
|
||||
.hero__lede { font-size: var(--text-lg); max-width: 64ch; margin: 0 0 var(--space-lg); color: var(--color-ink-2); }
|
||||
.hero__cta-row { display: flex; gap: var(--space-sm); flex-wrap: wrap; }
|
||||
.cta-fill { padding: 0.7rem 1.2rem; background: var(--color-ink); color: var(--color-paper); font-weight: 600; text-decoration: none; min-height: 44px; display: inline-flex; align-items: center; }
|
||||
.cta-link { padding: 0.7rem 1.2rem; color: var(--color-ink); text-decoration: underline; min-height: 44px; display: inline-flex; align-items: center; font-weight: 500; }
|
||||
|
||||
/* Bento grid */
|
||||
.bento { padding: var(--space-2xl) var(--page-gutter); display: grid; grid-template-columns: repeat(6, 1fr); grid-auto-rows: minmax(160px, auto); gap: var(--space-md); max-width: 90rem; margin: 0 auto; }
|
||||
.tile { padding: var(--space-md); background: var(--color-paper-2); border: var(--rule-hair) solid var(--color-rule); display: grid; gap: var(--space-2xs); align-content: start; }
|
||||
.tile h3 { font-size: var(--text-md); margin: 0; font-weight: 600; }
|
||||
.tile p { font-size: var(--text-sm); color: var(--color-ink-2); margin: 0; }
|
||||
.tile--stat { grid-column: span 2; grid-row: span 2; background: var(--color-ink); color: var(--color-paper); }
|
||||
.tile--stat .num { font-size: clamp(3rem, 6vw, 5rem); line-height: 1; font-weight: 800; letter-spacing: -0.03em; font-feature-settings: "tnum"; }
|
||||
.tile--stat .label { color: oklch(78% 0.010 250); font-size: var(--text-sm); }
|
||||
.tile--code { grid-column: span 4; grid-row: span 2; background: var(--color-paper); }
|
||||
.tile--code pre { background: var(--color-ink); color: var(--color-paper); padding: var(--space-md); border-radius: 0; font-size: var(--text-sm); overflow-x: auto; margin: var(--space-2xs) 0 0; font-family: ui-monospace, "JetBrains Mono", monospace; }
|
||||
.tile--feature { grid-column: span 2; }
|
||||
.tile--quote { grid-column: span 4; }
|
||||
.tile--quote blockquote { font-family: var(--font-display); font-size: var(--text-lg); font-style: italic; margin: 0 0 var(--space-2xs); }
|
||||
.tile--quote cite { font-size: var(--text-sm); color: var(--color-ink-2); font-style: normal; }
|
||||
.accent { color: var(--color-accent); }
|
||||
|
||||
/* Ft1 Mast-headed footer */
|
||||
.foot-mast { padding: var(--space-2xl) var(--page-gutter); border-top: 2px solid var(--color-ink); display: grid; gap: var(--space-sm); }
|
||||
.foot-mast__wordmark { font-family: var(--font-display); font-weight: 800; font-size: var(--text-2xl); letter-spacing: -0.01em; margin: 0; }
|
||||
.foot-mast__tag { font-style: italic; color: var(--color-ink-2); margin: 0; max-width: 50ch; }
|
||||
.foot-mast__links { display: flex; gap: var(--space-md); flex-wrap: wrap; padding-block-start: var(--space-md); border-top: var(--rule-hair) solid var(--color-rule); font-size: var(--text-sm); color: var(--color-ink-2); }
|
||||
|
||||
@media (max-width: 60rem) {
|
||||
.hero__display { white-space: normal; font-size: clamp(2.25rem, 9vw, 4.5rem); }
|
||||
.bento { grid-template-columns: repeat(2, 1fr); }
|
||||
.tile--stat, .tile--code, .tile--feature, .tile--quote { grid-column: span 2; grid-row: auto; }
|
||||
.slab-nav { display: none; }
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) { * { animation: none !important; transition: none !important; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header class="nav-slab">
|
||||
<a class="slab-mark" href="#top">HELIX</a>
|
||||
<nav class="slab-nav" aria-label="Primary">
|
||||
<ul>
|
||||
<li><a href="#features">FEATURES</a></li>
|
||||
<li><a href="#pricing">PRICING</a></li>
|
||||
<li><a href="#docs">DOCS</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<a class="cta-fill-slab" href="#start">START FREE</a>
|
||||
</header>
|
||||
|
||||
<main id="top">
|
||||
|
||||
<section class="hero">
|
||||
<h1 class="hero__display">FEATURE FLAGS, GROWN UP.</h1>
|
||||
<p class="hero__lede">Helix is a feature-flag service for teams who want fewer surprises. Type-safe SDKs, audit trails, deterministic targeting. Used by 1,200 engineering teams to ship without holding their breath.</p>
|
||||
<div class="hero__cta-row">
|
||||
<a class="cta-fill" href="#start">Start free →</a>
|
||||
<a class="cta-link" href="#book">Book a 20-min demo</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="bento" id="features">
|
||||
<article class="tile tile--stat">
|
||||
<span class="num">99.97<span style="opacity:.6;font-size:.5em">%</span></span>
|
||||
<span class="label">SDK uptime · last 90 days · measured externally</span>
|
||||
</article>
|
||||
<article class="tile tile--code">
|
||||
<h3>Type-safe SDK · TypeScript, Go, Python, Ruby</h3>
|
||||
<p>Every flag is a typed export. No magic strings.</p>
|
||||
<pre>// flags.ts — generated from your dashboard
|
||||
import { flags } from "@helix/sdk";
|
||||
if (flags.checkout_redesign(user)) {
|
||||
return <CheckoutV2 />;
|
||||
}</pre>
|
||||
</article>
|
||||
<article class="tile tile--feature">
|
||||
<h3>Deterministic targeting</h3>
|
||||
<p>Same input, same flag value — every time. Replayable, auditable, no drift.</p>
|
||||
</article>
|
||||
<article class="tile tile--feature">
|
||||
<h3>Audit trail</h3>
|
||||
<p>Every flag change writes a row. Who, what, when, why — and the diff.</p>
|
||||
</article>
|
||||
<article class="tile tile--quote">
|
||||
<blockquote>"We replaced four years of <span class="accent">if-statements</span> with Helix in a week. The audit trail alone paid for it."</blockquote>
|
||||
<cite>— Maya Okonkwo · Staff Engineer, Ridgeline</cite>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
<footer class="foot-mast">
|
||||
<p class="foot-mast__wordmark">Helix</p>
|
||||
<p class="foot-mast__tag">Feature flags for teams that ship on Friday afternoons.</p>
|
||||
<p class="foot-mast__links">
|
||||
<a href="#docs">Docs</a> ·
|
||||
<a href="#pricing">Pricing</a> ·
|
||||
<a href="#imprint">Imprint</a> ·
|
||||
<span>© 2026 · SOC 2 type II</span>
|
||||
</p>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,44 +0,0 @@
|
||||
# Test pass — nav · footer · hero polish variety
|
||||
|
||||
Five outputs, each generated by following the updated skill (`SKILL.md` flow + `component-cookbook.md` § Navigation / § Footers + `hero-enrichment.md` § Hero shape polish). Each tests a different combination of the new archetypes.
|
||||
|
||||
## Variety matrix
|
||||
|
||||
| # | Brief | Genre | Macrostructure | Nav | Footer | Hero polish | Theme |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| 01 | Tide — indie podcast | editorial | Letter | **N6** Newspaper masthead | **Ft6** Letter close | **HP4** Decorative-numeral (issue 04) | Salon |
|
||||
| 02 | streampipe — CLI | modern-minimal/terminal | Workbench | **N8** Terminal command | **Ft4** Dense colophon | none (terminal allows no decoration) | Terminal |
|
||||
| 03 | Nimbus — atmospheric SaaS | atmospheric | Stat-Led | **N5** Floating pill | **Ft5** Statement | **HP3** Cursor-spotlight | Bloom |
|
||||
| 04 | Marlow & Co. — photography | editorial | Photographic | **N9** Edge-aligned minimal | **Ft7** Newsletter-first | **HP1** Vertical-rail | Atelier |
|
||||
| 05 | Helix — B2B SaaS | modern-minimal | Bento Grid | **N7** Brutal slab | **Ft1** Mast-headed | **HP2** Marquee-overflow | Specimen-leaning |
|
||||
|
||||
## Verification — does the skill produce variety?
|
||||
|
||||
- [x] **Five different nav archetypes.** N5, N6, N7, N8, N9 — one each. No repeats.
|
||||
- [x] **Five different footer archetypes.** Ft1, Ft4, Ft5, Ft6, Ft7 — one each. No repeats.
|
||||
- [x] **Four different hero polish patterns.** HP1, HP2, HP3, HP4 — one each on the polish-enabled outputs. The Terminal output (02) deliberately ships *no* polish pattern, per the genre's decoration constraint.
|
||||
- [x] **Gate 51 (AI nav fingerprint)** — none of the five outputs use the wordmark-left + 5-link-row + button-right pattern. Pass.
|
||||
- [x] **Gate 52 (AI footer fingerprint)** — none use the 4-column + social-row + tiny-copyright pattern. Pass.
|
||||
- [x] **Gate 53 (centred-everything hero)** — none stack eyebrow + title + lede + CTA all centred. Pass.
|
||||
- [x] **Gate 54 (hero padding asymmetry)** — every hero pads bottom > top. Pass.
|
||||
- [x] **Gate 55 (decorative-without-purpose)** — every decorative element has a semantic anchor: 01's "04" names issue 4, 02's cursor sits inside a typed command, 03's spotlight responds to interaction, 04's vertical rail names the studio + plate + year, 05 has no decoration. Pass.
|
||||
- [x] **Terminal cursor constraint** (02) — the blinking caret appears only inside `<pre>` install code blocks and the N8 nav prompt — never as standalone decoration. Pass.
|
||||
|
||||
## Routing reads correctly
|
||||
|
||||
Cross-checked each pick against the routing tables in `component-cookbook.md`:
|
||||
|
||||
- **01 (editorial · Salon):** N6 is the genre default; Ft6 is in the "also OK" set. ✓
|
||||
- **02 (terminal · Terminal):** N8 is the genre default; Ft4 is the genre default. ✓
|
||||
- **03 (atmospheric · Bloom):** N5 is the genre default; Ft5 is the genre default. ✓
|
||||
- **04 (editorial · Atelier):** N9 is in the "also OK" set; Ft7 is in the "also OK" set. ✓ (Both are deliberate alternates rather than defaults — the brief leans atelier-quiet, which the routing allows.)
|
||||
- **05 (modern-minimal · Specimen-leaning):** N7 is *not* the modern-minimal default — it's a deliberate cross-genre pick. The N7 voice fits Helix's "FEATURE FLAGS, GROWN UP." brand voice better than the default N5. The skill allows this when justified; the rationale is in the stamp comment.
|
||||
|
||||
The five outputs together demonstrate that:
|
||||
- Each genre's default routing is honoured when the brief is genre-pure.
|
||||
- Cross-genre picks are allowed when the brief justifies them, with the rationale stamped in the CSS comment.
|
||||
- Diversification across consecutive runs is preserved — N# and Ft# never repeat.
|
||||
|
||||
## Stamps recorded
|
||||
|
||||
Every output's CSS opens with a `/* Hallmark · ... */` comment that names: genre, macrostructure, theme, nav, footer, polish (or "none"), slop-test pass, contrast pass, and a one-line brief summary. The stamps are how future Hallmark runs will read each project's history and rotate.
|
||||