diff --git a/README.md b/README.md index cd0eed2..233ab4f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ - + OneShot settlement engine — Arc Testnet, USDC, open workspace diff --git a/docs/assets/nav-panel-dark.svg b/docs/assets/nav-panel-dark.svg index b02f412..70cd07e 100644 --- a/docs/assets/nav-panel-dark.svg +++ b/docs/assets/nav-panel-dark.svg @@ -1,7 +1,7 @@ - + OneShot — settlement engine - - + + @@ -11,17 +11,6 @@ - OneShot - SETTLEMENT ENGINE - - - - Arc Testnet - - USDC - - LIGHT THEME - - - Open workspace + OneShot + SETTLEMENT ENGINE diff --git a/docs/assets/nav-panel-light.svg b/docs/assets/nav-panel-light.svg index 51fcba5..9cace45 100644 --- a/docs/assets/nav-panel-light.svg +++ b/docs/assets/nav-panel-light.svg @@ -1,7 +1,7 @@ - + OneShot — settlement engine - - + + @@ -11,17 +11,6 @@ - OneShot - SETTLEMENT ENGINE - - - - Arc Testnet - - USDC - - LIGHT THEME - - - Open workspace + OneShot + SETTLEMENT ENGINE diff --git a/scripts/render-nav-panel.mjs b/scripts/render-nav-panel.mjs index 8a95905..fcbf077 100644 --- a/scripts/render-nav-panel.mjs +++ b/scripts/render-nav-panel.mjs @@ -1,15 +1,16 @@ /** * Renders the README banner from the product's own front-end source. * - * The banner is the `.top-nav` panel of `apps/web` — the commit-ring mark, the - * wordmark, the network and token badges, the theme control, and the workspace - * link. Nothing here is redrawn by hand: the palette is read from - * `packages/brand/src/tokens.css`, the ring geometry from - * `packages/brand/src/CommitRing.tsx`, and the nav labels from + * The banner is the brand lock-up of `apps/web`'s `.top-nav` — the commit-ring + * mark beside the wordmark and its `SETTLEMENT ENGINE` tag — on the same + * rounded panel, sized for a README rather than for a full-width nav. Nothing + * here is redrawn by hand: the palette comes from + * `packages/brand/src/tokens.css`, the mark geometry from + * `packages/brand/src/CommitRing.tsx`, and the labels from * `apps/web/src/App.tsx`, so the README cannot drift from the shipped UI - * without this script failing or producing a visibly different panel. + * without this script failing or producing a visibly different banner. * - * GitHub strips CSS from Markdown, so the panel ships as two static SVGs (one + * GitHub strips CSS from Markdown, so the banner ships as two static SVGs (one * per theme) selected by a `` element in the README. * * node scripts/render-nav-panel.mjs @@ -22,9 +23,15 @@ const root = new URL('../', import.meta.url); const read = (path) => readFile(new URL(path, root), 'utf8'); const out = (path) => fileURLToPath(new URL(path, root)); +/** + * The nav renders the mark at 36px. A banner that has to survive GitHub's + * column width needs it larger, so every length below is the CSS value scaled + * by this one factor — the proportions stay exactly those of `.top-nav`. + */ +const SCALE = 64 / 36; + /** Geometry mirrored from `apps/web/src/styles.css`, in px at a 16px root. */ -const NAV = { - width: 1000, +const CSS = { paddingX: 20, // .top-nav padding-inline 1.25rem paddingY: 13.6, // .top-nav padding-block 0.85rem radius: 22, // --os-radius-lg @@ -35,21 +42,10 @@ const NAV = { nameTracking: 1.6, // .brand-name letter-spacing 0.1em tagSize: 10.4, // .brand-tag 0.65rem tagTracking: 1.248, // .brand-tag letter-spacing 0.12em - statusGap: 9.6, // .nav-status-group gap 0.6rem - badgeSize: 12, // .status-badge 0.75rem - badgePadX: 12, // .status-badge padding-inline 0.75rem - badgeHeight: 24, // 0.3rem block padding around a 12px line - badgeContentGap: 7.2, // .status-badge gap 0.45rem - dot: 7, // .status-dot - toggleSize: 12.48, // .theme-toggle 0.78rem - toggleTracking: 1.248, // .theme-toggle letter-spacing 0.1em - togglePadX: 14.4, // .theme-toggle padding-inline 0.9rem - toggleHeight: 34, // .theme-toggle min-height - linkSize: 12.8, // .nav-console-link 0.8rem - linkPadX: 16, // .nav-console-link padding-inline 1rem - linkHeight: 30, // 0.45rem block padding around a 12.8px line }; +const B = Object.fromEntries(Object.entries(CSS).map(([key, value]) => [key, value * SCALE])); + /** * Pulls one selector's `--os-*` declarations out of the token sheet. Values * are kept verbatim, so `rgba()` lines survive alongside the hex ones. @@ -65,7 +61,7 @@ function readTheme(css, selector) { return tokens; } -/** Extracts a quoted string list or single literal from a source file. */ +/** Extracts the commit-ring arc geometry from the component source. */ function readArcs(source) { const attempts = [ ...(/const ATTEMPT_ARCS[\s\S]*?=\s*\[([\s\S]*?)\];/u.exec(source)?.[1] ?? '').matchAll( @@ -79,7 +75,7 @@ function readArcs(source) { return { attempts, committed }; } -/** Reads the nav labels from the landing page so the banner cannot drift. */ +/** Reads the wordmark and its tag from the landing nav. */ function readLabels(app) { const pick = (className) => new RegExp(`className="${className}">([^<]+)<`, 'u').exec(app)?.[1]?.trim(); @@ -88,22 +84,13 @@ function readLabels(app) { if (name === undefined || tag === undefined) { throw new Error('could not read brand labels from App.tsx'); } - const network = /className="status-badge network-badge">[\s\S]*?\/>\s*([A-Za-z ]+)\s*([^<]+)\s*([^<]+?)\s* text.replace(/&/gu, '&').replace(//gu, '>'); -function panel(theme, arcs, labels) { - const height = Math.round(NAV.paddingY * 2 + NAV.ring); - const middle = height / 2; - const sans = theme['font-primary']; - const mono = theme['font-mono']; - - // Brand group, left-aligned inside the panel padding. - const ringY = (height - NAV.ring) / 2; - const textX = NAV.paddingX + NAV.ring + NAV.brandGap; - const stack = NAV.nameSize * 1.2 + NAV.brandTextGap + NAV.tagSize * 1.2; +function banner(theme, arcs, labels) { + const height = Math.round(B.paddingY * 2 + B.ring); + const textX = B.paddingX + B.ring + B.brandGap; + const textWidth = Math.max( + advance(labels.name, B.nameSize, B.nameTracking), + advance(labels.tag, B.tagSize, B.tagTracking, true), + ); + const width = Math.round(textX + textWidth + B.paddingX); + + // `.brand-text` is a centred column of two lines. + const stack = B.nameSize * 1.2 + B.brandTextGap + B.tagSize * 1.2; const stackTop = (height - stack) / 2; - const nameBaseline = stackTop + NAV.nameSize * 0.95; - const tagBaseline = stackTop + NAV.nameSize * 1.2 + NAV.brandTextGap + NAV.tagSize * 0.95; - - // Status group, centred like the flex row that holds it. - const networkWidth = - NAV.badgePadX * 2 + NAV.dot + NAV.badgeContentGap + advance(labels.network, NAV.badgeSize); - const tokenWidth = NAV.badgePadX * 2 + advance(labels.token, NAV.badgeSize, 0, true); - const toggleLabel = 'Light theme'.toUpperCase(); - const toggleWidth = NAV.togglePadX * 2 + advance(toggleLabel, NAV.toggleSize, NAV.toggleTracking); - const statusWidth = networkWidth + tokenWidth + toggleWidth + NAV.statusGap * 2; - const statusX = (NAV.width - statusWidth) / 2; - const tokenX = statusX + networkWidth + NAV.statusGap; - const toggleX = tokenX + tokenWidth + NAV.statusGap; - - // Workspace link, right-aligned against the panel padding. - const linkWidth = NAV.linkPadX * 2 + advance(labels.link, NAV.linkSize); - const linkX = NAV.width - NAV.paddingX - linkWidth; - - const ringScale = NAV.ring / 64; + const nameBaseline = stackTop + B.nameSize * 0.95; + const tagBaseline = stackTop + B.nameSize * 1.2 + B.brandTextGap + B.tagSize * 0.95; + + const ringScale = B.ring / 64; const attempts = arcs.attempts .map( (d) => @@ -148,10 +121,10 @@ function panel(theme, arcs, labels) { ) .join('\n'); - return ` - OneShot — settlement engine - - + return ` + ${escape(labels.name)} — ${escape(labels.tag.toLowerCase())} + + ${attempts} @@ -159,19 +132,8 @@ ${attempts} - ${escape(labels.name)} - ${escape(labels.tag)} - - - - ${escape(labels.network)} - - ${escape(labels.token)} - - ${escape(toggleLabel)} - - - ${escape(labels.link)} + ${escape(labels.name)} + ${escape(labels.tag)} `; } @@ -182,6 +144,6 @@ const dark = { ...light, ...readTheme(tokensCss, ":root\\[data-theme='dark'\\]") const arcs = readArcs(await read('packages/brand/src/CommitRing.tsx')); const labels = readLabels(await read('apps/web/src/App.tsx')); -await writeFile(out('docs/assets/nav-panel-light.svg'), panel(light, arcs, labels), 'utf8'); -await writeFile(out('docs/assets/nav-panel-dark.svg'), panel(dark, arcs, labels), 'utf8'); +await writeFile(out('docs/assets/nav-panel-light.svg'), banner(light, arcs, labels), 'utf8'); +await writeFile(out('docs/assets/nav-panel-dark.svg'), banner(dark, arcs, labels), 'utf8'); console.log('wrote docs/assets/nav-panel-{light,dark}.svg');