mirror of
https://github.com/Nutlope/hallmark.git
synced 2026-08-14 12:35:33 +02:00
v0.7.0 — add custom theme route, clean landing footer
The big shift: a custom theme route alongside the 16-theme catalog. Catalog stays the default. Custom fires only when the brief signals creative or unique intent (named brand colour, multi-attribute vibe the catalog can't carry, explicit ask). Vanilla briefs route to catalog silently — no visible behaviour change. Custom builds an OKLCH palette + free-font pairing tuned to the brief, inline at :root, with the same 38 slop-test gates firing as catalog runs. The freedom is the combination, not the rules. - New `skill/references/custom-theme.md` — protocol with sections A-G: trigger signals, palette construction, font pairing, axis computation, stamp format, log.json shape, three worked examples (Coffeebox, Loop, Mossroot). - New `site/_tests/custom/` — three rendered worked examples matching custom-theme.md § G. - `SKILL.md` — Step 1 trigger detection, Step 2.6 dispatch, sample preview block for custom, version 0.6.0 → 0.7.0. - `study.md` — theme-canon references → theme-catalog (font canon references intentionally left unchanged). - Landing footer — drop competitor links (impeccable, kami, frontend-design); keep GitHub + a quick description. Footer Influences column removed; stats refreshed (16 themes, 38 gates). - Site banner v0.2 → v0.7. README adds a current-version line. - Dense colophon JS — drop influences block, refresh stats and build date. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
6f1137b7dd
commit
a8518c63fc
@@ -3,6 +3,7 @@
|
||||
**A design skill that studies what you admire — and rebuilds your content with it.**
|
||||
|
||||
→ Live demo: **[hallmark-murex.vercel.app](https://hallmark-murex.vercel.app)** · sixteen themes, eight worked tests, press `T` to cycle.
|
||||
→ Current version: **v0.7.0** — adds the `custom` theme route (catalog stays the default; custom fires only when the brief signals it).
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Coffeebox — small-batch coffee, by post</title>
|
||||
<meta name="description" content="Roast Sunday, ship Monday, drink Tuesday. A small-batch coffee subscription for people who already buy good coffee." />
|
||||
<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,400;0,9..144,500;1,9..144,400&family=Source+Serif+4:ital,wght@0,400;0,500;1,400&family=JetBrains+Mono:wght@400;500&display=swap" />
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<main class="page">
|
||||
<header class="masthead">
|
||||
<h1 class="masthead__brand">Coffeebox<span class="dot">,</span></h1>
|
||||
<span class="masthead__meta">est. 2026 · n.º 12 · Lisbon</span>
|
||||
</header>
|
||||
|
||||
<h2 class="salutation">
|
||||
<span class="salutation__time">Sunday — 6:14 a.m.</span>
|
||||
The first roast of the week leaves the drum.
|
||||
</h2>
|
||||
|
||||
<article class="letter">
|
||||
<p>
|
||||
The bag goes in the post on Monday morning. By Tuesday night the kitchen
|
||||
smells like a café — beans rested forty-eight hours, ground at home, brewed how
|
||||
you brew. The point of <em>Coffeebox</em> isn't more coffee. It's a smaller
|
||||
kind of attention: one roaster, one small-farm origin per month, one flavour
|
||||
note worth waiting a week for.
|
||||
</p>
|
||||
<p>
|
||||
Subscribe and a paper bag arrives on Tuesdays. Skip a week from the email,
|
||||
change the roast from the email, pause the whole subscription from the
|
||||
email. We answer the email ourselves; there is no support portal.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<section class="notes">
|
||||
<h3 class="notes__head">§ Three things, then we'll go</h3>
|
||||
<div class="note">
|
||||
<span class="note__num">01</span>
|
||||
<p class="note__body">
|
||||
<strong>One roast a month.</strong> A single small-farm origin, single roast
|
||||
level, single bag size. We don't run a menu — we run a calendar.
|
||||
</p>
|
||||
</div>
|
||||
<div class="note">
|
||||
<span class="note__num">02</span>
|
||||
<p class="note__body">
|
||||
<strong>By post, not by app.</strong> No tracking, no rating, no streak. The
|
||||
beans show up. The email tells you what they are.
|
||||
</p>
|
||||
</div>
|
||||
<div class="note">
|
||||
<span class="note__num">03</span>
|
||||
<p class="note__body">
|
||||
<strong>The roaster reads the replies.</strong> Twelve people work here. We
|
||||
don't have a customer-success team because a small bakery doesn't need one.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<p class="signoff">
|
||||
— <span class="signoff__name">A. Coelho</span>, head roaster
|
||||
</p>
|
||||
|
||||
<footer class="colophon">
|
||||
<span>Coffeebox · Rua dos Anjos 92, Lisbon</span>
|
||||
<span><a href="mailto:hello@coffeebox.coffee">hello@coffeebox.coffee</a></span>
|
||||
<span><a href="#">subscribe</a></span>
|
||||
<span><a href="#">read the email</a></span>
|
||||
</footer>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,238 @@
|
||||
/* Hallmark · macrostructure: Long Document · H5 hero knobs: salutation=time-stamp, body=2 paragraphs, signoff=initials
|
||||
* theme: custom · vibe: "archival warmth, hand-set, no varnish" · paper: oklch(94% 0.020 65) · accent: oklch(58% 0.16 35)
|
||||
* display: Fraunces italic · body: Source Serif 4 · axes: light / italic-serif / chromatic-terracotta
|
||||
* studied: no · context: explicit · v0.7.0
|
||||
*/
|
||||
|
||||
:root {
|
||||
/* Custom palette — derived per brand vibe + anchor (terracotta) */
|
||||
--color-paper: oklch(94% 0.020 65);
|
||||
--color-paper-2: oklch(91% 0.022 65);
|
||||
--color-paper-3: oklch(88% 0.024 65);
|
||||
--color-rule: oklch(78% 0.018 65);
|
||||
--color-rule-2: oklch(82% 0.018 65);
|
||||
--color-muted: oklch(54% 0.014 60);
|
||||
--color-neutral: oklch(42% 0.014 60);
|
||||
--color-ink: oklch(22% 0.014 60);
|
||||
--color-ink-2: oklch(40% 0.014 60);
|
||||
--color-accent: oklch(58% 0.16 35);
|
||||
--color-accent-ink: oklch(96% 0.014 65);
|
||||
--color-focus: oklch(56% 0.20 35);
|
||||
|
||||
/* Pair: Editorial display + Editorial body — custom retains the catalog's free pairing */
|
||||
--font-display: "Fraunces", "EB Garamond", Georgia, serif;
|
||||
--font-body: "Source Serif 4", "Iowan Old Style", Georgia, serif;
|
||||
--font-mono: "JetBrains Mono", "Geist Mono", ui-monospace, monospace;
|
||||
|
||||
/* Canonical 4-pt scale (reused) */
|
||||
--space-3xs: 0.25rem;
|
||||
--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: 6.5rem;
|
||||
--space-4xl: 9rem;
|
||||
|
||||
--measure-prose: 60ch;
|
||||
--ease-out: cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
--dur-micro: 140ms;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
html, body { overflow-x: clip; }
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background: var(--color-paper);
|
||||
color: var(--color-ink);
|
||||
font-family: var(--font-body);
|
||||
font-size: 1.0625rem;
|
||||
line-height: 1.55;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.page {
|
||||
max-width: 56rem;
|
||||
margin: 0 auto;
|
||||
padding: var(--space-2xl) var(--space-lg) var(--space-3xl);
|
||||
}
|
||||
|
||||
/* Masthead — italic Fraunces wordmark */
|
||||
.masthead {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
border-block-end: 1px solid var(--color-rule);
|
||||
padding-block-end: var(--space-sm);
|
||||
margin-block-end: var(--space-3xl);
|
||||
}
|
||||
|
||||
.masthead__brand {
|
||||
font-family: var(--font-display);
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-variation-settings: "opsz" 144;
|
||||
font-size: 1.75rem;
|
||||
letter-spacing: -0.015em;
|
||||
margin: 0;
|
||||
color: var(--color-ink);
|
||||
}
|
||||
|
||||
.masthead__brand .dot { color: var(--color-accent); }
|
||||
|
||||
.masthead__meta {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-muted);
|
||||
}
|
||||
|
||||
/* Salutation — H5 time-stamp */
|
||||
.salutation {
|
||||
font-family: var(--font-display);
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-variation-settings: "opsz" 144;
|
||||
font-size: clamp(2rem, 4.5vw, 3.5rem);
|
||||
line-height: 1;
|
||||
letter-spacing: -0.02em;
|
||||
margin: 0 0 var(--space-xl);
|
||||
color: var(--color-ink);
|
||||
}
|
||||
|
||||
.salutation__time {
|
||||
display: block;
|
||||
font-family: var(--font-mono);
|
||||
font-style: normal;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 400;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-accent);
|
||||
margin-block-end: var(--space-sm);
|
||||
}
|
||||
|
||||
/* Letter body */
|
||||
.letter {
|
||||
max-width: var(--measure-prose);
|
||||
font-family: var(--font-body);
|
||||
font-size: 1.1875rem;
|
||||
line-height: 1.6;
|
||||
color: var(--color-ink);
|
||||
}
|
||||
|
||||
.letter p {
|
||||
margin: 0 0 var(--space-lg);
|
||||
}
|
||||
|
||||
.letter p:first-of-type::first-letter {
|
||||
font-family: var(--font-display);
|
||||
font-style: italic;
|
||||
font-size: 4.5rem;
|
||||
line-height: 0.85;
|
||||
float: inline-start;
|
||||
padding-inline-end: 0.18em;
|
||||
padding-block-start: 0.14em;
|
||||
color: var(--color-accent);
|
||||
font-variation-settings: "opsz" 144;
|
||||
}
|
||||
|
||||
.letter em { font-style: italic; color: var(--color-ink); }
|
||||
|
||||
/* Three notes */
|
||||
.notes {
|
||||
margin-block-start: var(--space-3xl);
|
||||
padding-block-start: var(--space-md);
|
||||
border-block-start: 1px solid var(--color-rule);
|
||||
}
|
||||
|
||||
.notes__head {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: 0.16em;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-muted);
|
||||
margin: 0 0 var(--space-lg);
|
||||
}
|
||||
|
||||
.note {
|
||||
display: grid;
|
||||
grid-template-columns: 4ch 1fr;
|
||||
gap: var(--space-md);
|
||||
padding: var(--space-md) 0;
|
||||
border-block-end: 1px solid var(--color-rule);
|
||||
font-family: var(--font-body);
|
||||
font-size: 1rem;
|
||||
line-height: 1.55;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.note:last-child { border-block-end: none; }
|
||||
|
||||
.note__num {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-accent);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.note__body { color: var(--color-ink); max-width: var(--measure-prose); }
|
||||
.note__body strong { color: var(--color-accent); font-weight: 500; }
|
||||
|
||||
/* Sign-off + colophon */
|
||||
.signoff {
|
||||
margin-block-start: var(--space-2xl);
|
||||
font-family: var(--font-display);
|
||||
font-style: italic;
|
||||
font-size: 1.125rem;
|
||||
color: var(--color-ink-2);
|
||||
}
|
||||
|
||||
.signoff__name { color: var(--color-ink); font-weight: 500; }
|
||||
|
||||
.colophon {
|
||||
margin-block-start: var(--space-3xl);
|
||||
padding-block-start: var(--space-md);
|
||||
border-block-start: 1px solid var(--color-rule);
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-md) var(--space-xl);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-muted);
|
||||
}
|
||||
|
||||
.colophon a {
|
||||
color: var(--color-muted);
|
||||
text-decoration: none;
|
||||
border-block-end: 1px solid transparent;
|
||||
padding-block-end: 1px;
|
||||
transition: color var(--dur-micro) var(--ease-out),
|
||||
border-color var(--dur-micro) var(--ease-out);
|
||||
}
|
||||
|
||||
.colophon a:hover, .colophon a:focus-visible {
|
||||
color: var(--color-accent);
|
||||
border-block-end-color: var(--color-accent);
|
||||
}
|
||||
|
||||
.colophon a:focus-visible {
|
||||
outline: 2px solid var(--color-focus);
|
||||
outline-offset: 4px;
|
||||
}
|
||||
|
||||
@media (max-width: 40rem) {
|
||||
.page { padding: var(--space-xl) var(--space-md) var(--space-2xl); }
|
||||
.salutation { font-size: 1.875rem; }
|
||||
.letter { font-size: 1.0625rem; }
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.colophon a { transition: none; }
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Loop — real-time payment-rail observability for fintechs</title>
|
||||
<meta name="description" content="See every cross-rail payment in real time. Find the regressed leg, read why, and clear the queue before the SLA fires." />
|
||||
<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" />
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<main class="page">
|
||||
<header class="masthead">
|
||||
<span class="masthead__brand"><span class="dot"></span>loop · v0.7</span>
|
||||
<nav class="masthead__nav">
|
||||
<a href="#walkthrough">Walkthrough</a>
|
||||
<a href="#">Rails</a>
|
||||
<a href="#">Pricing</a>
|
||||
<a href="#">Docs</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<section class="hero">
|
||||
<div class="hero__copy">
|
||||
<p class="hero__eyebrow">For payment platform engineers</p>
|
||||
<h1 class="hero__head">Real-time payment-rail observability that <em>tells you why.</em></h1>
|
||||
<p class="hero__sub">
|
||||
Watch every leg of a cross-rail payment in flight. Find the regressed
|
||||
hop, read why — in plain text — without grafana, splunk, and a status-page hunt.
|
||||
Loop is what your on-call wishes the bank's API gave them.
|
||||
</p>
|
||||
<div class="hero__cta">
|
||||
<a class="btn btn--primary" href="#">Try it free <span aria-hidden="true">→</span></a>
|
||||
<a class="btn" href="#">Talk to sales</a>
|
||||
</div>
|
||||
</div>
|
||||
<aside class="hero__panel" aria-label="Sample trace preview">
|
||||
<div class="hero__panel-head"><span>payment · 9c3a72ef</span><span>4.21 s</span></div>
|
||||
<div class="trace">
|
||||
<div class="span"><span class="span__label">api.transfer</span><span class="span__bar"><i style="width:100%"></i></span><span class="span__time">4.21s</span></div>
|
||||
<div class="span"><span class="span__label">↳ kyc.verify</span><span class="span__bar"><i style="width:18%"></i></span><span class="span__time">762ms</span></div>
|
||||
<div class="span span--err"><span class="span__label">↳ rail.swift</span><span class="span__bar"><i style="width:78%"></i></span><span class="span__time">3.28s</span></div>
|
||||
<div class="span span--warn"><span class="span__label"> ↳ fx.spot</span><span class="span__bar"><i style="width:62%"></i></span><span class="span__time">2.71s</span></div>
|
||||
<div class="span"><span class="span__label">↳ ledger.write</span><span class="span__bar"><i style="width:6%"></i></span><span class="span__time">240ms</span></div>
|
||||
</div>
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
<section class="workbench" id="walkthrough">
|
||||
<div class="steps">
|
||||
<article class="step">
|
||||
<p class="step__num">step 01 · open</p>
|
||||
<h2 class="step__head">Open the payment, not a dashboard.</h2>
|
||||
<p class="step__body">
|
||||
Drop a payment ID, an idempotency key, or a five-second time window. Loop
|
||||
pulls the legs from your rails and renders them inline — no Grafana hop,
|
||||
no link to Splunk, no separate logs window. The payment is the unit of work.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article class="step">
|
||||
<p class="step__num">step 02 · find</p>
|
||||
<h2 class="step__head">Find the regressed leg.</h2>
|
||||
<p class="step__body">
|
||||
Loop ranks legs by <strong>p99 deviation against your rail's seven-day baseline</strong>.
|
||||
The first one in the list is almost always the problem. The baseline is
|
||||
precomputed every five minutes per rail; you don't set it.
|
||||
</p>
|
||||
</article>
|
||||
|
||||
<article class="step">
|
||||
<p class="step__num">step 03 · read</p>
|
||||
<h2 class="step__head">Read why, in plain text.</h2>
|
||||
<p class="step__body">
|
||||
Each leg carries an <strong>auto-generated explainer</strong>: which dependency
|
||||
regressed, which version of which service, which deploy correlates, and the
|
||||
diff between then and now. Plain text, copy-paste ready into a post-mortem.
|
||||
</p>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<aside class="pinned" aria-label="Pinned trace panel">
|
||||
<header class="pinned__head">
|
||||
<span>payment · 9c3a72ef</span>
|
||||
<span class="pinned__chip">REGRESSED</span>
|
||||
</header>
|
||||
<div class="trace">
|
||||
<div class="span"><span class="span__label">api.transfer</span><span class="span__bar"><i style="width:100%"></i></span><span class="span__time">4.21s</span></div>
|
||||
<div class="span"><span class="span__label">↳ kyc.verify</span><span class="span__bar"><i style="width:18%"></i></span><span class="span__time">762ms</span></div>
|
||||
<div class="span span--err"><span class="span__label">↳ rail.swift</span><span class="span__bar"><i style="width:78%"></i></span><span class="span__time">3.28s</span></div>
|
||||
<div class="span span--warn"><span class="span__label"> ↳ fx.spot</span><span class="span__bar"><i style="width:62%"></i></span><span class="span__time">2.71s</span></div>
|
||||
<div class="span"><span class="span__label">↳ ledger.write</span><span class="span__bar"><i style="width:6%"></i></span><span class="span__time">240ms</span></div>
|
||||
</div>
|
||||
<p class="pinned__why">
|
||||
<strong>WHY:</strong> rail.swift p99 doubled at 14:02 UTC. Correlates with
|
||||
counterparty BNYM-N's queue depth crossing 12 k pending. Loop's escalation
|
||||
fires automatically; oncall has the diff in their inbox.
|
||||
</p>
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
<footer class="colophon">
|
||||
<span>Loop · v0.7 · made in Lisbon & Berlin</span>
|
||||
<span><a href="#">docs</a> · <a href="#">changelog</a> · <a href="#">status</a> · <a href="#">github</a></span>
|
||||
</footer>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,326 @@
|
||||
/* Hallmark · macrostructure: Workbench · F2 sticky-scroll knobs: pinned=right, content=trace-panel, steps=3
|
||||
* theme: custom · vibe: "industrial precision, cool, technical" · paper: oklch(13% 0.012 220) · accent: oklch(72% 0.16 220)
|
||||
* display: Geist Mono 500 · body: Geist · axes: dark / mono / cool
|
||||
* studied: no · context: explicit · v0.7.0
|
||||
*/
|
||||
|
||||
:root {
|
||||
/* Custom palette — dark cool, sea-blue anchor */
|
||||
--color-paper: oklch(13% 0.012 220);
|
||||
--color-paper-2: oklch(17% 0.014 220);
|
||||
--color-paper-3: oklch(22% 0.014 220);
|
||||
--color-rule: oklch(30% 0.012 220);
|
||||
--color-rule-2: oklch(26% 0.012 220);
|
||||
--color-muted: oklch(58% 0.012 220);
|
||||
--color-neutral: oklch(50% 0.012 220);
|
||||
--color-ink: oklch(94% 0.010 220);
|
||||
--color-ink-2: oklch(72% 0.010 220);
|
||||
--color-accent: oklch(72% 0.16 220);
|
||||
--color-accent-ink: oklch(13% 0.012 220);
|
||||
--color-focus: oklch(78% 0.20 220);
|
||||
--color-warn: oklch(72% 0.16 70);
|
||||
--color-error: oklch(64% 0.18 28);
|
||||
|
||||
/* Single-family pair: Geist Mono display + Geist body — same family, different widths */
|
||||
--font-display: "Geist Mono", "JetBrains Mono", ui-monospace, monospace;
|
||||
--font-body: "Geist", "Inter Tight", -apple-system, sans-serif;
|
||||
--font-mono: "Geist Mono", "JetBrains Mono", ui-monospace, monospace;
|
||||
|
||||
--space-3xs: 0.25rem;
|
||||
--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: 6.5rem;
|
||||
|
||||
--ease-out: cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
--dur-micro: 100ms;
|
||||
--dur-short: 200ms;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
html, body { overflow-x: clip; }
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background: var(--color-paper);
|
||||
color: var(--color-ink);
|
||||
font-family: var(--font-body);
|
||||
font-size: 0.9375rem;
|
||||
line-height: 1.55;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.page { max-width: 80rem; margin: 0 auto; padding: 0 var(--space-lg); }
|
||||
|
||||
.masthead {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: var(--space-md) 0;
|
||||
border-block-end: 1px solid var(--color-rule);
|
||||
font-family: var(--font-display);
|
||||
font-size: 0.8125rem;
|
||||
letter-spacing: 0.04em;
|
||||
color: var(--color-ink-2);
|
||||
}
|
||||
|
||||
.masthead__brand { color: var(--color-ink); font-weight: 500; }
|
||||
.masthead__brand .dot {
|
||||
display: inline-block;
|
||||
width: 0.55ch;
|
||||
height: 0.55ch;
|
||||
background: var(--color-accent);
|
||||
border-radius: 50%;
|
||||
margin-inline-end: 0.5ch;
|
||||
vertical-align: 0.1em;
|
||||
}
|
||||
|
||||
.masthead__nav { display: flex; gap: var(--space-md); }
|
||||
.masthead__nav a {
|
||||
color: var(--color-ink-2);
|
||||
text-decoration: none;
|
||||
transition: color var(--dur-micro) var(--ease-out);
|
||||
}
|
||||
.masthead__nav a:hover, .masthead__nav a:focus-visible { color: var(--color-ink); }
|
||||
.masthead__nav a:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 3px; }
|
||||
|
||||
/* Hero */
|
||||
.hero {
|
||||
padding: var(--space-3xl) 0 var(--space-2xl);
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
|
||||
gap: var(--space-2xl);
|
||||
align-items: end;
|
||||
border-block-end: 1px solid var(--color-rule);
|
||||
}
|
||||
|
||||
.hero__copy { max-width: 36rem; }
|
||||
|
||||
.hero__eyebrow {
|
||||
font-family: var(--font-display);
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-accent);
|
||||
margin: 0 0 var(--space-md);
|
||||
}
|
||||
|
||||
.hero__head {
|
||||
font-family: var(--font-display);
|
||||
font-size: clamp(2rem, 4.5vw, 3.5rem);
|
||||
font-weight: 500;
|
||||
line-height: 1.05;
|
||||
letter-spacing: -0.025em;
|
||||
margin: 0 0 var(--space-md);
|
||||
color: var(--color-ink);
|
||||
}
|
||||
|
||||
.hero__head em { font-style: normal; color: var(--color-accent); }
|
||||
|
||||
.hero__sub {
|
||||
font-size: 1.0625rem;
|
||||
line-height: 1.5;
|
||||
color: var(--color-ink-2);
|
||||
margin: 0 0 var(--space-lg);
|
||||
max-width: 56ch;
|
||||
}
|
||||
|
||||
.hero__cta { display: flex; gap: var(--space-sm); flex-wrap: wrap; }
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5ch;
|
||||
padding: var(--space-sm) var(--space-md);
|
||||
font-family: var(--font-display);
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
text-decoration: none;
|
||||
border: 1px solid var(--color-rule);
|
||||
background: transparent;
|
||||
color: var(--color-ink);
|
||||
cursor: pointer;
|
||||
line-height: 1;
|
||||
transition: background var(--dur-micro) var(--ease-out),
|
||||
border-color var(--dur-micro) var(--ease-out),
|
||||
color var(--dur-micro) var(--ease-out);
|
||||
}
|
||||
|
||||
.btn--primary { background: var(--color-accent); color: var(--color-accent-ink); border-color: var(--color-accent); }
|
||||
.btn--primary:hover, .btn--primary:focus-visible { background: var(--color-ink); border-color: var(--color-ink); }
|
||||
.btn:hover, .btn:focus-visible { border-color: var(--color-accent); color: var(--color-accent); }
|
||||
.btn:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 3px; }
|
||||
|
||||
/* Hero panel — sample trace */
|
||||
.hero__panel {
|
||||
background: var(--color-paper-2);
|
||||
border: 1px solid var(--color-rule);
|
||||
border-radius: 4px;
|
||||
padding: var(--space-md);
|
||||
font-family: var(--font-display);
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.hero__panel-head {
|
||||
display: flex; justify-content: space-between;
|
||||
border-block-end: 1px solid var(--color-rule);
|
||||
padding-block-end: var(--space-2xs);
|
||||
margin-block-end: var(--space-sm);
|
||||
color: var(--color-ink-2);
|
||||
}
|
||||
|
||||
.trace { display: grid; gap: 4px; }
|
||||
.span {
|
||||
display: grid;
|
||||
grid-template-columns: 14ch 1fr 6ch;
|
||||
gap: var(--space-2xs);
|
||||
align-items: center;
|
||||
}
|
||||
.span__label { color: var(--color-ink); white-space: nowrap; }
|
||||
.span__bar {
|
||||
position: relative;
|
||||
height: 8px;
|
||||
background: var(--color-paper-3);
|
||||
border-radius: 1px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.span__bar > i {
|
||||
position: absolute;
|
||||
inset-block: 0;
|
||||
inset-inline-start: 0;
|
||||
background: var(--color-accent);
|
||||
border-radius: 1px;
|
||||
}
|
||||
.span--err .span__bar > i { background: var(--color-error); }
|
||||
.span--warn .span__bar > i { background: var(--color-warn); }
|
||||
.span__time { font-variant-numeric: tabular-nums; color: var(--color-ink-2); text-align: end; }
|
||||
|
||||
/* Walkthrough */
|
||||
.workbench {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr);
|
||||
gap: var(--space-2xl);
|
||||
padding: var(--space-3xl) 0;
|
||||
}
|
||||
|
||||
.steps { display: grid; gap: var(--space-3xl); }
|
||||
|
||||
.step {
|
||||
border-inline-start: 2px solid var(--color-rule);
|
||||
padding-inline-start: var(--space-lg);
|
||||
position: relative;
|
||||
}
|
||||
.step::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset-inline-start: -2px;
|
||||
inset-block-start: 0;
|
||||
width: 2px;
|
||||
height: 0;
|
||||
background: var(--color-accent);
|
||||
transition: height var(--dur-short) var(--ease-out);
|
||||
}
|
||||
.step:hover::before, .step:focus-within::before { height: 100%; }
|
||||
|
||||
.step__num {
|
||||
font-family: var(--font-display);
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--color-accent);
|
||||
text-transform: uppercase;
|
||||
margin-block-end: var(--space-sm);
|
||||
}
|
||||
|
||||
.step__head {
|
||||
font-family: var(--font-display);
|
||||
font-size: clamp(1.5rem, 2.5vw, 2rem);
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.015em;
|
||||
line-height: 1.1;
|
||||
margin: 0 0 var(--space-md);
|
||||
color: var(--color-ink);
|
||||
}
|
||||
|
||||
.step__body {
|
||||
font-size: 1rem;
|
||||
line-height: 1.6;
|
||||
color: var(--color-ink-2);
|
||||
max-width: 48ch;
|
||||
}
|
||||
.step__body strong { color: var(--color-ink); font-weight: 500; }
|
||||
|
||||
.pinned {
|
||||
position: sticky;
|
||||
top: var(--space-xl);
|
||||
height: fit-content;
|
||||
background: var(--color-paper-2);
|
||||
border: 1px solid var(--color-rule);
|
||||
border-radius: 4px;
|
||||
padding: var(--space-lg);
|
||||
}
|
||||
|
||||
.pinned__head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-family: var(--font-display);
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--color-ink-2);
|
||||
border-block-end: 1px solid var(--color-rule);
|
||||
padding-block-end: var(--space-sm);
|
||||
margin-block-end: var(--space-md);
|
||||
}
|
||||
|
||||
.pinned__chip {
|
||||
font-family: var(--font-display);
|
||||
font-size: 0.6875rem;
|
||||
letter-spacing: 0.08em;
|
||||
padding: 2px 6px;
|
||||
border: 1px solid var(--color-accent);
|
||||
color: var(--color-accent);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.pinned__why {
|
||||
margin-block-start: var(--space-md);
|
||||
padding-block-start: var(--space-md);
|
||||
border-block-start: 1px solid var(--color-rule);
|
||||
font-family: var(--font-body);
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-ink-2);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.pinned__why strong { color: var(--color-error); font-weight: 500; }
|
||||
|
||||
.colophon {
|
||||
padding: var(--space-md) 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-sm);
|
||||
font-family: var(--font-display);
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--color-ink-2);
|
||||
border-block-start: 1px solid var(--color-rule);
|
||||
}
|
||||
|
||||
.colophon a { color: var(--color-ink-2); text-decoration: none; transition: color var(--dur-micro) var(--ease-out); }
|
||||
.colophon a:hover, .colophon a:focus-visible { color: var(--color-accent); }
|
||||
.colophon a:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 3px; }
|
||||
|
||||
@media (max-width: 56rem) {
|
||||
.hero { grid-template-columns: 1fr; }
|
||||
.workbench { grid-template-columns: 1fr; }
|
||||
.pinned { position: static; }
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.step::before { transition: none; }
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Mossroot — small-batch herbal apothecary, Porto</title>
|
||||
<meta name="description" content="A small herbal apothecary in Porto. Tinctures, salves, tea blends, dried herbs. Made by hand, sold by the gram." />
|
||||
<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=Cormorant+Garamond:ital,wght@0,400;0,500;1,400&family=EB+Garamond:ital,wght@0,400;0,500;1,400&display=swap" />
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<main class="page">
|
||||
<header class="masthead">
|
||||
<hr class="masthead__rule" />
|
||||
<h1 class="masthead__brand">Moss<span class="em">root</span></h1>
|
||||
<p class="masthead__sub">Herbal apothecary · Porto · est. 2024 · made by hand · sold by the gram</p>
|
||||
</header>
|
||||
|
||||
<section class="todays">
|
||||
<h2 class="todays__head">This week's batch</h2>
|
||||
<p class="todays__date">made the first of May — eight things — sold from the bench until they're gone</p>
|
||||
</section>
|
||||
|
||||
<ul class="catalogue" role="list" aria-label="This week's apothecary batch">
|
||||
|
||||
<li class="item">
|
||||
<span class="item__shape item__shape--accent" aria-hidden="true">
|
||||
<svg viewBox="0 0 96 96"><path d="M48 12 C 24 28, 24 60, 48 84 C 72 60, 72 28, 48 12"/><path d="M48 24 L48 80 M30 50 L48 50 L66 50"/></svg>
|
||||
</span>
|
||||
<div class="item__body">
|
||||
<h3 class="item__name">Calendula <em>salve</em></h3>
|
||||
<p class="item__desc">Cold-infused calendula in olive oil, set with beeswax. For dry hands and small cuts.</p>
|
||||
</div>
|
||||
<span class="item__price">€18 / 30 g</span>
|
||||
</li>
|
||||
|
||||
<li class="item">
|
||||
<span class="item__shape" aria-hidden="true">
|
||||
<svg viewBox="0 0 96 96"><path d="M30 76 L30 30 Q48 18 66 30 L66 76 Z"/><path d="M30 36 L66 36 M30 50 L66 50 M30 64 L66 64"/></svg>
|
||||
</span>
|
||||
<div class="item__body">
|
||||
<h3 class="item__name">Lemon balm <em>tincture</em></h3>
|
||||
<p class="item__desc">Garden-grown <em>melissa</em>, six-week alcohol extraction. Forty drops at bedtime.</p>
|
||||
</div>
|
||||
<span class="item__price">€24 / 50 ml</span>
|
||||
</li>
|
||||
|
||||
<li class="item">
|
||||
<span class="item__shape item__shape--accent" aria-hidden="true">
|
||||
<svg viewBox="0 0 96 96"><circle cx="48" cy="48" r="32"/><path d="M48 16 Q56 32 48 48 Q40 64 48 80"/><path d="M16 48 Q32 56 48 48 Q64 40 80 48"/></svg>
|
||||
</span>
|
||||
<div class="item__body">
|
||||
<h3 class="item__name">Three-flower <em>tea blend</em></h3>
|
||||
<p class="item__desc">Chamomile, rose, lavender. Loose-leaf, no tea-bag, no flavour.</p>
|
||||
</div>
|
||||
<span class="item__price">€14 / 60 g</span>
|
||||
</li>
|
||||
|
||||
<li class="item">
|
||||
<span class="item__shape" aria-hidden="true">
|
||||
<svg viewBox="0 0 96 96"><path d="M16 60 Q32 36 48 60 Q64 36 80 60"/><path d="M16 60 L16 72 L80 72 L80 60"/><path d="M48 30 L48 60"/></svg>
|
||||
</span>
|
||||
<div class="item__body">
|
||||
<h3 class="item__name">Mossy forest <em>balm</em></h3>
|
||||
<p class="item__desc">Pine resin, comfrey, beeswax. For aching joints — what the dock workers buy.</p>
|
||||
</div>
|
||||
<span class="item__price">€26 / 60 g</span>
|
||||
</li>
|
||||
|
||||
<li class="item">
|
||||
<span class="item__shape item__shape--accent" aria-hidden="true">
|
||||
<svg viewBox="0 0 96 96"><path d="M48 16 Q34 24 30 38 Q26 52 32 64 Q40 76 48 80"/><path d="M48 16 Q62 24 66 38 Q70 52 64 64 Q56 76 48 80"/></svg>
|
||||
</span>
|
||||
<div class="item__body">
|
||||
<h3 class="item__name">Yarrow <em>tincture</em></h3>
|
||||
<p class="item__desc">Wild-foraged from Gerês, six-week extraction. For colds, fevers, and blood-tonic.</p>
|
||||
</div>
|
||||
<span class="item__price">€22 / 50 ml</span>
|
||||
</li>
|
||||
|
||||
<li class="item">
|
||||
<span class="item__shape" aria-hidden="true">
|
||||
<svg viewBox="0 0 96 96"><circle cx="48" cy="48" r="20"/><circle cx="48" cy="48" r="32"/><path d="M48 16 L48 80 M16 48 L80 48"/></svg>
|
||||
</span>
|
||||
<div class="item__body">
|
||||
<h3 class="item__name">Rose-petal <em>honey</em></h3>
|
||||
<p class="item__desc">Local honey, wild rose petals from the Douro. Friday batch only — six jars.</p>
|
||||
</div>
|
||||
<span class="item__price item__price--out">sold out</span>
|
||||
</li>
|
||||
|
||||
<li class="item">
|
||||
<span class="item__shape item__shape--accent" aria-hidden="true">
|
||||
<svg viewBox="0 0 96 96"><path d="M48 14 Q60 28 60 48 Q60 68 48 82 Q36 68 36 48 Q36 28 48 14"/><path d="M48 14 L48 82"/></svg>
|
||||
</span>
|
||||
<div class="item__body">
|
||||
<h3 class="item__name">Sage & thyme <em>vinegar</em></h3>
|
||||
<p class="item__desc">Apple cider, fresh sage, thyme, four weeks dark. For salad, for sore throats.</p>
|
||||
</div>
|
||||
<span class="item__price">€16 / 250 ml</span>
|
||||
</li>
|
||||
|
||||
<li class="item">
|
||||
<span class="item__shape" aria-hidden="true">
|
||||
<svg viewBox="0 0 96 96"><circle cx="48" cy="48" r="6"/><circle cx="28" cy="38" r="4"/><circle cx="68" cy="38" r="4"/><circle cx="38" cy="62" r="4"/><circle cx="58" cy="62" r="4"/><circle cx="48" cy="22" r="3"/><circle cx="48" cy="74" r="3"/></svg>
|
||||
</span>
|
||||
<div class="item__body">
|
||||
<h3 class="item__name">Dried <em>seven-herb</em> bunch</h3>
|
||||
<p class="item__desc">St-John's-wort, mugwort, chamomile, rosemary, lavender, lemon balm, sage. Tied with twine.</p>
|
||||
</div>
|
||||
<span class="item__price">€12 / bunch</span>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
<section class="visit">
|
||||
<h2 class="visit__head">Find us</h2>
|
||||
<div class="visit__lines">
|
||||
<span><strong>Rua de Cedofeita 308, Porto</strong></span>
|
||||
<span><em>Tuesday – Saturday, 11 a.m. until the bench is empty (usually 6 p.m.)</em></span>
|
||||
<span><em>No online orders. Bring a glass jar if you'd like a refill.</em></span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer class="colophon">
|
||||
<span>Mossroot · the year twenty twenty-six · the first of May</span>
|
||||
·
|
||||
<a href="mailto:hello@mossroot.pt">hello@mossroot.pt</a>
|
||||
</footer>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,262 @@
|
||||
/* Hallmark · macrostructure: Catalogue · F1 catalogue knobs: tiles=8, columns=2, rule=hairline-between
|
||||
* theme: custom · vibe: "moss, lichen, soft pink, herbal" · paper: oklch(96% 0.018 145) · accent: oklch(72% 0.13 350)
|
||||
* display: Cormorant Garamond · body: EB Garamond · axes: light / roman-serif / chromatic-other (dusty-pink)
|
||||
* studied: no · context: explicit · v0.7.0
|
||||
*/
|
||||
|
||||
:root {
|
||||
/* Custom palette — moss-tinted paper with dusty-pink accent */
|
||||
--color-paper: oklch(96% 0.018 145);
|
||||
--color-paper-2: oklch(93% 0.020 145);
|
||||
--color-paper-3: oklch(90% 0.022 145);
|
||||
--color-rule: oklch(82% 0.018 145);
|
||||
--color-rule-2: oklch(86% 0.018 145);
|
||||
--color-muted: oklch(56% 0.014 140);
|
||||
--color-neutral: oklch(46% 0.014 140);
|
||||
--color-ink: oklch(22% 0.014 140);
|
||||
--color-ink-2: oklch(42% 0.014 140);
|
||||
--color-accent: oklch(72% 0.13 350);
|
||||
--color-accent-ink: oklch(96% 0.014 145);
|
||||
--color-focus: oklch(70% 0.18 350);
|
||||
|
||||
--font-display: "Cormorant Garamond", "EB Garamond", "Cardo", Georgia, serif;
|
||||
--font-body: "EB Garamond", "Crimson Pro", "Cardo", Georgia, serif;
|
||||
--font-mono: "Geist Mono", "JetBrains Mono", ui-monospace, monospace;
|
||||
|
||||
--space-3xs: 0.25rem;
|
||||
--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: 6.5rem;
|
||||
|
||||
--ease-out: cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
--dur-micro: 140ms;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
html, body { overflow-x: clip; }
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background: var(--color-paper);
|
||||
color: var(--color-ink);
|
||||
font-family: var(--font-body);
|
||||
font-size: 1.0625rem;
|
||||
line-height: 1.55;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.page {
|
||||
max-width: 64rem;
|
||||
margin: 0 auto;
|
||||
padding: var(--space-2xl) var(--space-lg) var(--space-3xl);
|
||||
}
|
||||
|
||||
/* Masthead — apothecary plate */
|
||||
.masthead {
|
||||
text-align: center;
|
||||
border-block-end: 2px double var(--color-rule);
|
||||
padding-block-end: var(--space-lg);
|
||||
margin-block-end: var(--space-2xl);
|
||||
}
|
||||
|
||||
.masthead__rule {
|
||||
width: 4ch;
|
||||
border: 0;
|
||||
border-block-start: 1px solid var(--color-ink);
|
||||
margin: 0 auto var(--space-md);
|
||||
}
|
||||
|
||||
.masthead__brand {
|
||||
font-family: var(--font-display);
|
||||
font-size: clamp(2.75rem, 6vw, 4.5rem);
|
||||
font-weight: 400;
|
||||
line-height: 1;
|
||||
letter-spacing: 0.005em;
|
||||
margin: 0 0 var(--space-2xs);
|
||||
color: var(--color-ink);
|
||||
}
|
||||
|
||||
.masthead__brand .em { color: var(--color-accent); font-style: italic; }
|
||||
|
||||
.masthead__sub {
|
||||
font-family: var(--font-display);
|
||||
font-style: italic;
|
||||
font-size: 1rem;
|
||||
letter-spacing: 0.04em;
|
||||
color: var(--color-ink-2);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Today's batch */
|
||||
.todays {
|
||||
text-align: center;
|
||||
margin-block-end: var(--space-xl);
|
||||
}
|
||||
|
||||
.todays__head {
|
||||
font-family: var(--font-display);
|
||||
font-size: clamp(1.625rem, 3vw, 2.25rem);
|
||||
font-weight: 400;
|
||||
letter-spacing: 0.02em;
|
||||
margin: 0 0 var(--space-2xs);
|
||||
color: var(--color-ink);
|
||||
}
|
||||
|
||||
.todays__date {
|
||||
font-family: var(--font-display);
|
||||
font-style: italic;
|
||||
font-size: 1rem;
|
||||
color: var(--color-muted);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Catalogue grid */
|
||||
.catalogue {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 0;
|
||||
border-block-start: 1px solid var(--color-rule);
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.item {
|
||||
display: grid;
|
||||
grid-template-columns: 96px 1fr auto;
|
||||
align-items: center;
|
||||
gap: var(--space-md);
|
||||
padding: var(--space-lg) var(--space-md);
|
||||
border-block-end: 1px solid var(--color-rule);
|
||||
position: relative;
|
||||
transition: background var(--dur-micro) var(--ease-out);
|
||||
}
|
||||
|
||||
.item:nth-child(odd) { border-inline-end: 1px solid var(--color-rule); }
|
||||
.item:hover { background: var(--color-paper-2); }
|
||||
|
||||
.item__shape {
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--color-ink-2);
|
||||
}
|
||||
|
||||
.item__shape svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-width: 1.4;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
}
|
||||
|
||||
.item__shape--accent svg { stroke: var(--color-accent); }
|
||||
|
||||
.item__body { min-width: 0; }
|
||||
|
||||
.item__name {
|
||||
font-family: var(--font-display);
|
||||
font-size: 1.5rem;
|
||||
font-weight: 400;
|
||||
letter-spacing: 0.005em;
|
||||
margin: 0 0 var(--space-3xs);
|
||||
color: var(--color-ink);
|
||||
}
|
||||
|
||||
.item__name em { font-style: italic; color: var(--color-accent); }
|
||||
|
||||
.item__desc {
|
||||
font-family: var(--font-body);
|
||||
font-style: italic;
|
||||
font-size: 0.9375rem;
|
||||
color: var(--color-ink-2);
|
||||
margin: 0;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.item__price {
|
||||
font-family: var(--font-display);
|
||||
font-size: 1.0625rem;
|
||||
font-variant-numeric: tabular-nums;
|
||||
color: var(--color-ink);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.item__price--out {
|
||||
color: var(--color-muted);
|
||||
font-style: italic;
|
||||
font-family: var(--font-body);
|
||||
font-size: 0.9375rem;
|
||||
}
|
||||
|
||||
/* Visit */
|
||||
.visit {
|
||||
margin-block-start: var(--space-2xl);
|
||||
padding: var(--space-xl) var(--space-md);
|
||||
border-block-start: 2px double var(--color-rule);
|
||||
border-block-end: 2px double var(--color-rule);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.visit__head {
|
||||
font-family: var(--font-display);
|
||||
font-size: 1.625rem;
|
||||
font-weight: 400;
|
||||
margin: 0 0 var(--space-md);
|
||||
color: var(--color-ink);
|
||||
}
|
||||
|
||||
.visit__lines {
|
||||
display: grid;
|
||||
gap: var(--space-2xs);
|
||||
font-family: var(--font-body);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.visit__lines em { font-style: italic; color: var(--color-ink-2); }
|
||||
.visit__lines strong { font-weight: 500; color: var(--color-ink); }
|
||||
|
||||
/* Colophon */
|
||||
.colophon {
|
||||
margin-block-start: var(--space-2xl);
|
||||
text-align: center;
|
||||
font-family: var(--font-display);
|
||||
font-style: italic;
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-muted);
|
||||
}
|
||||
|
||||
.colophon a {
|
||||
color: var(--color-muted);
|
||||
text-decoration: none;
|
||||
border-block-end: 1px solid transparent;
|
||||
transition: color var(--dur-micro) var(--ease-out),
|
||||
border-color var(--dur-micro) var(--ease-out);
|
||||
}
|
||||
|
||||
.colophon a:hover, .colophon a:focus-visible {
|
||||
color: var(--color-accent);
|
||||
border-block-end-color: var(--color-accent);
|
||||
}
|
||||
|
||||
.colophon a:focus-visible {
|
||||
outline: 2px solid var(--color-focus);
|
||||
outline-offset: 4px;
|
||||
}
|
||||
|
||||
@media (max-width: 48rem) {
|
||||
.catalogue { grid-template-columns: 1fr; }
|
||||
.item:nth-child(odd) { border-inline-end: none; }
|
||||
.item { grid-template-columns: 64px 1fr auto; gap: var(--space-sm); padding: var(--space-md) var(--space-2xs); }
|
||||
.item__shape { width: 64px; height: 64px; }
|
||||
.item__name { font-size: 1.25rem; }
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
# Custom worked examples
|
||||
|
||||
Three landing pages produced by the **custom** theme route — the new opt-in branch added to Hallmark in v0.6.x. Custom is the route for one-off custom palettes + font pairings tuned to a single brand. **Catalog** (the 16 named themes) is the default; custom fires only when the brief signals creative or unique intent.
|
||||
|
||||
These three pages are the worked examples written into [`skill/references/custom-theme.md`](../../../skill/references/custom-theme.md) § G — rendered as actual HTML so the palette + pairing can be inspected visually.
|
||||
|
||||
| # | Brand | Vibe | Anchor | Macrostructure | Axes |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 01 | **Coffeebox** — small-batch coffee subscription | "archival warmth, hand-set, no varnish" | terracotta | Long Document | light / italic-serif / chromatic-terracotta |
|
||||
| 02 | **Loop** — payment-rail observability for fintechs | "industrial precision, cool, technical" | sea-blue | Workbench | dark / mono / cool |
|
||||
| 03 | **Mossroot** — herbal apothecary in Porto | "moss, lichen, soft pink, herbal" | (skipped — derived) | Catalogue | light / roman-serif / chromatic-other (dusty-pink) |
|
||||
|
||||
Each folder holds:
|
||||
|
||||
- **`index.html`** — the rendered page
|
||||
- **`style.css`** — custom palette + pairing as inline `:root` tokens, with the custom stamp at the top
|
||||
|
||||
## What custom unlocks
|
||||
|
||||
The 16 named themes can't carry every brand. Coffeebox is closest to **Atelier** but warmer + more terracotta-led; Loop is closest to **Midnight** but mono-everywhere and sea-blue not phosphor-cyan; Mossroot has no catalog match (moss-tinted paper with dusty-pink accent isn't in the catalogue). Without custom, all three pages would compromise on the brand voice. With custom, the palette + pairing are tuned to the specific vibe — and every existing rule (OKLCH bands, accent footprint, font ban list, slop test) carries forward unchanged. The freedom is the combination, not the rules.
|
||||
|
||||
## What guards prevent over-invention
|
||||
|
||||
1. **The opt-in is opt-in.** Custom fires only when the user signals creative/unique intent in their prompt (named brand colour, multi-attribute aesthetic that doesn't match a catalog theme name, explicit "make it custom"). Default route is catalog — no behavioural change for vanilla briefs.
|
||||
2. **One question only.** The skill asks vibe (4–8 words) + optional anchor colour. Anything more is over-asking.
|
||||
3. **Every existing rule applies.** color.md OKLCH bands, typography.md pairing catalogue, anti-patterns.md ban list, the 38 slop-test gates — same gates fire as on catalog themes.
|
||||
4. **The Step 5 preview surfaces the palette + pairing.** Before any code is emitted, the user sees the OKLCH values and the chosen fonts in plain text. They can redirect early.
|
||||
5. **Diversification is theme-route-blind.** Each custom run records its three axis values (paper-band / display-style / accent-hue) in `.hallmark/log.json`. The next run rotates against them the same way it does against catalog themes.
|
||||
|
||||
See [`skill/references/custom-theme.md`](../../../skill/references/custom-theme.md) for the full protocol.
|
||||
+6
-15
@@ -46,7 +46,7 @@
|
||||
<header class="banner" role="banner">
|
||||
<a class="banner__mark" href="#top" aria-label="Hallmark — top of page">
|
||||
<span class="banner__name">Hallmark</span>
|
||||
<span class="banner__ver">v0.2</span>
|
||||
<span class="banner__ver">v0.7</span>
|
||||
</a>
|
||||
|
||||
<span class="banner__spacer" aria-hidden="true"></span>
|
||||
@@ -735,9 +735,6 @@
|
||||
<p class="foot__tagline">{{tagline}}</p>
|
||||
<div class="foot__links">
|
||||
<a href="https://github.com/Luffixos/hallmark" class="link">GitHub</a>
|
||||
<a href="https://github.com/pbakaus/impeccable" class="link">impeccable</a>
|
||||
<a href="https://github.com/tw93/kami" class="link">kami</a>
|
||||
<a href="https://github.com/anthropics/skills" class="link">Anthropic skills</a>
|
||||
</div>
|
||||
<p class="foot__powered"><span class="foot__powered-mark">Powered by Together AI</span></p>
|
||||
</div>
|
||||
@@ -762,23 +759,16 @@
|
||||
<ul>
|
||||
<li><a href="https://github.com/Luffixos/hallmark" class="link">GitHub</a></li>
|
||||
<li><a href="#install" class="link">Install</a></li>
|
||||
<li><a href="#install" class="link">Roadmap</a></li>
|
||||
<li><a href="#examples" class="link">Examples</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="foot__col">
|
||||
<p class="foot__col-head">Influences</p>
|
||||
<p class="foot__col-head">Catalog</p>
|
||||
<ul>
|
||||
<li><a href="https://github.com/pbakaus/impeccable" class="link">impeccable</a></li>
|
||||
<li><a href="https://github.com/tw93/kami" class="link">kami</a></li>
|
||||
<li><a href="https://github.com/anthropics/skills" class="link">frontend-design</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="foot__col">
|
||||
<p class="foot__col-head">Themes</p>
|
||||
<ul>
|
||||
<li>12 themes</li>
|
||||
<li>16 themes</li>
|
||||
<li>21 macrostructures</li>
|
||||
<li>32 archetypes</li>
|
||||
<li>38 slop-test gates</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="foot__col">
|
||||
@@ -786,6 +776,7 @@
|
||||
<ul>
|
||||
<li><span class="foot__powered-mark">Together AI</span></li>
|
||||
<li>MIT licensed</li>
|
||||
<li>v0.7.0</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+5
-8
@@ -429,18 +429,15 @@ function interpolate(node, copy) {
|
||||
function buildDenseColophon(themeName) {
|
||||
// Pre-formatted dense block for the dense footer. Generated rather than
|
||||
// template-string'd so it stays monospace-aligned.
|
||||
const today = "2026-04-28";
|
||||
const today = "2026-05-01";
|
||||
return [
|
||||
`# hallmark · v0.2 · ${themeName.toLowerCase()}`,
|
||||
`# hallmark · v0.7.0 · ${themeName.toLowerCase()}`,
|
||||
`# build: ${today} · MIT · powered by together ai`,
|
||||
`#`,
|
||||
`# influences:`,
|
||||
`# pbakaus/impeccable — the named-tells canon`,
|
||||
`# tw93/kami — the slop-test concept`,
|
||||
`# leonxlnx/taste-skill — taste vocabulary`,
|
||||
`# anthropics/skills — frontend-design skill`,
|
||||
`# stats: 21 macrostructures · 32 archetypes · 16 themes · 38 gates`,
|
||||
`# catalog (16 named) + custom (per-brand, opt-in)`,
|
||||
`#`,
|
||||
`# stats: 21 macrostructures · 32 archetypes · 12 themes · 29 gates`,
|
||||
`# repo: github.com/Luffixos/hallmark`,
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
|
||||
+47
-9
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: hallmark
|
||||
description: Use this skill when the user asks to design, build, redesign, audit, refine, or study a UI, web page, landing page, dashboard, component, or interface — or when they ask to make something "feel less AI-generated." Hallmark forces intentional design decisions (typography, color, layout, motion, interaction, structure) and refuses to default to the generic AI-UI template. Trigger phrases include "design a", "build a landing page", "make a dashboard", "redesign this site", "redesign the page", "refine this UI", "audit this design", "this looks AI-generated", "fix the design", "polish this", "give this a different look", and any request that will produce HTML / CSS / JSX / Tailwind output. **Also trigger when the user attaches a screenshot of a design they admire** — that is the `hallmark study` verb (extracts design DNA, never pixel-clones).
|
||||
version: 0.6.0
|
||||
version: 0.7.0
|
||||
---
|
||||
|
||||
# Hallmark
|
||||
@@ -30,6 +30,8 @@ Hallmark has one default behaviour and four explicit verbs.
|
||||
|
||||
If the user types anything that does not clearly map to `audit`, `refine`, `redesign`, or `study`, treat it as default. If the user attaches an image without a verb prefix, ask: *"Should I `study` this (extract the DNA), or should I treat it as a reference for a fresh build?"*
|
||||
|
||||
The default Design flow always picks a theme. By default it picks one of the **16 named themes** — the *catalog* — and rotates among them per the diversification rule. There is also a quiet *custom* branch that constructs a one-off OKLCH palette + free-font pairing for the brief; the custom route fires **only when the brief carries a creative-intent signal** (the user names a brand colour, names a multi-attribute vibe the catalog can't carry, or explicitly asks for a custom theme). For vanilla briefs, the user never sees the words "catalog" or "custom" — the catalog runs silently. See Step 1 (signal detection) and Step 2.6 (dispatch); the protocol lives in [`references/custom-theme.md`](references/custom-theme.md).
|
||||
|
||||
---
|
||||
|
||||
## Design flow (default)
|
||||
@@ -97,6 +99,17 @@ Hallmark works best when you know three things before writing code:
|
||||
|
||||
**Ask once, then commit.** If any of the three is missing, ask for all missing items in **one** short message — not one at a time, not in a follow-up. Offer the user an opt-out at the end of that message: *"or say 'go ahead' and I'll infer from the brief — I'll tell you what I picked."*
|
||||
|
||||
**Theme route — only surface when the brief signals it.** Hallmark has two theme routes: **catalog** (the 16 named themes — Specimen, Atelier, Pastel, Brutal, Salon, Newsprint, Linen, Studio, Manifesto, Plain, Terminal, Midnight, Almanac, Garden, Quiet, Riso) and **custom** (an OKLCH palette + free-font pairing tuned to this one brief). **Catalog is the default.** Do **not** offer the user a choice on every prompt — that's friction, not discipline. Surface the route only when the brief carries one of these signals:
|
||||
|
||||
- The user explicitly says **custom theme** / **tailored to our brand** / **make it ours** / **something unique** / **play with the colors and fonts**.
|
||||
- The user names a **specific brand colour** as the anchor (e.g., "use our terracotta", "the brand red is hex #c0392b", "anchor on sea-blue").
|
||||
- The user describes a **multi-attribute aesthetic that doesn't map to a single catalog theme** — three or more vibe words pointing at a specific feel (e.g., "moss, lichen, soft pink, herbal" / "sun-drenched, market-day, carbon-black" / "late-night, neon, brutalist deli"). One adjective ("warm", "technical", "playful") is *not* a custom signal — that's a tone, and the catalog already carries it.
|
||||
- The user attaches a **brand-mood reference** (a colour swatch, a moodboard, a Pantone chip) without asking to study a screenshot.
|
||||
|
||||
If any of those fires, ask one short follow-up before picking: *"This brief reads like a custom palette would fit better than the 16 named themes. Want me to construct a custom OKLCH palette + free-font pairing tuned to <one-line summary of the vibe>, or stay on the catalog for variety + speed?"* Wait for the user to say custom (or catalog). Default is still catalog — silence routes to catalog, not custom.
|
||||
|
||||
If none of the signals fires, **proceed with catalog silently. Do not mention the fork.** Most briefs don't need a custom theme — the catalog's 16 themes plus the rotation rule already deliver structural variety. See Step 2.6 for the dispatch.
|
||||
|
||||
**If the user opts out** (says "go ahead", "you pick", "skip", "just build it", "don't ask", or simply doesn't engage with the question after one prompt):
|
||||
|
||||
- Infer audience, use case, and tone from the brief, the domain, and any visible context (filename, framework, surrounding code is fair game *now* — only because the user delegated).
|
||||
@@ -171,6 +184,20 @@ Then the theme rotation, on the next line:
|
||||
|
||||
The rotation block keeps the user inside the discipline without making them read the rules. Skip it and the user starts thinking the diversification is theatre.
|
||||
|
||||
### 2.6. Theme route — catalog or custom
|
||||
|
||||
By the time you reach this step, one of three things is true:
|
||||
|
||||
1. **The user named custom** (because they said so, or because Step 1's signal detection fired and they confirmed) → load [`references/custom-theme.md`](references/custom-theme.md), ask the **one** follow-up (vibe in 4–8 words + optional anchor colour), construct the OKLCH palette + free-font pairing, compute the three axis values (paper-band / display-style / accent-hue), then continue to Step 3.
|
||||
2. **The user named catalog** (or implicitly accepted it by not naming custom) → pick one of the 16 named themes per the diversification rule above. Existing flow — continue to Step 3.
|
||||
3. **Neither was discussed** (Step 1's signals didn't fire — vanilla brief) → default to **catalog**. Do not pause. Do not ask. Continue to Step 3.
|
||||
|
||||
**Custom is a quiet branch, not a default question.** Most briefs route to catalog and the user never sees the words "catalog" or "custom." The 16 named themes plus the rotation rule already deliver structural variety; the fork is reserved for when the brief specifically asks for a tuned look the catalog can't carry.
|
||||
|
||||
A custom theme is a **complete** OKLCH palette + font pairing tuned to the brief — not a one-off colour swap, not an excuse to bypass the rules. Every constraint in [`color.md`](references/color.md), [`typography.md`](references/typography.md), and [`anti-patterns.md`](references/anti-patterns.md) still applies. The 38 slop-test gates fire unchanged. The Step 5 preview block surfaces the palette + pairing in plain text **before** any code is emitted, so the user can redirect.
|
||||
|
||||
The diversification rule is theme-route-blind: a custom run that follows another custom (or a catalog) must differ on at least one of the three axes from the previous entry, same as catalog-vs-catalog. Custom entries record their three axes explicitly into `.hallmark/log.json` (see [`custom-theme.md`](references/custom-theme.md) § F).
|
||||
|
||||
### 3. Load the visual ruleset
|
||||
|
||||
The non-negotiables live in [`references/`](references/). Read only what you need:
|
||||
@@ -213,7 +240,7 @@ Before emitting any code, output a tight summary of what you're about to ship. T
|
||||
**Format** (Markdown bullets, not ASCII boxes — they render reliably across every chat client and terminal):
|
||||
|
||||
```markdown
|
||||
**Hallmark · v0.6.0**
|
||||
**Hallmark · v0.7.0**
|
||||
|
||||
- **Macrostructure** · Stat-Led
|
||||
- **Theme** · Plain (#fff paper · cool greys · ink-blue accent)
|
||||
@@ -227,7 +254,7 @@ Before emitting any code, output a tight summary of what you're about to ship. T
|
||||
**Six required bullets, one optional:**
|
||||
|
||||
1. **Macrostructure** — the named pick from [`macrostructures.md`](references/macrostructures.md).
|
||||
2. **Theme** — name + one-line palette summary (paper colour band · accent hue · display style).
|
||||
2. **Theme** — for catalog: name + one-line palette summary (paper colour band · accent hue · display style). For custom: `custom (vibe: "<4–8 words>" · paper oklch(<L%> <C> <H>) · accent oklch(<L%> <C> <H>) <one-word hue label> · <display face> + <body face>)`.
|
||||
3. **Enrichment** — the chosen archetype + tier, or *none (typography only)*.
|
||||
4. **Sections** — section names separated by ` · `, in DOM order.
|
||||
5. **Motion** — microinteraction primitives separated by ` · `, or *none — typography only*. Always under three primitives per the [`microinteractions.md`](references/microinteractions.md) hard rules.
|
||||
@@ -237,7 +264,7 @@ Before emitting any code, output a tight summary of what you're about to ship. T
|
||||
**Three more sample preview blocks** for the model to imitate, varied across macrostructure types:
|
||||
|
||||
*Long Document (editorial, motion-cut):*
|
||||
> **Hallmark · v0.6.0**
|
||||
> **Hallmark · v0.7.0**
|
||||
>
|
||||
> - **Macrostructure** · Long Document
|
||||
> - **Theme** · Linen (cool slate paper · steel-blue accent · geometric sans)
|
||||
@@ -248,7 +275,7 @@ Before emitting any code, output a tight summary of what you're about to ship. T
|
||||
> - **Diversification** · first run for this project
|
||||
|
||||
*Bento Grid (SaaS, motion-on):*
|
||||
> **Hallmark · v0.6.0**
|
||||
> **Hallmark · v0.7.0**
|
||||
>
|
||||
> - **Macrostructure** · Bento Grid
|
||||
> - **Theme** · Pastel (light cool paper · indigo accent · geometric Geist)
|
||||
@@ -259,7 +286,7 @@ Before emitting any code, output a tight summary of what you're about to ship. T
|
||||
> - **Diversification** · differs from Plain on paper hue (light-cool vs pure-white) + accent (indigo vs ink-blue)
|
||||
|
||||
*Manifesto (declarative, no enrichment):*
|
||||
> **Hallmark · v0.6.0**
|
||||
> **Hallmark · v0.7.0**
|
||||
>
|
||||
> - **Macrostructure** · Manifesto
|
||||
> - **Theme** · Manifesto (dark · Inter Tight 900 · single red bleed)
|
||||
@@ -269,6 +296,17 @@ Before emitting any code, output a tight summary of what you're about to ship. T
|
||||
> - **Slop test** · 38 / 38 ✓
|
||||
> - **Diversification** · differs from Linen on paper band (dark vs light) + display style (display-heavy vs geometric-sans)
|
||||
|
||||
*Custom (Coffeebox archival café):*
|
||||
> **Hallmark · v0.7.0**
|
||||
>
|
||||
> - **Macrostructure** · Long Document
|
||||
> - **Theme** · custom (vibe: "archival warmth, hand-set, no varnish" · paper oklch(94% 0.020 65) · accent oklch(58% 0.16 35) terracotta · Fraunces italic display + Source Serif 4 body)
|
||||
> - **Enrichment** · Tier-A pure-CSS coffee bean (60-line SVG, breathing-loop optional)
|
||||
> - **Sections** · Masthead · Letter · Three Notes · Visit · Colophon
|
||||
> - **Motion** · breathing-loop on bean (with reduced-motion fallback)
|
||||
> - **Slop test** · 38 / 38 ✓
|
||||
> - **Diversification** · custom axes: light / italic-serif / chromatic-terracotta — differs from previous catalog Linen on accent hue + display style
|
||||
|
||||
If any slop-test gate fails when you reach Step 7, return to the relevant Build step, fix it, and **re-emit the preview block** with the corrected slop-test row. The preview is the durable summary; it's wrong to ship if it lies.
|
||||
|
||||
### 6. Build
|
||||
@@ -287,8 +325,8 @@ Always:
|
||||
- Include `:focus-visible` with a visible ring at ≥3:1 contrast. **Never animate the ring's appearance** — it must show instantly on focus.
|
||||
- For each interaction in the output (button, input, modal, toast, drag, copy, etc.), apply the recipe in [`microinteractions.md`](references/microinteractions.md). Pick *silent success* over celebratory toasts. Pick *optimistic update + Undo* over confirmation dialogs. Pick *delay 800ms* on hover tooltips and *0ms* on focus tooltips.
|
||||
- Cut motion before adding it. Most pages have too much, not too little. If removing an animation wouldn't lose the user information, remove it.
|
||||
- **Stamp the output.** The first non-empty line of the produced CSS file (or the top of `<style>` if inline) MUST be a comment of the form: `/* Hallmark · macrostructure: <name> · tone: <tone> · anchor hue: <hue> */`. This stamp is the durable record of what you chose. The next time Hallmark runs in this project, it reads the stamp and picks a *different* macrostructure.
|
||||
- **Append to project memory.** After you write the stamp, update (or create) `.hallmark/log.json` at the project root. Append a new entry at the **front** of the array: `{ "date": "<YYYY-MM-DD>", "macrostructure": "<name>", "theme": "<name>", "enrichment": "<E# name or 'none'>", "brief": "<one-line summary>" }`. Trim the file to the last 20 entries (rotate the oldest off). Create `.hallmark/` and the file if they don't exist; respect any existing `.gitignore` (the user may or may not want this committed). This file is what Step 2.5 reads on the next run.
|
||||
- **Stamp the output.** The first non-empty line of the produced CSS file (or the top of `<style>` if inline) MUST be a comment of the form: `/* Hallmark · macrostructure: <name> · tone: <tone> · anchor hue: <hue> */`. This stamp is the durable record of what you chose. The next time Hallmark runs in this project, it reads the stamp and picks a *different* macrostructure. **For custom themes**, the stamp also carries the vibe, paper + accent OKLCH values, the chosen display + body fonts, and the three diversification axes — the full multi-line format is in [`custom-theme.md`](references/custom-theme.md) § E.
|
||||
- **Append to project memory.** After you write the stamp, update (or create) `.hallmark/log.json` at the project root. Append a new entry at the **front** of the array: `{ "date": "<YYYY-MM-DD>", "macrostructure": "<name>", "theme": "<name>", "enrichment": "<E# name or 'none'>", "brief": "<one-line summary>" }`. **Custom entries** also carry `"theme": "custom"` plus `"theme_axes": "<paper-band> / <display-style> / <accent-hue>"` and an optional `"vibe": "<4–8 words>"` — see [`custom-theme.md`](references/custom-theme.md) § F. Trim the file to the last 20 entries (rotate the oldest off). Create `.hallmark/` and the file if they don't exist; respect any existing `.gitignore` (the user may or may not want this committed). This file is what Step 2.5 reads on the next run.
|
||||
|
||||
### 7. The slop test
|
||||
|
||||
@@ -436,7 +474,7 @@ The user has attached or pasted a screenshot of a design they admire. They want
|
||||
|
||||
4. **Confirmation question.** Ask: *"Adopt this DNA wholesale, or change one axis? For example, I could keep the macrostructure but pick a theme that better matches your tone."* Wait for the user's answer before building.
|
||||
|
||||
5. **Build.** Pick the closest matching theme from the canon. Stamp the comment with the inferred macrostructure + archetypes + theme. The user's content goes in; the screenshot's content does not.
|
||||
5. **Build.** Pick the closest matching theme from the catalog. Stamp the comment with the inferred macrostructure + archetypes + theme. The user's content goes in; the screenshot's content does not.
|
||||
|
||||
### Output contract for `study`
|
||||
|
||||
|
||||
@@ -0,0 +1,329 @@
|
||||
# Custom theme — protocol
|
||||
|
||||
Loaded only when the user has opted into the **custom** theme route in Step 1 of the Design flow. Custom produces a **complete OKLCH palette + free-font pairing tuned to this brief** — a one-off theme written inline into the page's `:root`, not a permanent catalog entry.
|
||||
|
||||
**The freedom is the combination, not the rules.** Every constraint in [`color.md`](color.md), [`typography.md`](typography.md), and [`anti-patterns.md`](anti-patterns.md) still applies. The 38 slop-test gates fire unchanged. The Step 5 preview block surfaces the palette + pairing in plain text *before* any code is emitted, so the user can redirect.
|
||||
|
||||
## Two words, plain English
|
||||
|
||||
- **catalog** — the named-theme catalogue. Hallmark's 16 themes (Specimen, Atelier, Pastel, Brutal, Salon, Newsprint, Linen, Studio, Manifesto, Plain, Terminal, Midnight, Almanac, Garden, Quiet, Riso). Each one is a fixed combination of paper-band, display-style, and accent-hue. The rotation rule cycles through them so two consecutive runs don't read alike. **This is the default.** Most briefs use it.
|
||||
- **custom** — made-to-measure. A one-off palette + font pairing constructed specifically for one brief. The skill builds the OKLCH values, picks fonts, and stamps the page — but does **not** add a 17th theme to the catalog. The rules behind the catalog (paper L bands, accent chroma caps, font ban list, slop-test gates) all still apply; only the *combination* is per-brief.
|
||||
|
||||
## When to surface this fork — Step 1 trigger signals
|
||||
|
||||
Hallmark must **not** offer catalog-vs-custom on every prompt. That's friction, not discipline. Surface the fork only when the brief carries one of these signals:
|
||||
|
||||
1. **Explicit ask** — the user types `custom`, "custom theme", "tailored to our brand", "make it ours", "something unique", "play around with the colors and fonts", "I want my own palette".
|
||||
2. **Named brand colour** — the user gives a specific anchor colour as a hex / OKLCH / brand name. Example: "use our terracotta", "the brand red is hex #c0392b", "anchor on sea-blue".
|
||||
3. **Multi-attribute aesthetic the catalog can't carry** — three or more vibe words pointing at a specific, off-catalog feel. Examples: "moss, lichen, soft pink, herbal" / "sun-drenched, market-day, carbon-black" / "late-night, neon, brutalist deli". Compare against the 16 catalog themes; if no single catalog theme is within one axis-step of the vibe, fire the fork. **One adjective ("warm", "technical", "playful") is not a signal — that's a tone, the catalog already carries it.**
|
||||
4. **Brand-mood reference attached** — the user attaches a colour swatch, a moodboard, a Pantone chip. (If they attach a *page* screenshot, route to `study` instead; custom is for brand colour / mood, study is for design DNA.)
|
||||
|
||||
If any signal fires, ask one short follow-up before picking a theme:
|
||||
|
||||
> *"This brief reads like a custom palette would fit better than the 16 named themes. Want me to construct a custom OKLCH palette + free-font pairing tuned to <one-line summary of the vibe>, or stay on the catalog for variety + speed?"*
|
||||
|
||||
Wait for the user to answer. If they say custom (or yes / go) → continue this protocol from § A. If they say catalog (or no / stay catalog) → drop the fork and proceed with the catalog route. **Default to catalog** — silence routes to catalog, not custom.
|
||||
|
||||
If **none** of the signals fires, do not mention the fork at all. Continue silently with the catalog flow.
|
||||
|
||||
---
|
||||
|
||||
## § A · The one follow-up question
|
||||
|
||||
Once the user names `custom` as the theme route, ask **one** thing in **one** message:
|
||||
|
||||
> *"Custom needs one input — describe the brand's vibe in 4–8 words. Examples: 'archival warmth, hand-set, no varnish' · 'industrial precision, cool, technical' · 'moss, lichen, soft pink, herbal' · 'sun-drenched, market-day, carbon black' · 'late-night, neon, brutalist deli'.*
|
||||
>
|
||||
> *Optional second input: an anchor colour — hex, OKLCH, or a name like 'terracotta', 'sea-blue', 'forest-green', 'dusty-pink'. If you skip it, I'll pick one from the vibe."*
|
||||
|
||||
**Do not ask anything else.** Audience / use / tone (Step 1) plus the brand vibe is already enough signal. The model has no business asking the user to nominate paper lightness or font weights — that's the model's job.
|
||||
|
||||
If the user gives just two or three words ("sun-drenched"), proceed; the recipe below extracts enough. If the user gives a paragraph, accept it but compress to 4–8 words for the stamp.
|
||||
|
||||
---
|
||||
|
||||
## § B · Palette construction
|
||||
|
||||
Build the palette in this order. Each step cites the rule it's obeying — do not restate the rule, just apply it.
|
||||
|
||||
### B.1 · Anchor accent first
|
||||
|
||||
- Convert the user's named or hex anchor into OKLCH.
|
||||
- Clamp chroma to **0.12–0.20** per [`color.md`](color.md) § "Accent — the discipline".
|
||||
- If user skipped: derive hue from the vibe — *warmth* → 30–60° · *technical/industrial* → 220–250° · *botanical/moss* → 130–160° · *late-night/neon* → 280–320° · *sun-drenched/market* → 60–80° amber. Keep chroma 0.12–0.16 (mid-saturation; saturation comes from contrast against neutral, not from chroma).
|
||||
|
||||
### B.2 · Paper
|
||||
|
||||
- Derive paper L from the vibe:
|
||||
- bright/airy/breakfast/hand-set → **L 95–98 %** (warm-tinted)
|
||||
- archival/editorial/restrained → **L 92–95 %** (warm-tinted)
|
||||
- technical/clinical/spec-sheet → **L 98–100 % near-white** (cool-tinted; can equal #fff but tinted neutrals downstream)
|
||||
- dark/restless/late-night/manifesto → **L 12–18 %** (anchor-tinted)
|
||||
- **Always tint paper toward the anchor hue with chroma 0.005–0.020** per [`color.md`](color.md) § "Neutral tinting". Pure-white #fff is allowed only when ink + accent + greys carry the chroma; the paper itself never carries chroma 0 in *both* directions.
|
||||
- Paper-2 (one elevation step): step ±2–4 % L from paper.
|
||||
- Paper-3 (optional second step): step ±5–7 % L from paper. Skip on minimal palettes.
|
||||
|
||||
### B.3 · Ink
|
||||
|
||||
- If paper L < 50: ink L **88–96 %**.
|
||||
- If paper L ≥ 50: ink L **16–24 %**.
|
||||
- Tint ink chroma **0.005–0.014** toward anchor (a shade darker / lighter, never neutral).
|
||||
- Ink-2 (secondary text): step 4–8 % L away from ink toward paper. Same hue family.
|
||||
|
||||
### B.4 · Supporting greys
|
||||
|
||||
Step by ~6–10 % L between paper and ink, all tinted toward anchor with chroma 0.005–0.018:
|
||||
|
||||
- `--color-rule` — dividers · L ~70–82 % (light paper) or ~26–34 % (dark paper).
|
||||
- `--color-rule-2` — secondary dividers · 4–6 % L closer to paper than rule.
|
||||
- `--color-muted` — de-emphasised text · L ~38–56 %.
|
||||
- `--color-neutral` — mid-grey equivalent · L ~30–56 %.
|
||||
|
||||
These are not arbitrary. The L-step gives the palette **typographic depth** without leaning on accent.
|
||||
|
||||
### B.5 · Focus
|
||||
|
||||
- Same hue as accent, slightly higher chroma (0.18–0.22) for visibility.
|
||||
- Same L as accent ±5 %.
|
||||
- Used only on `:focus-visible` — must show instantly per [`microinteractions.md`](microinteractions.md) § "Focus is a first-class state".
|
||||
|
||||
### B.6 · Accent-ink (overlay text colour on accent)
|
||||
|
||||
- If accent L > 50: use ink (text reads dark on accent fill).
|
||||
- If accent L ≤ 50: use paper (text reads light on accent fill).
|
||||
- Verify **APCA contrast ≥ 7:1** for body, ≥ 3:1 for large text per [`color.md`](color.md).
|
||||
|
||||
### B.7 · Verification
|
||||
|
||||
- **Gate 8** (no pure #000 / #fff base): paper and ink both have chroma > 0. Pass.
|
||||
- **Gate 24** (no zero-chroma neutrals): every grey has chroma ≥ 0.005. Pass.
|
||||
- **Gate 25** (accent ≤ 5 % footprint): plan the accent's role on the page (active state, one wordmark dot, one CTA fill). Don't carpet a section in accent.
|
||||
|
||||
---
|
||||
|
||||
## § C · Font pairing
|
||||
|
||||
Custom pulls from the seven tone-pairings in [`typography.md`](typography.md) — Editorial, Technical, Brutalist, Soft, Luxury, Playful, Austere, Workshop. Each tone has a **free baseline** and a **paid upgrade**.
|
||||
|
||||
### C.1 · The freedom
|
||||
|
||||
The catalog pairs Display-from-tone-X with Body-from-tone-X. **Custom can mix tones** — that's the whole point:
|
||||
|
||||
- Editorial display + Technical body (italic Fraunces wordmark + Geist body) — works for an academic-tone SaaS.
|
||||
- Brutalist display + Editorial body (Anton + Newsreader italic) — works for a left-leaning manifesto magazine.
|
||||
- Playful display + Austere body (Bricolage Grotesque + Inter Tight) — works for a creator-tool brand.
|
||||
- Luxury display + Technical body (Cormorant Garamond + JetBrains Mono) — works for a hand-crafted dev-tool.
|
||||
|
||||
Pick **one display face** and **one body face** from any tone's columns. Optional mono if the page has code or tabular data.
|
||||
|
||||
### C.2 · The discipline
|
||||
|
||||
- **Free baseline only** unless the user has confirmed paid licences. Per [`typography.md`](typography.md) § "The discipline": "Never name a paid font in code without confirming the user is licensed."
|
||||
- **Banned defaults still banned** per [`typography.md`](typography.md) § "Banned defaults" — Inter / Roboto / Open Sans / Poppins / Lato / Work Sans / DM Sans / Montserrat / system-ui as display all fail Gate 1.
|
||||
- **Variable fonts are preferred** when available (Fraunces, Bricolage Grotesque, Newsreader, Geist, EB Garamond, Inter Tight) — they support optical-size and weight axes for tighter typographic control.
|
||||
|
||||
### C.3 · The pair must read
|
||||
|
||||
Once you have display + body, mentally render the page:
|
||||
|
||||
- Does the display face have enough weight contrast (200/400 next to 700/900) per [`typography.md`](typography.md) § "Commit to extremes"?
|
||||
- Does the body face read at the chosen body size (≥ 14 px floor; default 1 rem) at the chosen measure (45–75 ch)?
|
||||
- If display is mono and body is mono — that's only allowed when the page IS the design (Terminal-aesthetic, true single-font specimen). Per [`typography.md`](typography.md) line 7.
|
||||
|
||||
If any answer is no, redirect — pick a different body face or shift the display weight.
|
||||
|
||||
---
|
||||
|
||||
## § D · Custom-axis computation
|
||||
|
||||
A custom theme must declare its three diversification-rule axis values explicitly so [`SKILL.md`](../SKILL.md) § "Theme-diversification rule" fires the same way as it does on catalog themes.
|
||||
|
||||
### D.1 · Paper band
|
||||
|
||||
- **dark** — paper L < 30 %
|
||||
- **mid** — paper L 30–85 %
|
||||
- **light** — paper L > 85 %
|
||||
|
||||
### D.2 · Display style
|
||||
|
||||
Pick one based on the chosen display face:
|
||||
|
||||
- **italic-serif** — Fraunces italic, Newsreader italic, EB Garamond italic, Cormorant italic
|
||||
- **roman-serif** — Source Serif 4, Newsreader, Crimson Pro, Bitter, Cardo
|
||||
- **geometric-sans** — Geist, Bricolage Grotesque, Inter Tight, Manrope, Sora
|
||||
- **mono** — Geist Mono, JetBrains Mono, IBM Plex Mono, Space Mono
|
||||
- **display-condensed-italic** — Migra italic, Tobias italic
|
||||
- **display-condensed-bold** — Anton, Bebas Neue, Oswald, Barlow Condensed
|
||||
- **display-heavy** — Inter Tight 900, Bricolage 800, Druk-class
|
||||
- **slab-serif** — Roboto Slab, Bitter heavy, Zilla Slab
|
||||
- **system-native** — system-ui, Inter Tight 400 (austere)
|
||||
- **risograph-bold** — bold sans with hand-crafted feel
|
||||
- **handwritten** — Caveat, Sacramento, Patrick Hand (rare; only when brand demands)
|
||||
|
||||
### D.3 · Accent hue band
|
||||
|
||||
- **warm** — hue 10–60° (red, orange, amber)
|
||||
- **cool** — hue 200–300° (blue, indigo, cyan)
|
||||
- **neutral** — no chromatic accent (austere; chroma < 0.05)
|
||||
- **chromatic-other** — anything outside warm/cool/neutral. Sub-tag the specific anchor: `chromatic-green ~145°` · `chromatic-sage ~120°` · `chromatic-phosphor ~150°` · `chromatic-terracotta ~30°` · `chromatic-dusty-pink ~350°` · `chromatic-moss ~140°` · `chromatic-amber ~75°`.
|
||||
|
||||
### D.4 · Where these go
|
||||
|
||||
Write all three into the macrostructure stamp (§ E below) and the `.hallmark/log.json` entry (§ F below). They are the durable record. The next run reads them.
|
||||
|
||||
---
|
||||
|
||||
## § E · Stamp format
|
||||
|
||||
The CSS comment at the top of the produced stylesheet (per [`SKILL.md`](../SKILL.md) Step 6 § "Stamp the output"):
|
||||
|
||||
```css
|
||||
/* Hallmark · macrostructure: <name> · <hero archetype + knobs>
|
||||
* theme: custom · vibe: "<4–8 words>" · paper: oklch(<L>% <C> <H>) · accent: oklch(<L>% <C> <H>)
|
||||
* display: <font name> · body: <font name> · axes: <paper-band> / <display-style> / <accent-hue>
|
||||
* studied: no · context: <user-provided | inferred> · v0.6.x
|
||||
*/
|
||||
```
|
||||
|
||||
Concrete example:
|
||||
|
||||
```css
|
||||
/* Hallmark · macrostructure: Long Document · H5 hero knobs: salutation=time-stamp, body=2 paragraphs, signoff=initials
|
||||
* theme: custom · vibe: "archival warmth, hand-set, no varnish" · paper: oklch(94% 0.020 65) · accent: oklch(58% 0.16 35)
|
||||
* display: Fraunces italic · body: Source Serif 4 · axes: light / italic-serif / chromatic-terracotta
|
||||
* studied: no · context: explicit · v0.7.0
|
||||
*/
|
||||
```
|
||||
|
||||
The stamp is the durable record. `audit` reads it. The next run reads it. The user reads it.
|
||||
|
||||
---
|
||||
|
||||
## § F · `.hallmark/log.json` entry shape
|
||||
|
||||
Custom runs extend the existing schema with a `theme_axes` field and an optional `vibe` field:
|
||||
|
||||
```json
|
||||
{ "date": "2026-05-01",
|
||||
"macrostructure": "Stat-Led",
|
||||
"theme": "custom",
|
||||
"theme_axes": "light / italic-serif / chromatic-terracotta",
|
||||
"vibe": "archival warmth, hand-set, no varnish",
|
||||
"enrichment": "none",
|
||||
"brief": "Coffeebox · subscription" }
|
||||
```
|
||||
|
||||
Catalog entries continue to record `theme: <name>` and skip `theme_axes` (the catalog's axes are looked up from [`tokens.css`](../../site/css/tokens.css)). Step 2.5 logic uses the same diversification check on both — for catalog entries it reads the axes from tokens.css; for custom entries it reads them from the entry.
|
||||
|
||||
When rotating, **a custom run that follows another custom run must differ on at least one axis from the previous custom** — same rule as catalog-vs-catalog. A custom run that follows a catalog run must differ on at least one axis from the catalog's axes. The diversification rule is theme-route-blind.
|
||||
|
||||
---
|
||||
|
||||
## § G · Three worked examples
|
||||
|
||||
Concrete generations to seed model imitation. Each shows the brief, the user's vibe answer, the constructed palette, the chosen pair, and the stamp.
|
||||
|
||||
### G.1 · Archival café — "Coffeebox"
|
||||
|
||||
**Brief:** *"Build me a landing page for Coffeebox — a small-batch coffee subscription. Roast on Sunday, ship on Monday, drink Tuesday. Audience: people who already buy good coffee and want fewer trips to the shop. Tone: warm, hand-set, editorial — like a small café's chalkboard. Theme route: custom."*
|
||||
|
||||
**Vibe answer:** *"archival warmth, hand-set, no varnish."* **Anchor:** *"terracotta."*
|
||||
|
||||
**Palette:**
|
||||
- paper `oklch(94% 0.020 65)` — warm-cream, hue 65 (amber-warm)
|
||||
- paper-2 `oklch(91% 0.022 65)` — one elevation step
|
||||
- ink `oklch(22% 0.014 60)` — warm dark brown-black
|
||||
- ink-2 `oklch(40% 0.014 60)` — warm secondary
|
||||
- rule `oklch(78% 0.018 65)` — warm hairline
|
||||
- muted `oklch(54% 0.014 60)` — warm grey
|
||||
- accent `oklch(58% 0.16 35)` — terracotta (hue 35, chroma 0.16)
|
||||
- accent-ink `oklch(96% 0.014 65)` — paper for text on accent
|
||||
- focus `oklch(56% 0.20 35)` — accent at higher chroma
|
||||
|
||||
**Pair:** display **Fraunces italic** (Editorial, free) · body **Source Serif 4** (Editorial, free) · mono **JetBrains Mono** (Technical, free).
|
||||
|
||||
**Axes:** **light / italic-serif / chromatic-terracotta**.
|
||||
|
||||
**Stamp:**
|
||||
```css
|
||||
/* Hallmark · macrostructure: Long Document · H5 hero knobs: salutation=time-stamp, body=2 paragraphs, signoff=initials
|
||||
* theme: custom · vibe: "archival warmth, hand-set, no varnish" · paper: oklch(94% 0.020 65) · accent: oklch(58% 0.16 35)
|
||||
* display: Fraunces italic · body: Source Serif 4 · axes: light / italic-serif / chromatic-terracotta
|
||||
* studied: no · context: explicit · v0.7.0
|
||||
*/
|
||||
```
|
||||
|
||||
### G.2 · Industrial fintech — "Loop"
|
||||
|
||||
**Brief:** *"Loop is a real-time payment-rail observability platform for fintechs. Audience: platform engineers. Use case: try it / contact sales. Tone: industrial, cool, technical. Theme route: custom."*
|
||||
|
||||
**Vibe answer:** *"industrial precision, cool, technical."* **Anchor:** *"sea-blue."*
|
||||
|
||||
**Palette:**
|
||||
- paper `oklch(13% 0.012 220)` — dark cool
|
||||
- paper-2 `oklch(17% 0.014 220)` — one step up
|
||||
- paper-3 `oklch(22% 0.014 220)` — two steps up (panels)
|
||||
- ink `oklch(94% 0.010 220)` — cool light
|
||||
- ink-2 `oklch(72% 0.010 220)`
|
||||
- rule `oklch(30% 0.012 220)`
|
||||
- muted `oklch(58% 0.012 220)`
|
||||
- accent `oklch(72% 0.16 220)` — sea-blue (cool)
|
||||
- focus `oklch(78% 0.20 220)`
|
||||
|
||||
**Pair:** display **Geist Mono 500** (Technical, free) · body **Geist** (Technical, free) · mono **Geist Mono** (Technical, free).
|
||||
|
||||
Note: this *is* a single-family page (Geist + Geist Mono are the same family at different widths). [`typography.md`](typography.md) line 7 allows it: "single-font pages are allowed only when the single font IS the design choice." For an industrial-precision fintech, that's the design choice.
|
||||
|
||||
**Axes:** **dark / mono / cool**.
|
||||
|
||||
**Stamp:**
|
||||
```css
|
||||
/* Hallmark · macrostructure: Workbench · F2 sticky-scroll knobs: pinned=right, content=trace-panel, steps=3
|
||||
* theme: custom · vibe: "industrial precision, cool, technical" · paper: oklch(13% 0.012 220) · accent: oklch(72% 0.16 220)
|
||||
* display: Geist Mono 500 · body: Geist · axes: dark / mono / cool
|
||||
* studied: no · context: explicit · v0.7.0
|
||||
*/
|
||||
```
|
||||
|
||||
### G.3 · Botanical apothecary — "Mossroot"
|
||||
|
||||
**Brief:** *"Mossroot is a small herbal apothecary in Porto. We make tinctures, salves, and tea blends. Audience: locals + visitors. Use: see what we make + visit. Tone: quiet, herbal, hand-poured. Theme route: custom."*
|
||||
|
||||
**Vibe answer:** *"moss, lichen, soft pink, herbal."* **Anchor:** *(skipped — pick from vibe)*.
|
||||
|
||||
The vibe names two hues: *moss* (greenish, ~140°) and *soft pink* (warm, ~350°). Pick **soft pink as the accent** (single anchor — custom is one-accent strict) and use the moss-green as the *paper tint* (chroma 0.018 toward 145°). This carries the dual-vibe without splitting accent.
|
||||
|
||||
**Palette:**
|
||||
- paper `oklch(96% 0.018 145)` — moss-tinted near-white
|
||||
- paper-2 `oklch(93% 0.020 145)`
|
||||
- ink `oklch(22% 0.014 140)` — moss-tinted dark
|
||||
- ink-2 `oklch(42% 0.014 140)`
|
||||
- rule `oklch(82% 0.018 145)`
|
||||
- muted `oklch(56% 0.014 140)`
|
||||
- accent `oklch(72% 0.13 350)` — dusty-pink (chromatic-other)
|
||||
- focus `oklch(70% 0.18 350)`
|
||||
|
||||
**Pair:** display **Cormorant Garamond** (Luxury, free) · body **EB Garamond** (Luxury, free) · mono **Geist Mono** (rare on this page; only for ingredient lists).
|
||||
|
||||
**Axes:** **light / roman-serif / chromatic-other (dusty-pink)**.
|
||||
|
||||
**Stamp:**
|
||||
```css
|
||||
/* Hallmark · macrostructure: Catalogue · F1 catalogue knobs: tiles=8, columns=2, rule=hairline-between
|
||||
* theme: custom · vibe: "moss, lichen, soft pink, herbal" · paper: oklch(96% 0.018 145) · accent: oklch(72% 0.13 350)
|
||||
* display: Cormorant Garamond · body: EB Garamond · axes: light / roman-serif / chromatic-other (dusty-pink)
|
||||
* studied: no · context: explicit · v0.7.0
|
||||
*/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## What custom does **not** do (worth restating)
|
||||
|
||||
1. **Does not invent themes that ignore the rules.** Every paper L band, accent chroma cap, neutral-tinting requirement, font ban, and slop-test gate carries forward. The freedom is the *combination* — not the rules.
|
||||
2. **Does not save themes for reuse.** A custom run is per-output. The skill does not write back to [`tokens.css`](../../site/css/tokens.css). If the user wants a permanent theme, they paste the custom palette into tokens.css themselves and name it.
|
||||
3. **Does not ask multiple follow-up questions.** One vibe answer (+ optional anchor) is enough. The audience/use/tone from Step 1 plus the brief plus the macrostructure pick already give the model 80 % of the signal.
|
||||
4. **Does not relax the diversification rule.** Custom entries declare their three axes the same way catalog entries do; the rotation rule fires on both, theme-route-blind.
|
||||
5. **Does not bypass the Step 5 preview.** The custom palette + pairing surface in plain text *before* any code is emitted, so the user can redirect early.
|
||||
|
||||
If any of those five lines is bent, the custom output is over-invented. Audit it; redirect.
|
||||
@@ -171,7 +171,7 @@ Anti-patterns I'd skip: [list anything from anti-patterns.md visible in
|
||||
the screenshot — bouncy hovers, transition-all, three-feature grid, etc.
|
||||
If there are none, say so.]
|
||||
|
||||
If you adopt this DNA, the closest theme in the canon is [theme name].
|
||||
If you adopt this DNA, the closest theme in the catalog is [theme name].
|
||||
That's a candidate, not a requirement — your content might point elsewhere.
|
||||
|
||||
Want me to build with this DNA, or change one axis first?
|
||||
@@ -232,7 +232,7 @@ The last line is the **confirmation question**. Wait for the user before buildin
|
||||
>
|
||||
> Anti-patterns I'd skip: nothing from this screenshot stands out as carry-over risk. The reference is clean.
|
||||
>
|
||||
> If you adopt this DNA, the closest theme in the canon is **Studio** (Instrument Serif italic display + Geist body + forest-green accent). Note: the screenshot's accent is neutral; Studio uses a small green mark — if you want neutral, I can desaturate the accent in the build.
|
||||
> If you adopt this DNA, the closest theme in the catalog is **Studio** (Instrument Serif italic display + Geist body + forest-green accent). Note: the screenshot's accent is neutral; Studio uses a small green mark — if you want neutral, I can desaturate the accent in the build.
|
||||
>
|
||||
> Want me to build with this DNA, or change one axis first?
|
||||
|
||||
|
||||
Reference in New Issue
Block a user