Author the design language once in the canonical React designbook and project it
one-way onto each stack: React -> designbook/ -> ir/ -> adapters/<stack>/.
Phase 0 — contracts & governance (T01-T03):
- ir/SCHEMA.md + ir/schema/{component,tokens}.schema.json — neutral IR contract
(W3C DTCG tokens; React prop -> HTML attribute mapping; non-portable props flagged).
- adapters/ADAPTER_CONTRACT.md — inputs, drift-report + parity-result shapes,
idempotency rules, CI exit codes (0 ok / 2 usage / 3 drift / 4 parity / 5 internal).
- .claude/rules/designbook-propagation.md + DesignSystemIntroduction.md §5.1 —
one-way directionality + drift-resolution workflow.
T04 — canonical React designbook + the missing pull tool:
- The bundled /design-sync skill only PUSHES repo->cloud; it cannot populate
designbook/. Added scripts/designbook_pull.py + `make designbook-pull`, which drives
the local claude binary headless (acceptEdits) so DesignSync fetch+write runs in a
subprocess (contents never hit the orchestrator's context). Pulled 44 files;
excludes the _whynot-design-seed/ self-copy. Corrected the docs that wrongly called
/design-sync the pull.
T05 — IR extractor (scripts/ir-extract.mjs + `make ir`):
- ir/tokens.json (80 tokens, DTCG, var() -> {ref} alias resolution); ir/components/*.json
(10 contracts parsed from .jsx signatures: enum/boolean/number inference, prop->attr
map, style/callback marked non-portable); ir/exemplars/*.
T06 — Lit token adapter (adapters/lit/ + `make adapt-lit`):
- Full-gen tokens into src/styles/colors_and_type.css :root (marker-bounded, idempotent
no-op on re-run; hand-authored type CSS preserved).
NOTE: token regen synced Lit to canonical React — fonts IBM Plex -> system stacks and 8
status tokens added. This is a VISUAL change: review and run `pnpm test:visual:update`
before merge. Remaining: T07 scaffold+drift, T08 parity, T09 runbook, T10 2nd-adapter.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
225 lines
9.4 KiB
HTML
225 lines
9.4 KiB
HTML
<!doctype html>
|
|
<!-- @dsCard group="Pages" name="Pages · Landing — Login & Registration" subtitle="Public landing · log in / request access toggle" viewport="1280x820" -->
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>whynot — landing</title>
|
|
<link rel="icon" href="../assets/whynot-logo.png">
|
|
<link rel="stylesheet" href="../colors_and_type.css">
|
|
<style>
|
|
html, body { height: 100%; }
|
|
body { margin: 0; background: var(--paper); color: var(--fg-1); }
|
|
|
|
/* faint engineering-graph backdrop, very subtle */
|
|
.page {
|
|
min-height: 100vh;
|
|
display: flex; flex-direction: column;
|
|
}
|
|
|
|
/* ---- top nav ---- */
|
|
.nav {
|
|
height: 60px; flex: none;
|
|
display: flex; align-items: center; gap: 24px;
|
|
padding: 0 40px;
|
|
border-bottom: 1px solid var(--line);
|
|
}
|
|
.nav .brand { display: flex; align-items: center; gap: 10px; }
|
|
.nav .brand img { width: 24px; height: 24px; }
|
|
.nav .brand .nm { font: 500 15px var(--ff-sans); letter-spacing: -0.01em; }
|
|
.nav .brand .slug { font: 400 12px var(--ff-mono); color: var(--fg-3); }
|
|
.nav .links { margin-left: auto; display: flex; align-items: center; gap: 24px; }
|
|
.nav .links a { font: 500 13px var(--ff-sans); color: var(--fg-2); text-decoration: none; }
|
|
.nav .links a:hover { color: var(--fg-1); }
|
|
.nav .pill {
|
|
font: 500 11px/1 var(--ff-mono); letter-spacing: 0.1em; text-transform: uppercase;
|
|
color: var(--fg-3); border: 1px solid var(--line); border-radius: 999px; padding: 6px 11px;
|
|
}
|
|
|
|
/* ---- hero split ---- */
|
|
.hero {
|
|
flex: 1; display: grid; grid-template-columns: 1.15fr 0.85fr;
|
|
align-items: stretch;
|
|
}
|
|
.pitch {
|
|
padding: 72px 56px 56px 40px;
|
|
display: flex; flex-direction: column; gap: 24px;
|
|
border-right: 1px solid var(--line);
|
|
background:
|
|
linear-gradient(to right, var(--line-soft) 1px, transparent 1px) 0 0 / 28px 28px,
|
|
linear-gradient(to bottom, var(--line-soft) 1px, transparent 1px) 0 0 / 28px 28px,
|
|
var(--paper-2);
|
|
}
|
|
.eyebrow-lg { font: 500 11px/1 var(--ff-mono); letter-spacing: 0.14em; text-transform: uppercase; color: var(--fg-3); }
|
|
.pitch h1 {
|
|
font: 300 64px/0.98 var(--ff-sans); letter-spacing: -0.035em;
|
|
margin: 4px 0 0; color: var(--ink);
|
|
max-width: 13ch;
|
|
}
|
|
.pitch h1 .q { font-weight: 500; }
|
|
.pitch .lede { font: 400 18px/1.55 var(--ff-sans); color: var(--fg-2); margin: 0; max-width: 42ch; }
|
|
.codeline {
|
|
font: 500 14px var(--ff-mono); color: var(--fg-1);
|
|
background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-2);
|
|
padding: 12px 16px; align-self: flex-start;
|
|
}
|
|
.codeline .c { color: var(--ink-4); }
|
|
.codeline mark { background: var(--hi); color: var(--hi-ink); padding: 0 3px; }
|
|
|
|
.principles { margin-top: auto; display: flex; flex-direction: column; gap: 0; }
|
|
.principles .p {
|
|
display: grid; grid-template-columns: 28px 1fr; gap: 14px;
|
|
padding: 16px 0; border-top: 1px solid var(--line);
|
|
align-items: baseline;
|
|
}
|
|
.principles .p .k { font: 500 12px var(--ff-mono); color: var(--fg-3); }
|
|
.principles .p .v { font: 400 14px/1.5 var(--ff-sans); color: var(--fg-2); }
|
|
.principles .p .v b { color: var(--fg-1); font-weight: 500; }
|
|
|
|
/* ---- auth panel ---- */
|
|
.auth {
|
|
padding: 72px 40px 56px 56px;
|
|
display: flex; flex-direction: column;
|
|
max-width: 480px;
|
|
}
|
|
.auth .tabs { display: flex; gap: 0; border-bottom: 1px solid var(--line); margin-bottom: 28px; }
|
|
.auth .tab {
|
|
font: 500 13px var(--ff-sans); color: var(--fg-3); background: none; border: 0;
|
|
padding: 0 0 12px; margin-right: 28px; cursor: pointer;
|
|
border-bottom: 2px solid transparent; margin-bottom: -1px;
|
|
}
|
|
.auth .tab.active { color: var(--fg-1); border-bottom-color: var(--ink); }
|
|
|
|
.form { display: flex; flex-direction: column; gap: 18px; }
|
|
.form.hidden { display: none; }
|
|
.field { display: flex; flex-direction: column; gap: 7px; }
|
|
.field label { font: 500 11px/1 var(--ff-mono); letter-spacing: 0.08em; text-transform: uppercase; color: var(--fg-3); }
|
|
.field input, .field textarea {
|
|
font: 400 14px var(--ff-sans); color: var(--fg-1);
|
|
padding: 11px 13px; border: 1px solid var(--line); border-radius: var(--r-1);
|
|
background: var(--paper); outline: none; transition: border-color 120ms ease;
|
|
}
|
|
.field input:focus, .field textarea:focus { border-color: var(--ink); }
|
|
.field input::placeholder, .field textarea::placeholder { color: var(--ink-5); }
|
|
.field textarea { resize: none; min-height: 76px; font-family: var(--ff-sans); }
|
|
.field .row { display: flex; justify-content: space-between; align-items: baseline; }
|
|
.field .row a { font: 400 11px var(--ff-mono); color: var(--fg-3); text-decoration: none; }
|
|
.field .row a:hover { color: var(--fg-1); text-decoration: underline; }
|
|
|
|
.btn {
|
|
font: 500 14px var(--ff-sans); padding: 12px 18px; border-radius: var(--r-2);
|
|
border: 1px solid var(--ink); background: var(--ink); color: var(--paper);
|
|
cursor: pointer; transition: background 120ms ease; margin-top: 4px;
|
|
}
|
|
.btn:hover { background: var(--ink-2); }
|
|
|
|
.note {
|
|
font: 400 12px/1.5 var(--ff-mono); color: var(--fg-3);
|
|
margin-top: 18px; padding-top: 18px; border-top: 1px solid var(--line-soft);
|
|
}
|
|
.note b { color: var(--fg-2); font-weight: 500; }
|
|
|
|
.footer {
|
|
flex: none; padding: 18px 40px; border-top: 1px solid var(--line);
|
|
display: flex; align-items: center; gap: 16px;
|
|
font: 400 11px var(--ff-mono); color: var(--fg-3); letter-spacing: 0.04em;
|
|
}
|
|
.footer .dot { width: 5px; height: 5px; border-radius: 999px; background: var(--ink-4); }
|
|
.footer .sp { margin-left: auto; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="page">
|
|
|
|
<nav class="nav">
|
|
<div class="brand">
|
|
<img src="../assets/whynot-logo.png" alt="">
|
|
<span class="nm">whynot</span>
|
|
<span class="slug">/ prototypes</span>
|
|
</div>
|
|
<div class="links">
|
|
<span class="pill">A1 · Incubating</span>
|
|
<a href="#" onclick="show('login');return false;">Log in</a>
|
|
</div>
|
|
</nav>
|
|
|
|
<div class="hero">
|
|
|
|
<!-- left: pitch -->
|
|
<section class="pitch">
|
|
<span class="eyebrow-lg">Prototype & market-signal space</span>
|
|
<h1>why<span class="q">?</span> why not<span class="q">!</span></h1>
|
|
<p class="lede">A quiet workshop for discovering the weird and the useful — building, testing, and reviewing prototypes before they ever pretend to be products.</p>
|
|
<div class="codeline"><span class="c">$</span> try(<mark>$idea</mark>) until success<span class="c">;</span></div>
|
|
|
|
<div class="principles">
|
|
<div class="p"><span class="k">01</span><span class="v"><b>A prototype is a question made tangible.</b> Not a promise.</span></div>
|
|
<div class="p"><span class="k">02</span><span class="v"><b>Signal beats enthusiasm.</b> Evidence, not vibes.</span></div>
|
|
<div class="p"><span class="k">03</span><span class="v"><b>Capture is not commitment.</b> A good idea can still be parked.</span></div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- right: auth -->
|
|
<section class="auth">
|
|
<div class="tabs">
|
|
<button class="tab active" id="tab-login" onclick="show('login')">Log in</button>
|
|
<button class="tab" id="tab-register" onclick="show('register')">Request access</button>
|
|
</div>
|
|
|
|
<form class="form" id="form-login" onsubmit="return false;">
|
|
<div class="field">
|
|
<label for="li-email">Email</label>
|
|
<input id="li-email" type="email" placeholder="you@example.com" autocomplete="username">
|
|
</div>
|
|
<div class="field">
|
|
<div class="row">
|
|
<label for="li-pw">Password</label>
|
|
<a href="#" onclick="return false;">Forgot?</a>
|
|
</div>
|
|
<input id="li-pw" type="password" placeholder="••••••••" autocomplete="current-password">
|
|
</div>
|
|
<button class="btn" type="submit">Log in</button>
|
|
<p class="note">Access is limited to current contributors and invited beta participants. <b>No public sign-ups.</b></p>
|
|
</form>
|
|
|
|
<form class="form hidden" id="form-register" onsubmit="return false;">
|
|
<div class="field">
|
|
<label for="rg-name">Name</label>
|
|
<input id="rg-name" type="text" placeholder="What should we call you?">
|
|
</div>
|
|
<div class="field">
|
|
<label for="rg-email">Email</label>
|
|
<input id="rg-email" type="email" placeholder="you@example.com">
|
|
</div>
|
|
<div class="field">
|
|
<label for="rg-build">What would you want to build or test?</label>
|
|
<textarea id="rg-build" placeholder="One sentence is plenty. The weirder the better."></textarea>
|
|
</div>
|
|
<button class="btn" type="submit">Request invite</button>
|
|
<p class="note"><b>Closed beta. Invitation only.</b> Requests are read, not auto-approved — silence is also an answer.</p>
|
|
</form>
|
|
</section>
|
|
|
|
</div>
|
|
|
|
<footer class="footer">
|
|
<span class="dot"></span>
|
|
<span>whynot · 2026</span>
|
|
<span>·</span>
|
|
<span>Prereleases & prototypes only</span>
|
|
<span class="sp">try($idea) until success;</span>
|
|
</footer>
|
|
|
|
</div>
|
|
|
|
<script>
|
|
function show(which) {
|
|
var isLogin = which === 'login';
|
|
document.getElementById('form-login').classList.toggle('hidden', !isLogin);
|
|
document.getElementById('form-register').classList.toggle('hidden', isLogin);
|
|
document.getElementById('tab-login').classList.toggle('active', isLogin);
|
|
document.getElementById('tab-register').classList.toggle('active', !isLogin);
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|