Skip to content

v1.4.0: Brand Book and website tokens, color roles, a shared brand guard, the Brand Book highlighter, supergraphics that follow the book, and the spec in the package - #5

Merged
TheBitcoinBackup merged 21 commits into
masterfrom
quentin/design-package-gaps
Oct 6, 2026
Merged

TheBitcoinBackup merged 21 commits into
masterfrom
quentin/design-package-gaps

Conversation

@TheBitcoinBackup

Copy link
Copy Markdown
Contributor

Who decides

The deciders named in CONTRIBUTING.md decide this pull request. Merging it releases v1.4.0 (package.json goes from 1.3.0 to 1.4.0, and the release job tags the merge commit). Nothing reaches a project until that project raises its pin.

Upgrading needs action from projects. The .highlighter class itself now draws the Brand Book's thin line, so a project that styles .highlighter with its own band must delete that band in the same commit that raises its pin. The supergraphics classes also change how they look where they contradicted the Brand Book: black text on orange and on light surfaces (an info bar's date included), and the book's line height and tracking on headlines, labels and body text (item 6 below). No class, variable, export or Tailwind key is removed or renamed, and no variable or Tailwind key changes value; everything else is added. Three choices to make:

  • Keep this approach to the highlighter (the Brand Book wins any conflict with the written spec), or ship the line under a new class and leave .highlighter as it was in v1.3.0, deprecated.
  • 1.4.0, 1.5.0 or 2.0.0. Proposed as 1.4.0, because nothing is removed and the new token groups only add names. The new groups could instead wait for a 1.5.0; 2.0.0 would signal that projects must act on the highlighter and see the supergraphics classes change.
  • The source rule (below): accept that values the Brand Book is silent on are declared from the live website, with their source, or keep the package to Brand Book values only.

Four values are this package's own recommendations, neither Brand Book nor website values; each needs a yes or a different value (see "Package recommendations" below).

Why

The package did not carry much of what the Brand Book already defines (type relationships, logo size and clear space, color roles, CMYK, pattern geometry), and nothing at all for spacing, radius, shadow, motion or widths, so every project wrote those values by hand and they drifted. The live website, myfirstbitcoin.org, was built following the Brand Book, so where the book is silent its values are the best evidence there is, and they can come back into the pipeline with their source. Where the website contradicts the book, the book wins and the website value stays out.

21 commits; node scripts/check.mjs passes on every commit.

The source rule (CONTRIBUTING.md)

  1. Brand Book first. Where the Brand Book in Figma (file mFIc75UUSyftaqnNUQgjLX, page 262:2) defines a value, its value is the token, cited by node.
  2. Where the Brand Book is silent, the value used on the live website may become the token, declared with its origin on the site's main branch: "sourceKind": "declared", "source": "myfirstbitcoin.org@<commit>, <CSS name>", with the number of uses in the note. The CSS name is what carries the value in the CSS and pages the site serves, which are public: a custom property (myfirstbitcoin.org@6368b2a, --r-lg), a selector and property (myfirstbitcoin.org@6368b2a, .article-prose line-height), or a property set inline, with where it is used (myfirstbitcoin.org@6368b2a, box-shadow (inline, media frames)). The site's source repository is private and this one is public, so no file, directory or line of it is named here; the deciders named in CONTRIBUTING keep the exact lines outside the repository. The spec generator refuses a website source in any other form, or one that names a file or a line.
  3. Never a website value that contradicts the Brand Book. Not imported: the 10 degree slant (the book's is 13), the hero's cut of about 5 degrees and its mirrored and mobile versions, the 9 degree ribbon and the 45 degree corner triangle, the logo icon at 15.5 degrees in the favicon, the thick animated highlighter bar (the book draws a thin line under 80% of one word), duotone photos and the dot overlay (the book requires halftone), IBM Plex Mono and the Georgia quote marks, weight 700, teal and indigo tiles and the other off-palette colors, translucent white text, all-caps labels and their 0.12em tracking, orange text and links on light backgrounds and white text on orange buttons (Color Contrast 918:2588: dark text on light colors and on every orange), gray-900 headings and text (the book's are black), heading line height 1.05 and body line height 1.55 in page sections (the book's are 1 and 1.2; the website's 1.7 for article text is declared separately, for long reading only, and is an open question).
  4. Where the site has no single value, no token is made until someone picks one (see the open questions; the breakpoints are held back under this point).
  5. The deciders named in CONTRIBUTING decide every brand change.

tokens.json's $verification and CONTRIBUTING define the source kinds and the website citation the same way: measured now covers a node's rendered fill as well as its geometry (read through the Figma MCP, with node ids in the note), and declared covers a website value (with its site source) or this package's own recommendation ("source": "declared", with the reason).

What changes

New in this round

  1. Brand Book values in tokens.json, each with its Figma node:

    • lineHeight and letterSpacing per type level: 1 for h1 to h4 and quote, 1.1 for h5 and h6, 1.2 for body and label (type specimen 918:2323); no tracking on any brand text style.
    • logo: minimum 120px wide (63px tall) on screen and 25mm in print (918:2711), aspect 1.904, clear space and corner margin 0.5 x the logo height (measured on 918:2681 and 918:2714).
    • colorRole: heading, body, muted, link, link underline, border, highlighter and logo colors on light, dark and orange backgrounds, as aliases of palette colors only, each description naming the surfaces it holds on. Headings and body on light: black (Type Relationships 918:2990, the specimen, the posters); on dark: white (on gray-600, 24px and up only); on orange: black, CTAs included (program card 918:4183 to 918:4185). Links on dark: orange-300 on purple-300 and purple-400 (the website's link color); white and underlined on purple-200 and the dark greys, where orange-300 is too faint for text (3.8:1 on purple-200). Muted on light: gray-700 (the website's). Highlighter: orange-300 on white to gray-300, white on oranges and on gray-400 and gray-500, where an orange line all but disappears. Links on light, the link underline, muted on dark and the logo on orange are this package's recommendations (below).
    • shapeTone: a shape one step lighter than its base (purple-200 on purple-300, orange-200 on orange-300, gray-600 on gray-700; 918:3604).
    • CMYK for all 17 colors (918:2258, 918:2386, 918:2465).
    • Geometry: para-edge-ratio 0.572, pattern-row-step 1.2, pattern-row-gap 0 (measured on 918:4200 and 918:4295), halftone-scale 2.5 (918:3736). The canon does not read the pattern values yet (it uses a 10px gap and steps of 1.25 and 1.55), so the build lists them as token-only, with the reason, and refuses any new geometry that is neither gated against the canon nor listed.
    • Provenance: angle-base becomes measured (13.00 degrees on the base parallelogram 918:4200, the logo icon and five photo frames); base-h-ratio quotes the visible 918:4219 word for word ("The base parallelogram height equals 2× the height of the logo icon."; the old 918:4196 sits in a hidden frame), with the note that no drawing shows the 2x; logo-icon-h (40px) becomes declared, since the book states no pixel value.
    • Color descriptions the book contradicts are corrected: purple-300 is the primary color for surfaces and shapes, not a heading color; text on light backgrounds is black, not gray-900; orange is never text on a light background.
  2. Website-declared tokens for what the book is silent on (table below): the spacing scale and semantic spacing, radius, three shadows tinted with palette colors, easing and durations, container widths, fluid type floors and slopes (the caps are the book's sizes, and the build refuses any other), layers, media ratios, the touch target and the link underline offset. Each cites the website commit and the CSS name that carries the value (rule 2 above). Every note gives its use count, all counted the same way: git grep in the site's source at 6368b2a.

  3. Emitted without touching any existing utility. One GROUP_MAP in the build says where each new group goes: brand.css as --mfb-<family>-<key> (roles as --mfb-<element>-on-<surface>, values by var() and color-mix() of palette variables, never a hex); theme.css and tailwind.js (theme.extend only) with mfb- prefixed keys (rounded-mfb-lg, shadow-mfb-media, ease-mfb-out, duration-mfb-fast, max-w-mfb-page, py-mfb-section), type levels (leading-h1, tracking-h1, text-h1-fluid) or color roles (text-heading-on-light); index.js named exports. The spacing scale equals Tailwind's own steps, so it gets no Tailwind keys; layers, ratios, logo and interface values go only to brand.css and index.js. The build refuses an unmapped group, a key equal to a Tailwind 3 or 4 default or a static keyword, overlapping keys between groups, a role that is not a palette alias, a hex in a new brand.css variable, and a font weight that would change Tailwind's value for its name (font-medium stays 500).

  4. The spec (brand-spec.md) gains color roles, shape tones and CMYK; type relationships (line height, tracking, fluid size per level, weights per role, 40px between a heading and its text and 48px between a quote and its label, measured on 918:2990 and 918:3005); the logo's size, clear space and color by background; layout and spacing, radius and shadow, motion, layers and media, each value with its source; the list of website values not imported; and a table of the supergraphics classes that still differ from the Brand Book, with what to set instead (below). Corrected: headings and text on light are black (Section 3), text on orange is black, buttons included; links on light backgrounds always keep their underline; the logo files (purple logo on greys, black on orange; clear space is half the logo height, not the height of the B); halftone cutouts also sit on purple-200; Rule 7 states the measured pattern step and gap; and the slide labels (Parallelogram 918:4189, Patterns 918:4212, Frames 918:4126, Book Panel 918:3757, Spotlights and Books in Use 918:3716).

  5. brand-check.mjs, one brand guard for every project. A zero-dependency Node script in the package's files and exports (not a package.json script), run after a build: node node_modules/@myfirstbitcoin/design/brand-check.mjs [--src src] [--dist dist]. It generalises the guards two of My First Bitcoin's sites already ran and reads every value from the package's tokens.json at run time.

    • Fails on color literals, #RRGGBBAA and hex inside arbitrary utilities included (text between markup tags is prose and is not read, so a block number such as #840000 or "a color (any one)" is not a color), and named colors (in properties, custom properties, style objects, a canvas fillStyle, SVG attributes and arbitrary utilities such as bg-[teal]); font families other than the brand variables, in CSS, the font shorthand, a --font-* theme variable or a React style object; skew, rotation and angle literals that are not quarter turns, in CSS, style objects, unitless animation props (rotate: -15), Tailwind classes, SVG transform, patternTransform and gradientTransform attributes, or the rotate attribute of SVG text, transform matrices that rotate or skew, and slant geometry computed from --sg-angle-*; hand-rolled clip-path shapes (an SVG clipPath polygon included) and hand-rolled highlighters (a transparent 80% band, an orange pseudo-element drawn as a marker: sized in em or %, behind the text, or named like a highlight; an orange mark; an orange inset shadow, with or without blur; an orange underline 0.2em or 4px thick or more; orange after: or before: utilities); a redefinition of a variable the package defines (--mfb-*, the --sg-* geometry); uppercase (in any case, with quoted style-object keys, and the class as a lone string such as clsx('uppercase') or class:list={['uppercase']}), small caps and small-cap font features; gradients in CSS and SVG gradient elements (the deprecated one included; a hard-stop split that blends nothing is not one, unless it draws in orange), smooth color filters and blend modes; Tailwind color utilities outside the palette and roles; weights above 600; translucent brand shapes (a class, opacity-[...], an inline style, or a highlighter color that is not a palette variable) and orange or purple fills that are not solid (opacity, fill-opacity or filter: opacity() beside them outside a disabled state, alpha modifiers such as bg-orange-200/10, a fill or border mixed with transparent in color-mix()); and raw values that have a token (radius, duration, easing, container width, font size, font weight, shadow or semantic spacing, with the token to use).
    • Warns by default, because the answer depends on what it cannot see or the drift is widespread: spacing literals on the scale (raw-spacing), orange text, purple headings and gray-900 text (text-color, the last also through a value given to a name such as textColor), translucent text (translucent-text: white mixed with transparent, text-white/85), letter spacing (letter-spacing: a non-zero letter-spacing, letterSpacing or tracking-*), and words typed in capitals (typed-caps). --error RULES makes them fail; --warn RULES turns any other rule into a warning, for step-by-step adoption.
    • In the built CSS, read declaration by declaration with nesting, it fails when --sg-angle-base is missing, or when any declaration of --sg-angle-base or --sg-angle-alt (a rule, a nested rule, an @property initial value, a style attribute in built HTML) has another value; when a --color-* is not a palette color or role; on uppercase or small caps outside a utility class definition (a utility that shares its compound selector with another class, .eyebrow.uppercase, is the page's own rule and is reported); and on a font-family in the mono or serif family outside a utility class definition and the base styles of code elements. A utility definition is not a use (Tailwind 3 emits .uppercase when its content scanner sees the word in a comment; the source check reports the class where a page uses it), nor is Tailwind 4's --default-mono-font-family: var(--font-mono). Built findings carry line numbers.
    • It skips what describes rather than uses: @font-face descriptors, family names and palette words in data, Inkscape metadata in SVG files, scale-only matrices, zero skews, prose between markup tags, a function a script declares (function color(name)), // comments in SCSS, Sass and Less, and an angle written another way (24.0deg).
    • The README says what it does not check: contrast, which background a text sits on, text supplied by a script or a content system, line height, the one-word highlighter rule, shapes by content type, photos, and Markdown or MDX files.
    • An allowlist file (each entry with its reason) covers third-party marks. scripts/brand-check.test.mjs plants each case on its own line, in the source and in the built CSS, plus clean controls (a clean source file and the output of correct Tailwind 3 and Tailwind 4 builds), an allowlist, --warn and --error, and scripts/check.mjs runs it. The README documents every option and exit code.
  6. The supergraphics canon follows the Brand Book where it contradicted it, reading the roles and type relationships above instead of literals (src/supergraphics.canon.css; no comment added, one corrected):

    • Every orange-300 surface takes dark text (Color Contrast 918:2588): .sg-cta sets --mfb-link-on-orange, .sg-bg-orange --mfb-body-on-orange, .sg-cover--orange (and so its title) --mfb-heading-on-orange. Black on orange-300 is 9.2:1; the white it replaces was 2.3:1.
    • Text on light surfaces is black (Type Relationships 918:2990): .sg-bg-white, .sg-bg-gray and .sg-info-bar--on-light set --mfb-body-on-light instead of gray-900.
    • The light info bar's date is --mfb-heading-on-light (black) instead of orange-300 (2.3:1 on white). A bar without a variant has no background of its own, so its orange date vanished on the package's orange surfaces (1.0:1) and failed on white (2.3:1); placed directly in .sg-bg-white, .sg-bg-gray or one of the package's orange surfaces, its date is now black, and on purple surfaces and photos it stays orange-300 (selectors wrapped in :where(), so --on-dark and --on-light still win; checked in a headless render, nested surfaces included). Its small line loses its 75% opacity and takes a palette color that passes AA: --mfb-muted-on-light (gray-700, 5.1:1) on the light bar, --mfb-muted-on-dark (gray-400, 8.3:1 on purple-400; orange-300 at 75% was 4.2:1) on the dark bar.
    • .sg-headline reads --mfb-leading-h1 and --mfb-tracking-h1 (1 and none, where it had 1.05 and -0.03em), and its comment, which called -0.03em the book's heading tracking, now says the book has none. The class sets no size, and 1 is the book's line height for H1 to H4 and quotes only, so two new modifiers, .sg-headline--h5 and .sg-headline--h6, set the book's 1.1 for a headline set at those sizes. .sg-cover__title reads the h2 values the same way.
    • .sg-label is Regular with --mfb-leading-label (1.2) and --mfb-tracking-label (none), where it was Medium with 1.5px; .sg-body reads --mfb-leading-body (1.2, unchanged) and now sets --mfb-tracking-body (none), so it no longer inherits a parent's tracking.
    • Radii: no radius token is 6px or 40px, so the 6px corners of .sg-para-frame and .sg-para-accent and the 40px of .sg-cta stay, and the spec lists them with what to set.
    • The supergraphics prelude now also carries the color roles, line heights and letter spacing, and the build refuses a canon that reads a --mfb-* variable the prelude does not set.
    • A long-reading line height, lineHeight.body-long (1.7), declared from the website's article prose (.article-prose line-height), emitted as --mfb-leading-body-long and leading-body-long. The spec says the book's body line height is for body copy paired with a heading and this one is a website declaration for articles, flagged for review (open question 1). The build allows it as a named variant of the body level.

Earlier on this branch

  1. Font weights are exported: --mfb-weight-regular|medium|semibold (brand.css and the supergraphics prelude), --font-weight-* in theme.css (plus normal for 400), theme.extend.fontWeight in the Tailwind 3 preset (extending, so font-bold keeps working), and export const fontWeight. font-normal, font-medium and font-semibold restate Tailwind's own values; font-regular is new.
  2. Halftone colors are exported, the full-portrait highlight (#EEEEEE) is added, and their source is corrected to the Halftones slide (918:3730, text node 918:3737). They are not palette colors and get no Tailwind utilities. .sg-halftone-cutout reads the variables.
  3. The canon's type classes read variables (var(--mfb-font-heading|body), var(--mfb-weight-*)), so the Brand Book's system fallback, Arial (918:2375), applies. .sg-cover__title reads --mfb-size-h2 instead of the never-defined --mfb-font-h2. The supergraphics prelude carries the font families (not the deprecated mono), sizes and weights.
  4. A base highlighter, measured from the Brand Book (918:2874): a thin tapered line at the baseline, beneath the glyphs, under 80% of the word, centered, orange-300 by default and white inside the package's orange surfaces; the word keeps its heading's color. Geometry tokens highlighter-coverage (prose), highlighter-aspect, highlighter-offset and highlighter-shape (measured) join the geometry gate.
  5. Two canon geometry fixes from --sg-angle-base and --sg-aspect only: .sg-para-pattern rows inset themselves so their first shape is no longer clipped, and .sg-para-frame scales its photo by 1 + tan(angle) / --sg-aspect, the smallest scale that covers.
  6. Links in the spec follow the contrast rule: a dark brand color on light backgrounds (purple-300, with an orange-300 underline as this package's recommendation), orange-300 or white on dark.
  7. brand-spec.md ships in the package (files and exports), so projects and their AI tools read the rules at the version they pin.
  8. README says how the package is built, documents every variable and utility, the highlighter, --sg-aspect on every frame, the brand guard, and the upgrade steps; values in it are read from tokens.json. Its prose follows AGENTS.md.
  9. Deprecations stay compatible: the brand gradient, .sg-photo-zone and the mono font are marked deprecated with what to use instead.

Website-declared tokens

All from myfirstbitcoin.org at 6368b2a (main), each cited in tokens.json as myfirstbitcoin.org@6368b2a, <CSS name>; the Source column gives the CSS name. Each token's note gives the number of uses, counted with git grep in the site's source at that commit.

Token Value Source (CSS name) Uses
spaceScale.1 to .32 4, 8, 12, 16, 24, 32, 48, 64, 96, 128px (keyed by Tailwind step) --s-1 to --s-10 declared there, read nowhere (see open questions)
space.gutter clamp(20px, 4vw, 64px) --container-px the .container padding; 50 lines set container
space.section clamp(64px, 9vw, 128px) .section padding 40 lines set section (one page overrides it)
space.section-tight clamp(48px, 6vw, 96px) .section--tight padding 3
space.header-gap clamp(32px, 4vw, 56px) margin-bottom (inline, section headers) 13
space.block-end clamp(32px, 4vw, 48px) margin-top (inline, section calls to action) 11
radius.sm, .md, .lg, .pill 4px, 8px, 12px, 999px --r-sm, --r-md, --r-lg, --r-pill 2, 20, 48, 9
shadow.media 0 20px 60px -20px, purple-400 at 25% box-shadow (inline, media frames) 6
shadow.media-control 0 12px 32px -8px, black at 40% box-shadow (inline, video play buttons) 7
shadow.overlay 0 40px 80px -20px, black at 50% .mfb-overlay__dialog box-shadow 3
easing.out, .in-out cubic-bezier(0.16, 1, 0.3, 1), cubic-bezier(0.65, 0, 0.35, 1) --ease-out, --ease-in-out 82, 7
duration.fast, .med 180ms, 320ms --dur-fast, --dur-med 77, 5
duration.slide 520ms transition (inline, carousel tracks) 6
duration.reveal 700ms .reveal transition 105 lines set reveal
container.page 1280px (80rem in Tailwind) --container 50 lines set container
container.wide 1440px max-width (inline, wide frames) 6
container.measure 720px article width (the article width) also 19 section headers
fontSizeFluid.h1 to .h6, .quote, .label clamp(44px, 6vw, 70px) ... (caps are the book's sizes) --fs-h1 to --fs-h6, --fs-quote, --fs-label h1 2, h2 8, h3 30, label 4; h4 to h6 and quote declared, not used yet
zIndex.header, .dropdown, .overlay 100, 200, 1000 .mfb-header z-index, .mfb-dropdown z-index, .mfb-overlay z-index 1, 1, 8
mediaRatio.video, .photo, .portrait, .square 16/9, 4/3, 4/5, 1/1 .video-tile aspect-ratio; aspect-ratio inline on gallery images, educator photos and supporter photos 7, 4, 3, 3
ui.touch-target 44px width and height (inline, icon buttons) 9 elements
ui.link-underline-offset 3px .article-prose a text-underline-offset 2
lineHeight.body-long 1.7 .article-prose line-height (the page default, the body line height, is the same) 3 lines set 1.7 (article prose, page body, footer); under review
colorRole.muted-on-light gray-700 --gray-700 (inline text color) 57
colorRole.border-on-light gray-200 --gray-200 (inline border color) 53
colorRole.link-on-dark orange-300 --accent (the color of every link), which the site sets to var(--orange-300) one global rule

Package recommendations (a decision each)

Neither the Brand Book nor the website gives these; each is "source": "declared" (or, for the logo, a selection between two book variants) and says so in its note.

  1. Links on light backgrounds: purple-300 text (link-on-light, 11.5:1 on white). The website's orange link text is 2.3:1 and breaks 918:2588.
  2. The link underline on light backgrounds: orange-300 (link-underline-on-light), on white to gray-300; on gray-400 and gray-500, where orange cannot be seen, the underline is in the link's own color. Links on light backgrounds always keep their underline (purple-300 is only 1.8:1 from black text).
  3. Muted text on dark backgrounds: gray-400 (muted-on-dark, 6.4:1 on purple-300). The website uses white at 72% (35 lines), which is translucent text and is not imported; gray-400 is the nearest palette color to it, and the site never uses gray-400 as text on dark.
  4. The logo on orange: black (logo-on-orange). The Brand Book shows both a black (918:2978) and a white (918:2980) logo on orange-300; black is the one that keeps to the contrast rule.

What a project sees when it raises its pin

The README's "Upgrading from v1.3.0" section says what to do. In short:

  • The highlighter. A local .highlighter band draws together with the new line until it is deleted. The span is an inline-block sized from its own width, so a local display or text-align override breaks it. The brand guard reports a local band as hand-rolled-highlighter.
  • Parallelogram frames and patterns. The photo scale is 1 + tan(13°) / --sg-aspect (1.154 at the default 1.5); pattern rows gain a left inset of about 0.115 x the row height, so local padding added for the slant should go. The brand guard reports such padding when it is computed from --sg-angle-*.
  • The supergraphics prelude also sets --mfb-font-*, --mfb-size-*, --mfb-weight-* and --mfb-halftone-* in :root.
  • Tailwind 4 projects that reset a theme namespace (--font-weight-*: initial, --radius-*: initial) must do it before importing theme.css.
  • A test that snapshots the hex-valued --mfb-* variables of brand.css must refresh its snapshot (the three halftone variables are new); the other new variables carry no hex.
  • Headings and text on light backgrounds are black in the spec, and text on orange is black, buttons included. No variable changed value: projects change their own colors when they choose.
  • The supergraphics classes change how they look. .sg-cta, .sg-bg-orange and .sg-cover--orange turn from white text to black; .sg-bg-white, .sg-bg-gray and .sg-info-bar--on-light from gray-900 to black; the light info bar's date from orange to black, and its date line loses its 75% opacity (gray-700 on light, gray-400 on dark). An info bar without a variant placed directly in a light or orange surface of the package gets a black date; anywhere else it keeps its orange date. A local white color set on those classes brings the old contrast back and should go.
  • Headlines and labels. .sg-headline and .sg-cover__title set line height 1 and no tracking instead of 1.05 and -0.03em, so multi-line headlines sit a little closer and every headline is a little wider (0.03em per letter): a headline that only just fitted its box may wrap. A headline set at the H5 or H6 size adds .sg-headline--h5 or .sg-headline--h6 for the book's 1.1. .sg-label turns from Medium with 1.5px tracking to Regular with none, at line height 1.2. .sg-body stops inheriting a parent's tracking.
  • A new utility, leading-body-long (1.7), for article text; nothing uses it until a page adopts it.
  • The brand guard warns about more: gray-900 text, translucent text and letter spacing. Warnings do not fail a build; --error makes them fail.
  • Nothing else changes by itself. The new utilities and variables are additions. Re-checked with the generated output: compiling the class names of ten consumer projects against the old and new theme gives 0 utilities changed, added or removed and 0 theme values changed, in Tailwind 4.2.2, 4.3.3 and 3.4.19.

Per-project upgrade notes for My First Bitcoin's own projects are tracked internally.

How it was checked

  • node scripts/check.mjs passes on every commit; it also runs the brand guard's test.
  • The build's gate refused, with one message each, a role aliased to {color.teal-500}, a fluid h1 capped at 72px, an unmapped group, an ungated geometry token, a line height for a level that does not exist, a spacing key equal to a container key, and a medium weight of 550.
  • The new utilities compile in Tailwind 4.2.2, 4.3.3 and 3.4.19 (text-heading-on-light/50 gets its opacity; shadow-mfb-media keeps --tw-shadow-color); rounded-md, ease-out, duration-150 and max-w-xl are unchanged.
  • Every website value in the table was read at its line at 6368b2a on the site's main branch, and the CSS name cited for it was taken from that line; every count was taken with git grep in the site's source at that commit. The exact lines are kept outside this repository by the deciders named in CONTRIBUTING. base-h-ratio's quote was read from 918:4219 through the Figma MCP.
  • import.meta.resolve('@myfirstbitcoin/design/brand-check.mjs') resolves in a scratch consumer.
  • brand-check.mjs against two of My First Bitcoin's sites (built with v1.3.0): the first reports 3 problems (a local highlighter band that this release says to delete, a local pattern inset computed from the angle, and rounded-[4px]) and 8 warnings; the second reports 114 problems (105 raw values that have a token, four slant computations from the angle and a local copy of the frame's counter-skew, 3 other gradients: a photo scrim, a dashed line and a hard purple and white split, and a local highlighter band) and 369 warnings, 324 of them spacing literals on the scale. The false positives found in review (a map SVG's editor metadata and scale-only matrix, a place name in content data, surface names in content props, @font-face descriptors, var(--font-sans), skewX(0deg), Tailwind's .uppercase from a comment and its --default-mono-font-family) are gone. A scratch page that plants ten known website drifts gets all ten reported, seven as problems and three as warnings (orange text, a purple heading, typed capitals). A second review planted ten more (SVG attribute transforms, React style objects with camelCase properties, named and #RRGGBBAA colors, angle literals in rotate() and skewY(), a hand-rolled highlighter pseudo-element, an override of --sg-angle-base in the built CSS) and found three reported, and found that the built-CSS rules failed correct Tailwind 3 and 4 builds; each case is now a line in the test, which fails on the earlier guard. Re-run after that change: the first site is unchanged; the second reports 126 problems, the 12 new ones translucent orange or purple fills (scrims and tints made with color-mix(), see open question 13). Scratch builds of a Tailwind 3 and a Tailwind 4 consumer with this branch: the built CSS passes in both; the Tailwind 3 one gains 30 tinted panels (bg-orange-200/10 and similar). Both sites' built CSS passes (the angle is 13deg, every --color-* is a palette color). A third review found more forms that got past the guard (uppercase written UPPERCASE, with a quoted key, or as a lone string in clsx(), a ternary or class:list; the font shorthand and --font-* theme variables; SVG text rotate, gradient elements and clipPath polygons; an inset shadow without blur, a thick orange underline and an orange mark; filter: opacity() and translucent borders) and prose it failed (a block number #840000, "a color (any one)", a script's function color(name), const id = '#feed', a // comment in SCSS), plus hard-stop gradients reported as gradients and a built 24.0deg reported as another angle. Each is now a planted or clean line in the test: on the previous guard the test reports 25 planted lines missed and 8 clean lines reported. Re-run on both sites after that change: the first still reports 3 problems, and 22 warnings (8 before; the new ones are 5 gray-900 text, 7 translucent white text, 2 letter spacing); the second reports 127 problems (126 before: the dashed line and the purple and white split are hard stops and no longer count as gradients, and three translucent orange or purple borders are new) and 554 warnings (369 before; the new ones are 54 gray-900 text, 36 translucent text, 95 letter spacing, among them the white-at-85% eyebrow with 0.12em tracking on the orange banner). Both sites' built CSS still passes. The scratch Tailwind 3 and 4 consumer builds were not rebuilt after this change; the test's Tailwind 3 and 4 controls pass.
  • No added comment in the canon (four existing comment lines are reworded: one named an internal path, two used the internal shorthand for My First Bitcoin, and one called -0.03em the book's heading tracking); no em-dash in any added line.
  • No added line names a file, a directory, a component or a line of the website's private source repository: checked with grep over the diff against master. The only Astro and TSX file names left in added lines are the file types the brand guard reads, its test fixtures and an allowlist example, the last two under a generic src/ui path. Every value in tokens.json is unchanged by the citation change, checked token by token. With an old-style citation planted in tokens.json, the spec generator refuses to write.
  • The canon's info bar and headline changes were rendered headless and read back with getComputedStyle: a bare bar's date is black in an orange cover and in .sg-bg-white, orange in a purple cover (also when that cover sits inside .sg-bg-white), and --on-dark keeps its orange date inside an orange cover; .sg-headline--h5 at 36px gives a 39.6px line; .sg-body under a parent with 5px tracking has none.
  • The build's new gate refused a canon line reading an undefined --mfb-* variable, before writing any file. Contrast ratios for the canon changes were computed from the palette hex values (WCAG relative luminance). The website's line-height counts were taken with git grep at 6368b2a: 3 lines at 1.7 and 38 at 1.55. The published files name no internal project, person or place: the older tokens.json notes, the supergraphics.css header and those canon comments were reworded here, and the generated files and the tokens.json description write My First Bitcoin in full.

Open questions

  1. Line heights where the Brand Book disagrees with itself. Its type specimen (918:2323) states 1 for H3 and 1.2 for body, and the tokens follow the specimen for every level. Type Relationships (918:2990) draws H3 at 1.1 (918:3035: four lines of 48px in 212px) and body copy under a heading at 1.22 (918:2994: four lines of 18px in 88px). One rule should decide both: the specimen's stated numbers (as now), or the Type Relationships drawing (H3 1.1, body 1.22). Separately, the website uses 1.7 for long articles (.article-prose line-height), shipped as a separate declared token, lineHeight.body-long, and 1.55 in section paragraphs (38 lines), which is not imported. The deciders named in CONTRIBUTING.md to confirm.
  2. Label and body on small screens. Only headings, quote and label are fluid, and body stays at 18px, so below about 1125px wide the fluid label (16px floor) is smaller than body text, the reverse of the book's order (label 22px above body 18px). Either (a) declare a fluid body size from the website (--fs-body, clamp(15px, 1.2vw, 18px), read on 65 lines; its floor holds up to a 1250px viewport, so body text would be below the book's 18px on most laptops), or (b) raise the label clamp's floor to 18px, a package declaration. The spec says to use the fixed label size next to body text on small screens until then.
  3. The space after a heading on phones. The book's 40px is static and is wider than the fluid h4 to h6 on a phone. The website's heading-to-content gaps are fluid, for example clamp(24px, 3vw, 40px) (7 lines in 7 blocks). Keep 40px only, or add a fluid companion that reaches it.
  4. Values with no single website value, so no token yet: breakpoints (13 max-width values: 720px 7 times, 900px 5, 600px 3, then 860, 768, 760 and 520px twice each and five single values, plus a JavaScript threshold at 760px; 600, 720 and 900px were proposed and are held back until someone picks), card padding (seven clamps), card shadow (two exact uses, five variants), a medium content width (1040, 960, 920 or 820px), a small text size (14px or 13px), the overlay scrim (six values). The site's --dur-slow (600ms) is never used; the practised slow step, 700ms, is duration.reveal.
  5. The spacing scale. The site declares --s-1 to --s-10 but no line reads them; the values equal Tailwind's steps. The tokens stay, and the brand guard only warns on spacing literals; drop the scale if a read value is required.
  6. Status colors. A forms project needs error and success colors (152 off-palette utilities on one form site). Neither the Brand Book nor the palette has them. Add status roles (a Brand Book change first), or keep them per project in the allowlist with a reason, as the README now says.
  7. Focus ring on light backgrounds. The site's orange-300 ring is 2.3:1 on white, under the 3:1 non-text contrast guideline. No role is added for it; a dark ring (purple-300 is 11.5:1) would meet it.
  8. What the canon still does differently from the book. Text colors, headline and label type are corrected here (item 6). Still listed in the spec, with what to set meanwhile: 1px tracking and a 40px radius in .sg-cta; 18px in .sg-label (the book's label is 22px); the info bar's SemiBold 32px date (the book's 32px level is Medium) and its 15px and 17px lines at line height 1.3; a 6px radius on .sg-para-frame and .sg-para-accent (no radius token has either value); the pattern's 10px gap and 1.25 and 1.55 steps; and .sg-logo and .sg-cover__logo at 40px tall (logo-icon-h), below the book's 63px minimum, at fixed 40px and 44px offsets rather than half the logo height. Each can be fixed in one later release, or one at a time.
  9. The spotlight's second cut. The book draws it at about 21.75 degrees (21.0 to 22.0, never 24); angle-alt stays at its declared 24 degrees until that earlier decision is revisited.
  10. Highlighter color on grey and coverage in Brand in Use. The slide draws a white line on a mid grey (918:2893, close to gray-500) and Brand in Use an orange one on grey twice. The package keeps orange on white to gray-300 (sg-bg-gray is gray-200) and uses white on gray-400 and gray-500, where white stands out more. Brand in Use covers 70% to 88% of the word against the slide's "up to 80%"; the slide is treated as normative.
  11. Highlighter offset across the slide. 0.054em below the baseline on the two "Education" examples, 0.036em on "Unconference" and about 0.0375em on "convince"; the token follows the cited node.
  12. Token types. highlighter-shape (string), mediaRatio (string), the shadows' opacity field and percent dimensions are not strict DTCG; tools that validate DTCG may warn.
  13. Tints and scrims of orange and purple. The brand guard reports an orange or purple fill that is not solid (an alpha modifier such as bg-orange-200/10, color-mix() with transparent, or opacity beside the fill) as translucent-shape, following the book's solid fills. Two of My First Bitcoin's projects use such tints for panels and hover states and such scrims over photos. If they are wanted, the book (or a declared token) should say which; until then a project can pass --warn translucent-shape or list them in its allowlist with the reason.

Not in this pull request

Brand Book gaps to settle in Figma first: a link style, status colors, a focus style, UI radii and shadows, breakpoints, and web layout. The website values in this pull request stand in for them, each with its source, until the book says otherwise; a book value then replaces the website value.

Package follow-ups. The parallelogram frame still leans out of its box by tan(13°) x height / 2. --mfb-halftone-highlight-full is exported but no class uses it yet. Folding line height into text-h1 waits for a major release. The deprecated gradient and mono stay until a major release. The internal copy of the canon (see CONTRIBUTING) is brought into line after merging. An info bar's orange top line still cannot be seen on an orange surface. The brand guard does not read Markdown or MDX files; a project's MDX pages hold no brand values today.

Older wording on master, left for a separate clean-up: CONTRIBUTING.md's release and brand-change sections name the people who decide and internal routines, its canon section names the internal shared package, and two comments in scripts/build-design-package.mjs name it too. Commit metadata: every commit on this branch has an internal author identity, and three earlier commit messages carry what the files no longer do: the source rule commit names the people who decide, as CONTRIBUTING on master already does; the first review-fixes commit cites a line of the website's style sheet and the directory the counts were taken over; and the tokens commit gives the old file and line citation format. The last commit replaced those citations in the files, not in the history. Master already holds commits by the same author, squash-merged and rebase-merged. The pull request's commit list shows them whatever the merge method; a squash merge with an edited message keeps them out of master's history, and rewriting the author and those messages before the first push would keep them out of the pull request as well. Whoever merges decides.

🤖 Generated with Claude Code

MFB Ops Agent and others added 21 commits October 6, 2026 04:21
tokens.json has carried fontWeight (regular 400, medium 500, semibold 600) since
v1.0.0, but no output exposed it, so consumers hand-wrote their own aliases.

- brand.css and the supergraphics prelude: --mfb-weight-regular/-medium/-semibold
- theme.css (Tailwind 4): --font-weight-regular/-medium/-semibold, plus
  --font-weight-normal with the regular value (Tailwind's own name for 400)
- tailwind.js (Tailwind 3): theme.extend.fontWeight with the same names; it
  extends Tailwind's scale, so font-normal and font-bold keep working
- index.js: export fontWeight

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Brand Book Halftones slide (918:3730, text 918:3737) gives two
highlight caps: #F7E6FF for cutout portraits and #EEEEEE for full portraits,
both with shadow #45265B. tokens.json had the cutout pair only.

- tokens.json: geometry.halftone-highlight-full #EEEEEE (prose, figma:918:3730)
- brand.css and the supergraphics prelude: --mfb-halftone-highlight,
  --mfb-halftone-highlight-full, --mfb-halftone-shadow (kept out of the
  palette and the Tailwind colour utilities, as the tokens say)
- canon: .sg-halftone-cutout reads the variables instead of hex literals
- brand-spec Rule 4: the full-portrait highlight comes from the token

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
.sg-headline, .sg-body, .sg-label, .sg-cta, .sg-info-bar (and its date and
location) and .sg-cover__title wrote 'IBM Plex Sans', sans-serif and numeric
weights by hand. They now read var(--mfb-font-heading|body) and
var(--mfb-weight-*), so the Brand Book's system fallback, Arial (918:2375),
applies and consumers no longer need local font-family overrides.

.sg-cover__title read var(--mfb-font-h2, 58px), a variable that never
existed, so it always fell back to the literal. It now reads
var(--mfb-size-h2) (58px, the same rendered size).

The supergraphics prelude now carries the non-deprecated font families and
the font sizes, so the file stays standalone. Mono is left out of it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Brand Book Highlighter slide (918:2874) says: one word per heading,
"covering up to 80% of the word length. Do not alter its appearance". Its
vectors draw a thin tapered line at the baseline, behind the glyphs, starting
a little in from the first letter. The package had no base class, and the
written rule (a linear-gradient band) painted a band about twice as thick,
lower and across the whole word.

- tokens.json, geometry: highlighter-coverage 80% (prose, figma:918:2874);
  highlighter-aspect 26.6, highlighter-offset 0.03em and highlighter-shape
  (measured from the vectors 918:2883, 918:2887, 918:2893, 918:2898 and the
  Brand in Use copies; node ids and ranges in each note). The $verification
  text defines the new sourceKind "measured".
- build: the four values join the geometry gate, so the build refuses when
  the canon's --sg-highlighter-* declarations disagree with tokens.json,
  and they are emitted into brand.css and the supergraphics prelude.
- canon: a base .highlighter (inline-block) whose ::after is an inline-block
  line sitting on the baseline: width 80% of the word, centred by a negative
  margin, thickness from aspect-ratio (so it scales with the word, as in the
  book), shape from clip-path, moved down by the measured offset, painted
  behind the text. Orange-300 by default; white inside the existing orange
  contexts (.sg-spotlight--orange, .sg-bg-orange, which keep their white
  text), .sg-cover--orange and .sg-bg-gray. --sg-highlighter-color overrides
  it on other orange or grey surfaces.
- brand-spec Rule 2 and checklist item 4 describe the measured construction.
- verify-figma.mjs lists measured tokens in its coverage census.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both corrections derive only from --sg-angle-base and --sg-aspect; no new
numbers.

- .sg-para-pattern: each .sg-para is skewed about its centre, so the first
  shape of a row reaches tan(angle) x row height / 2 past the row's left edge,
  where the pattern's overflow: hidden cut its corner off. Rows now take that
  inset as padding-left. Row height moves to --sg-row-h so the progressive
  rows (x1.25, x1.55) get their own inset.
- .sg-para-frame: the counter-skewed photo used a fixed scale(1.12), which
  covers the parallelogram only when the frame is at least 1.92 times as wide
  as it is tall; at the default aspect 1.5, and on square or portrait frames,
  the corners showed through. The scale is now 1 + tan(angle) / --sg-aspect,
  the smallest that covers the frame, so --sg-aspect must be the frame's real
  width over height (as for the spotlight, book and halftone frames).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The colour quick reference said "Links: orange-300". The Brand Book's Colour
contrast slide (918:2588) says light text goes on dark colours and dark text
on light colours, and orange is a light colour. Links on light backgrounds
now use purple-300 with an orange-300 underline; on dark backgrounds,
orange-300 or white.

Heading colours are not changed here (see the pull request description).

The CSS Variables paragraph now lists the weight, halftone and highlighter
variables.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brand-spec.md was public in the repository but not installed, so consumer
projects and their AI tools had to fetch the rules from GitHub, at whatever
version master happened to be. It is now in package.json's files and in the
exports map as ./brand-spec.md, so it arrives in node_modules at the pinned
version.

CONTRIBUTING's table and install list, the spec generator's header, the
spec's Section 5 and the workflow's published-file lists (version-check and
release warnings) say so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The README said the package was generated "Figma -> sync-brand.js". Since
the build moved into this repository (the point of the closed PR #4), it is
scripts/build-design-package.mjs here, from tokens.json. The README now says
so, and:

- documents the weight, halftone and highlighter variables and the
  geometry list, read from tokens.json (angles, weights, coverage) rather
  than written by hand
- explains the base highlighter and that a consumer's own .highlighter band
  must go when it upgrades, or the two draw on top of each other
- states the --sg-aspect rule: spotlight, book, halftone and parallelogram
  frames need it set to the frame's real width over height
- marks as deprecated, without removing anything, the brand gradient
  (--mfb-gradient-brand, --mfb-gradient, bg-brand-gradient, .sg-bg-gradient:
  use a solid palette fill), .sg-photo-zone and the mono font (use the brand
  sans, whose fallback is Arial per Brand Book 918:2375)
- points to brand-spec.md in node_modules

This is a release pull request, so the README is reworded to the public
conventions: no em-dash, "My First Bitcoin" in full. CONTRIBUTING says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Raise the version so that merging this pull request releases v1.4.0. All
changes since v1.3.0 are additive or corrective; nothing a consumer uses
today is removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…re taken

halftone-highlight and halftone-shadow cited figma:918:4126, the
Parallelogram Frames slide, which has no halftone text. Both now cite the
Halftones slide 918:3730 (text node 918:3737), as halftone-highlight-full
does, verified 2026-10-06.

The quotes in the token notes are now verbatim: the Highlighter sentence
goes on past "Do not alter its appearance", and the Halftones text is two
lines, not one sentence.

The measured source kind said the values were read off "by a person"; they
were read through the Figma MCP by an AI session. $verification and the
Figma check's coverage line now say so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…color

- .highlighter sets text-align: left and text-indent: 0. The line takes
  10% less room than the word, so an inherited center or right alignment
  moved the word right by 5% or 10% of its width and ate the space after it.
- The white line no longer applies on .sg-bg-gray: that is gray-200, and a
  white line on it cannot be seen. The Brand Book's white-on-grey example is
  a mid grey (close to gray-500); README and spec say to set
  --sg-highlighter-color there.
- The white line now applies on every orange-300 surface of the package:
  .sg-spotlight-frame--orange, .sg-book-frame--orange and
  .sg-halftone-cutout--orange-bg join the list.
- The orange variants no longer turn the word itself white. On the
  Highlighter slide the word stays dark on orange and only the line is
  white ("Do not alter its appearance"; Color Contrast 918:2588).
- highlighter-offset is 0.054em: the distance from the baseline to the
  center of the line's box, which is what the CSS positions, on the cited
  node 918:2883 (checked against a raster of the slide). 0.03em measured
  to the shape's average centerline, so the line sat about 1px high at 48px.
- Spec Rule 2 wording: the twelve copies have small variants, thickness is
  proportional to the word's width, and the quote is the full sentence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- README gains "Upgrading from v1.3.0": delete a local .highlighter band and
  any display or text-align override on it; remove local padding added
  around .sg-para-pattern for the slant (rows now inset themselves); set
  --sg-aspect on parallelogram frames, whose photo scale now depends on it;
  the :root prelude of supergraphics.css now also sets the font, size,
  weight and halftone variables; .sg-cover__title reads --mfb-size-h2; and
  Tailwind 4 projects that reset --font-weight-* must do it before
  importing theme.css, or the package's weights go too.
- The links rule cites the Color Contrast slide's evidence (every orange is
  paired with dark text) and marks the orange-300 underline as this
  package's recommendation, not a Brand Book rule.
- New prose uses US spelling, as the Brand Book and the rest of the spec do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… is silent)

- Where the Brand Book defines a value, its value is the token, cited by
  node.
- Where the book is silent, the value the live website myfirstbitcoin.org
  uses may become the token, declared with its exact origin on the site's
  main branch: sourceKind "declared", source
  "myfirstbitcoin.org@<commit>:<file>:<line>". The site was built following
  the Brand Book, and its CSS is public.
- A website value that contradicts the Brand Book is never imported; where
  the site has several near-duplicates, no token is made until someone
  picks one; Quentin or Patrick decides, as for any change here.
- The source kinds are restated as tokens.json's $verification defines
  them, with "measured" covering a node's rendered fill as well as its
  geometry, and "declared" covering a website value or a value this
  package recommends.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Under the source rule (CONTRIBUTING), tokens.json gains what the Brand Book
defines and the package did not carry, and the values the book is silent on
as the live website (main branch at 6368b2a) uses them, each with its
source. No existing value, variable, utility or export changes.

From the Brand Book (figma provenance, prose or measured):
- lineHeight and letterSpacing per type level (specimen 918:2323: 1 for
  h1 to h4 and quote, 1.1 for h5 and h6, 1.2 for body and label; no
  tracking on any brand text style).
- logo: minimum 120px wide (63px tall) on screen, 25mm in print, aspect
  1.904, clear space and corner margin 0.5 x the logo height (918:2681,
  918:2714).
- colorRole: heading, body, link, muted, border, highlighter and logo
  colors on light, dark and orange backgrounds, as aliases of palette
  colors only. Headings and body on light: black (918:2990); on dark:
  white; on orange: black, CTAs included (918:4183 to 918:4185). Links on
  light: purple-300 with an orange-300 underline, declared as this
  package's recommendation; on dark: orange-300, the website's.
- shapeTone: a shape one step lighter than its base (918:3604).
- CMYK for all 17 colors, and geometry: para-edge-ratio 0.572,
  pattern-row-step 1.2, pattern-row-gap 0, halftone-scale 2.5.
- Provenance: angle-base is measured (13.00deg on 918:4200, the logo icon
  and five photo frames); base-h-ratio cites the visible 918:4219;
  logo-icon-h (40px) is declared, as the book states no pixel value.
- Color descriptions the book contradicts are corrected: purple-300 is the
  primary color for surfaces and shapes, not a heading color; text on
  light backgrounds is black, not gray-900; orange is never text on light.

Declared from the website (myfirstbitcoin.org@6368b2a:<file>:<line>):
spacing scale and semantic spacing (gutter, section, header gap, block
end), radius, three shadows tinted with palette colors, easing and
durations, container widths, fluid type floors and slopes (the caps are
the book's sizes and the build refuses any other), breakpoints, layers,
media ratios, the touch target and the link underline offset. Website
values that contradict the book (10 degree slant, the hero cut, the thick
animated highlighter, duotone, IBM Plex Mono, off-palette tiles,
uppercase labels, orange text on light, white text on orange) are not
imported.

Build: one GROUP_MAP says where each new group is emitted (brand.css as
--mfb-<family>-<key>; theme.css and tailwind.js with mfb- prefixed keys,
type levels or <element>-on-<surface> roles; index.js named exports). The
build refuses an unmapped group, a geometry token that is neither gated
against the canon nor listed as token-only, a key equal to a Tailwind 3 or
4 default, overlapping keys, a role that is not a palette alias, a fluid
size whose cap is not the book's, and a hex in a new brand.css variable.
Checked against the class names of ten consumer projects: 0 utilities
or theme values changed in Tailwind 4.2.2, 4.3.3 and 3.4.19.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ack text on light

brand-spec.md gains, each value filled from tokens.json with its source (a
Brand Book node, or the website file and line it was declared from):
- Color roles by background, shape tones, and CMYK in the color table.
- Type relationships: line height, letter spacing and the fluid size per
  level, weights per role, 40px between a heading and its text and 48px
  between a quote and its label (measured on 918:2990 and 918:3005).
- Logo: minimum size, clear space, corner margin and color by background.
- Layout and spacing, radius and shadow, motion, breakpoints, layers,
  media ratios, and the website values that are not imported because they
  contradict the Brand Book.

Corrections:
- Headings and text on light backgrounds are black (Type Relationships
  918:2990), not purple-300 headings and gray-900 text; text on orange is
  black, buttons included. The quick reference says which supergraphics
  classes still set white text on orange.
- Logo files: the purple logo on greys up to gray-500, the black one on
  orange; clear space is half the logo height, not the height of the B.
- Halftone cutouts also sit on purple-200; the Canva scale is a token.
- Rule 7 states the measured pattern step (1.2) and row gap (0), and that
  .sg-para-pattern does not follow them yet.
- Slide labels: Parallelogram 918:4189, Patterns 918:4212, Frames
  918:4126, Book Panel 918:3757, Spotlights and Books in Use 918:3716.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A zero-dependency Node script in the package's files (not a package.json
script), which a project runs after its build:

  node node_modules/@myfirstbitcoin/design/brand-check.mjs [--src src] [--dist dist]

It generalises the guards two of My First Bitcoin's sites already ran, and
reads every value it compares with from the tokens.json next to it. It
fails on color literals and named colors, font families other than the
brand variables, skew, rotate and angle literals that are not quarter
turns, hand-rolled clip-path shapes, uppercase, gradients (the deprecated
brand gradient included) and smooth color filters, Tailwind color
utilities outside the palette and the roles, weights above 600,
translucent brand shapes, and raw values that have a token (a spacing,
radius, duration, easing, container width, font size, font weight or
shadow literal equal to a token value, with the token to use). In the built
CSS it fails when --sg-angle-base is missing or not the token value, when a
--color-* is not a palette color or a role, and on uppercase or the mono
family. An allowlist (each entry with its reason) covers third-party marks;
--warn reports a rule without failing, for a step-by-step adoption.

scripts/brand-check.test.mjs plants one violation per rule on known lines,
a clean file that must pass, an allowlist and --warn; scripts/check.mjs
runs it. README documents the guard; CONTRIBUTING lists it with the
published files; the workflow counts it as a published file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ences listed

Sources (tokens.json, under the source rule):
- muted-on-dark is this package's recommendation (source "declared"): the
  website's value at the cited line is white at 72%, which is not imported.
- link-on-dark names global.css:47, where the link color resolves, and its
  description is limited to purple-300 and purple-400; links on purple-200
  and the dark greys are white and underlined.
- mediaRatio.square cites a line that carries 1 / 1; container.measure cites
  the article width. Every website-declared note gives its use count, all
  counted the same way (git grep over src at 6368b2a); the spacing scale's
  notes say the site declares it but never reads it.
- The breakpoint tokens are held back: the site has 13 max-width values and
  no convention, so under rule 4 none is a token until one is picked.
- base-h-ratio quotes 918:4219 word for word. Surfaces are named exactly for
  heading-on-dark, the highlighter roles and the link underline; logo-on-orange
  says the Brand Book also shows white.
- Two older notes no longer name a person or an internal project, and the
  supergraphics.css header and a canon comment no longer name internal
  places; published comments and the tokens.json description write My
  First Bitcoin in full. CONTRIBUTING names roles, not people, in the new
  source rule.

Build: font weights that Tailwind already names must keep Tailwind's value,
and the README says so. README: the brand guard's rules, its default
warnings and what it does not check; Tailwind 4 notes for code blocks and
shadows; text on orange; no "typed --sg-aspect". package.json exports
brand-check.mjs.

Spec: a table of the supergraphics classes that differ from the Brand Book
and what to set instead; links on light always underlined; label size
against body text on small screens; breakpoints not declared yet.

brand-check.mjs: fewer false positives (@font-face, family names in data,
palette words in script data, Inkscape metadata, scale-only matrices, zero
skew, var(--font-sans), Tailwind utility definitions and its
--default-mono-font-family in built CSS) and more drift caught (SVG
rotations, blend modes, arbitrary named colors, translucent sg-* via
opacity-[...] or inline style, small-cap features, redefined package
variables, built angle overrides, slant geometry computed from the angle,
hand-rolled highlighter bands and bars). Spacing literals, orange text,
purple headings and typed capitals warn by default; --error makes them
fail. The test plants a line for each.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ok type relationships

The supergraphics canon now reads the color roles and the type relationships:
- .sg-cta, .sg-bg-orange and .sg-cover--orange set black text on orange-300 (9.2:1; white was 2.3:1)
- .sg-bg-white, .sg-bg-gray and .sg-info-bar--on-light set black instead of gray-900
- the light info bar's date is black instead of orange-300; the date's small line loses its
  75% opacity and takes gray-700 on light and gray-400 on dark
- .sg-headline and .sg-cover__title: line height 1 and no tracking (were 1.05 and -0.03em);
  the comment that called -0.03em book tracking is corrected
- .sg-label: Regular, line height 1.2, no tracking (was Medium, 1.5px); .sg-body reads the token

The 6px radius on .sg-para-frame and .sg-para-accent and the 40px radius on .sg-cta have no
radius token of that value, so they stay and the spec lists them.

New declared token lineHeight.body-long (1.7), from the website's article prose, for long
reading only and flagged for review; the Brand Book's body line height is for body copy next
to a heading. The build allows it as a level variant and refuses a canon that reads a variable
the supergraphics prelude does not set; the prelude now carries the roles, line heights and
letter spacing. README upgrade notes and the spec list every visible change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… Tailwind builds

Source rules now also report:
- angles in SVG patternTransform and gradientTransform attributes, unitless
  rotate and skew props in script code (rotate: -15), and rotate-x/y/z classes
- named colors in custom properties, canvas fillStyle and style objects
  (a CSSProperties object, or a second property on the same line)
- hex colors inside arbitrary utilities (after an underscore) and in values
  continued on the next line, which were taken for id selectors
- translucent orange or purple fills: opacity or fill-opacity beside the
  fill, alpha modifiers such as bg-orange-200/10, color-mix with transparent
- hand-rolled highlighters drawn as orange pseudo-elements sized in em or %,
  put behind the text or named like a highlight, inset orange shadows, and
  after:/before: orange utilities

The built CSS is now read declaration by declaration, nesting included:
every declaration of --sg-angle-base and --sg-angle-alt must have the token
value (rules, @Property initial-value, style attributes in built HTML);
uppercase is reported outside utility class definitions, whatever variant or
important marker Tailwind 3 or 4 gives them, since the source check reports
where a class is used; the mono rule reads only font-family declarations, so
Tailwind 4's --default-mono-font-family and code element base styles pass.
Built findings carry line numbers.

The test plants each case on its own line, in the source and the built CSS,
and keeps Tailwind 3 and Tailwind 4 build output as clean controls. The README
documents every option and exit code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ard that sees more and reads prose as prose

Canon (src/supergraphics.canon.css, no comment added):
- An .sg-info-bar without a variant sitting directly in .sg-bg-white or
  .sg-bg-gray, or in one of the package's orange surfaces, now has a black
  date (heading-on-light, heading-on-orange). On orange its orange date was
  invisible (1.0:1); on white it was 2.3:1. On purple surfaces and photos
  it stays orange-300, and the --on-dark variant keeps its orange date.
- .sg-headline--h5 and .sg-headline--h6 set the book's 1.1 line height for a
  headline set at those sizes; .sg-headline itself stays at 1 (H1 to H4,
  quotes) and sets no size.
- .sg-body sets --mfb-tracking-body, so it no longer inherits tracking.

Brand guard (brand-check.mjs, with planted and clean lines in its test):
- Uppercase: any case, quoted style-object keys, and the class as a lone
  string (clsx('uppercase'), a ternary, class:list).
- Prose is not read as values: hex and color functions in text between
  markup tags are skipped, a color function needs its parenthesis directly
  after the name, color() needs a color space, and a declared function
  (function color(name)) is code. Names such as id and selector are skipped.
- New warnings: gray-900 text (text-color), translucent text, letter
  spacing. Warn by default, like the other rules that cannot see the page.
- Also caught: the font shorthand and --font-* theme variables, SVG text
  rotate, SVG gradient elements, SVG clipPath polygons, an inset orange
  shadow without blur, a thick orange underline, an orange mark element,
  filter: opacity() beside a brand fill, and orange or purple borders mixed
  with transparent.
- No longer reported: hard-stop gradients that blend nothing (unless
  orange), opacity in a disabled state, // comments in SCSS, Sass and Less,
  an angle written 24.0deg; a built .uppercase compounded with another
  class (.eyebrow.uppercase) is now reported instead of exempt.

Docs: README and brand-spec say all of this; the info bar's type values join
the spec's table of classes that differ from the Brand Book; gray-700 on
white is 5.1:1, not 5.2:1. CONTRIBUTING no longer says the website's source
is public: its repository is private, and what the site serves is public.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The website's source repository is private and this one is public, so a
website-declared token no longer names a file, directory or line of it.
Its source is now "myfirstbitcoin.org@<commit>, <CSS name>": the custom
property, selector and property, or inline property (with where it is
used) that carries the value in the CSS and pages the site serves, for
example "myfirstbitcoin.org@6368b2a, --r-lg". The deciders named in
CONTRIBUTING keep the exact lines outside the repository.

- tokens.json: the 52 website sources move to the new form; notes drop
  file and line references and component names (use counts stay, counted
  with git grep in the site's source at 6368b2a). No value changes.
- CONTRIBUTING source rule 2 and $verification describe the new form.
- The spec generator reads the new form, shows the CSS name, and refuses a
  website source that names a file, a directory or a line.
- README and brand-spec.md say what a website source names; regenerated.
- The brand guard's test fixtures and allowlist example use src/ui and
  src/styles/site.css, generic consumer paths.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@TheBitcoinBackup
TheBitcoinBackup merged commit d08a397 into master Oct 6, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant