diff --git a/.npmrc b/.npmrc deleted file mode 100644 index c46cf20..0000000 --- a/.npmrc +++ /dev/null @@ -1,4 +0,0 @@ -# @bloxwap/hig-react declares optional exact Radix peers (dialog/popover 1.1.15) that conflict with -# Fumadocs (1.1.23). The docs import no HIG overlay entry, so keep npm from nesting duplicate Radix -# trees (which also detaches the packages/chart workspace link). See vendor/bloxwap-hig/README.md. -legacy-peer-deps=true diff --git a/apps/docs/README.md b/apps/docs/README.md index d290747..076336d 100644 --- a/apps/docs/README.md +++ b/apps/docs/README.md @@ -25,21 +25,21 @@ path from the built artifact. It does not run Next.js or a search backend. Edit MDX in `content/docs/` and sidebar order in each `meta.json`. The build checks local HTML links and assets, and emits a browser-searchable index at -`search.json`. Fonts come from the installed `@bloxwap/font` and `@bloxwap/hig-tokens` packages, so builds do not fetch Google Fonts. +`search.json`. Fonts come from the installed `@bloxwap/font` package (and `@bloxwap/hig-native-fonts` for social cards), so builds do not fetch Google Fonts. The presentation follows the Bloxwap HIG and consumes it as packages, not copies: -- `@bloxwap/hig-tokens`, `@bloxwap/hig-contracts` and `@bloxwap/hig-react` are installed from the - pinned, unpublished candidate tarballs in [`vendor/bloxwap-hig/`](../../vendor/bloxwap-hig/README.md). +- `@bloxwap/hig-tokens`, `@bloxwap/hig-contracts` and `@bloxwap/hig-react` (plus the build-time + `@bloxwap/hig-native-fonts`) are installed from the pinned, unpublished candidate tarballs in [`vendor/bloxwap-hig/`](../../vendor/bloxwap-hig/README.md). `app/global.css` imports `@bloxwap/hig-tokens/css` and `@bloxwap/hig-react/css` and maps HIG roles onto Fumadocs; it does not declare palette values. -- `styles/hig-roles.generated.css` is generated from the package manifest by - `scripts/hig-tokens.mjs --write` (radius, type, motion and density roles the CSS distribution does not - emit yet). `npm run lint` fails on drift, tarball hash changes, copied palettes, `.btn`-style primitive - copies or font binaries in the docs source. +- Radius, type, motion and density roles (`--hig-radius-*`, `--hig-type-*`, `--hig-motion-*`, + `--hig-density-*`) and the font roles come from `@bloxwap/hig-tokens/css`; the docs declare none. + `npm run lint` (`scripts/hig-tokens.mjs --check`) fails on tarball hash changes, local role declarations + or retired role names, copied palettes, `.btn`-style primitive copies or font binaries in the docs source. - Buttons, install tabs/copy, code-window copy, callouts, search error/empty states and the playground loading status use HIG components (`Button`, `IconButton`, `Tabs`, `Surface`, `Callout`, `Alert`, - `EmptyState`, `Spinner`). The embedded demo keeps the SDK's own controls, themed from HIG roles. + `EmptyState`, `Spinner`) and the `InstallCommand`/`CodeWindow` recipe entries. The embedded demo keeps the SDK's own controls, themed from HIG roles. - `public/logos/bloxwap-wordmark-white.svg` and `public/icon.svg` are the supplied monorepo documentation artwork. Preserve the marks instead of typesetting them. @@ -94,7 +94,8 @@ chrome's `--cts-font`/`--cts-mono` at Bloxwap Sans/Mono, and sets the canvas `theme.fontFamily` to Bloxwap Sans and `monoFamily`/`scaleFontFamily` (status line, drawing labels and scales) to Bloxwap Mono. The standalone playground and SDK presets retain their existing font defaults. -Social cards use the static Bloxwap Sans faces shipped in `@bloxwap/hig-tokens/fonts` (OFL). +Social cards use the static Bloxwap Sans faces shipped in `@bloxwap/hig-native-fonts/fonts` (OFL), a +build-time devDependency; Satori cannot read the variable WOFF2 in `@bloxwap/font`. HIG presentation, pinned candidate provenance, validation, exceptions and upgrade/rollback instructions are recorded in [the adoption report](../../docs/hig-adoption/README.md). diff --git a/apps/docs/app/global.css b/apps/docs/app/global.css index 583db9b..c14f955 100644 --- a/apps/docs/app/global.css +++ b/apps/docs/app/global.css @@ -3,7 +3,6 @@ @import 'fumadocs-ui/css/preset.css'; @import '@bloxwap/hig-tokens/css'; @import '@bloxwap/hig-react/css'; -@import '../styles/hig-roles.generated.css'; @theme inline { --font-sans: var(--font-bloxwap-sans), ui-rounded, system-ui, sans-serif; @@ -73,7 +72,7 @@ html > body[data-scroll-locked] { #nd-page > p { font-size: var(--hig-type-body-size); - line-height: var(--hig-type-body-line); + line-height: var(--hig-type-body-line-height); } .prose { @@ -140,7 +139,7 @@ html > body[data-scroll-locked] { min-height: 2.5rem; padding: 0.5rem 0.75rem; border-radius: var(--hig-radius-pill); - transition: background-color var(--hig-duration-fast) var(--ease-spring), color var(--hig-duration-fast) var(--ease-spring); + transition: background-color var(--hig-motion-duration-fast) var(--hig-motion-easing-standard), color var(--hig-motion-duration-fast) var(--hig-motion-easing-standard); font-size: 0.875rem; font-weight: 600; } @@ -151,22 +150,23 @@ html > body[data-scroll-locked] { } /* - * HIG controls (@bloxwap/hig-react/css) in the docs shell. The Next font loader hashes family names, so - * point HIG's 'Bloxwap Sans'/'Bloxwap Mono' at the loaded faces, and let links and icons share the button. - * Dense docs regions opt into HIG compact density on fine pointers; coarse pointers keep 48px targets. + * HIG controls (@bloxwap/hig-react/css) in the docs shell. HIG reads --hig-font-sans/--hig-font-mono, which + * resolve to the @bloxwap/font Next loader variables, so no family bridge is needed. Links and icons share the + * button. Dense docs regions opt into HIG compact density on fine pointers; coarse pointers keep 48px targets. */ -.hig-button { display: inline-flex; align-items: center; justify-content: center; gap: var(--hig-space-sm); font-family: var(--font-sans); text-decoration: none; touch-action: manipulation; transition: filter var(--hig-duration-fast) var(--ease-spring), transform var(--hig-duration-fast) var(--ease-spring); } +.hig-button { display: inline-flex; align-items: center; justify-content: center; gap: var(--hig-space-sm); text-decoration: none; touch-action: manipulation; transition: filter var(--hig-motion-duration-fast) var(--hig-motion-easing-standard), transform var(--hig-motion-duration-fast) var(--hig-motion-easing-standard); } .hig-button svg { flex: none; width: 1rem; height: 1rem; } .hig-button > span[aria-hidden='true'] { display: contents; } .hig-surface pre { font-family: var(--font-mono); } -[data-hig-density='compact'] { --hig-control: var(--hig-control-compact); --hig-gap: var(--hig-space-sm); } +[data-hig-density='compact'] { --hig-control: var(--hig-density-compact-control); --hig-gap: var(--hig-space-sm); } [data-hig-density='compact'] .hig-button { padding: 0 var(--hig-space-md); font-size: var(--hig-type-label-size); } .docs-icon-button.hig-button { position: relative; width: var(--hig-control); padding: 0; } /* HIG busy text stays announced but is visually hidden inside icon-only buttons. */ .docs-icon-button .hig-busy { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; } .copy-feedback { color: var(--hig-negative); font-size: var(--hig-type-caption-size); } @media (pointer: coarse) { - [data-hig-density='compact'] { --hig-control: var(--hig-control-comfortable); } + /* HIG buttons are 44px (--hig-button) by default; touch keeps the comfortable 48px target role. */ + [data-hig-density='compact'] { --hig-control: var(--hig-density-comfortable-control); --hig-button: var(--hig-density-comfortable-target); } } /* Chart-specific layout on the scale above; typography, surfaces, and states follow workers/docs. */ @@ -210,7 +210,7 @@ html > body[data-scroll-locked] { .window-dots { display: flex; gap: var(--hig-space-xs); } .window-dots > i { width: 6px; height: 6px; border-radius: var(--hig-radius-pill); background: var(--hig-nested); } .code-actions { display: flex; align-items: center; gap: var(--hig-space-xs); margin-right: calc(-1 * var(--hig-space-md)); } -.code-toolbar { min-height: var(--hig-control-compact); } +.code-toolbar { min-height: var(--hig-density-compact-control); } /* HIG CodeWindow recipe inside the hero card: the card supplies the surface, the recipe its language label and code. */ .code-window > .hig-surface { padding: 0; border-radius: 0; background: transparent; } .code-window > .hig-surface > .hig-text { margin: 0; padding: var(--hig-space-sm) var(--hig-space-xxl) 0; font: var(--text-2xs) / 1.5 var(--font-mono); color: color-mix(in oklab, var(--hig-info) 60%, var(--hig-text)); } @@ -233,7 +233,7 @@ html > body[data-scroll-locked] { @keyframes landing-rise { from { opacity: 0; translate: 0 8px; } } -.hero-copy > *, .code-window, .hero-preview { animation: landing-rise 600ms var(--ease-spring) both; } +.hero-copy > *, .code-window, .hero-preview { animation: landing-rise 600ms var(--hig-motion-easing-standard) both; } .hero-copy > h1 { animation-delay: 60ms; } .hero-copy > :not(.eyebrow, h1) { animation-delay: 200ms; } .code-window { animation-delay: 360ms; } @@ -244,9 +244,9 @@ html > body[data-scroll-locked] { /* Search (header and docs sidebar): a zero-stroke filled pill at the small control height, with key chips one step lighter than the box. */ button[data-search-full] { - height: var(--hig-control-compact); gap: var(--hig-space-sm); padding: 0 var(--hig-space-xs) 0 var(--hig-space-md); + height: var(--hig-density-compact-control); gap: var(--hig-space-sm); padding: 0 var(--hig-space-xs) 0 var(--hig-space-md); border: 0; border-radius: var(--hig-radius-pill); background: var(--hig-surface); color: var(--hig-muted); font-size: var(--text-sm); - transition: background-color var(--hig-duration-fast) var(--ease-spring), color var(--hig-duration-fast) var(--ease-spring); + transition: background-color var(--hig-motion-duration-fast) var(--hig-motion-easing-standard), color var(--hig-motion-duration-fast) var(--hig-motion-easing-standard); } button[data-search-full]:hover { background: var(--hig-nested); color: var(--hig-text); } button[data-search-full] kbd { @@ -300,13 +300,13 @@ button[data-search-full] kbd { .guide-links { display: grid; gap: var(--hig-space-md); } .guide-links a { display: flex; align-items: center; gap: var(--hig-space-xl); - transition: background-color var(--hig-duration-fast) var(--ease-spring); + transition: background-color var(--hig-motion-duration-fast) var(--hig-motion-easing-standard); } .guide-links a:hover { background: var(--hig-nested); } .guide-links a > span { font: var(--text-2xs) var(--font-mono); color: var(--color-fd-muted-foreground); } -.guide-links a > svg { flex: none; margin-left: auto; width: 1rem; height: 1rem; color: var(--color-fd-muted-foreground); transition: color var(--hig-duration-fast) var(--ease-spring), transform var(--hig-duration-fast) var(--ease-spring); } +.guide-links a > svg { flex: none; margin-left: auto; width: 1rem; height: 1rem; color: var(--color-fd-muted-foreground); transition: color var(--hig-motion-duration-fast) var(--hig-motion-easing-standard), transform var(--hig-motion-duration-fast) var(--hig-motion-easing-standard); } .guide-links a:hover > svg { color: var(--color-fd-primary); transform: translateX(2px); } -.guide-links h3 { font-size: var(--text-sm); margin-bottom: var(--hig-space-xs); font-weight: 600; transition: color var(--hig-duration-fast) var(--ease-spring); } +.guide-links h3 { font-size: var(--text-sm); margin-bottom: var(--hig-space-xs); font-weight: 600; transition: color var(--hig-motion-duration-fast) var(--hig-motion-easing-standard); } .guide-links a:hover h3 { color: var(--color-fd-primary); } .guide-links p { color: var(--color-fd-muted-foreground); font-size: var(--text-xs); } /* Full-bleed rule; content inset to the header's box so both edges line up with the nav. */ @@ -314,14 +314,14 @@ button[data-search-full] kbd { .site-footer { margin-top: auto; padding: var(--hig-space-section) max(var(--hig-space-lg), calc((100% - var(--fd-layout-width, 1400px)) / 2 + var(--hig-space-lg))) var(--hig-space-section); } /* Page end: Edit on GitHub as a quiet ghost pill, prev/next as zero-stroke fill cards (the landing's card style). */ #nd-page .edit-on-github { - align-self: flex-start; height: var(--hig-control-compact); padding: 0 var(--hig-space-md); border: 0; border-radius: var(--hig-radius-pill); + align-self: flex-start; height: var(--hig-density-compact-control); padding: 0 var(--hig-space-md); border: 0; border-radius: var(--hig-radius-pill); background: transparent; color: var(--hig-muted); font-size: var(--text-sm); - transition: background-color var(--hig-duration-fast) var(--ease-spring), color var(--hig-duration-fast) var(--ease-spring); + transition: background-color var(--hig-motion-duration-fast) var(--hig-motion-easing-standard), color var(--hig-motion-duration-fast) var(--hig-motion-easing-standard); } #nd-page .edit-on-github:hover { background: var(--hig-surface); color: var(--hig-text); } .page-nav > a { padding: var(--hig-space-xl) var(--hig-space-xxl); border: 0; border-radius: var(--hig-radius-xl); background: var(--hig-surface); - transition: background-color var(--hig-duration-fast) var(--ease-spring), color var(--hig-duration-fast) var(--ease-spring); + transition: background-color var(--hig-motion-duration-fast) var(--hig-motion-easing-standard), color var(--hig-motion-duration-fast) var(--hig-motion-easing-standard); } .page-nav > a:hover { background: var(--hig-nested); color: var(--hig-action); } .footer-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); column-gap: var(--hig-space-xxl); row-gap: var(--hig-space-section); } @@ -332,7 +332,7 @@ button[data-search-full] kbd { /* The wordmark's box reserves descender room for the "p"; shift it so the cap height (not the box) centers on the icon: (143/2 - (149-74.6)/2) / 223.6 of the viewBox. */ .footer-wordmark { width: auto; height: 32px; transform: translateY(15.3%); } .footer-col h2 { margin: 0 0 var(--hig-space-md); color: var(--color-fd-muted-foreground); font: 700 var(--text-2xs) / 1.5 var(--font-sans); letter-spacing: .14em; text-transform: uppercase; } -.footer-col a, .footer-base a { transition: color var(--hig-duration-fast) var(--ease-spring), opacity var(--hig-duration-fast) var(--ease-spring); } +.footer-col a, .footer-base a { transition: color var(--hig-motion-duration-fast) var(--hig-motion-easing-standard), opacity var(--hig-motion-duration-fast) var(--hig-motion-easing-standard); } .footer-col a { display: block; width: fit-content; padding: var(--hig-space-xs) 0; color: var(--color-fd-foreground); font-size: var(--text-sm); opacity: .8; } .footer-col a:hover, .footer-base a:hover { color: var(--color-fd-primary); opacity: 1; } .footer-base { display: flex; flex-wrap: wrap; justify-content: space-between; gap: var(--hig-space-sm); margin-top: var(--hig-space-section); color: var(--color-fd-muted-foreground); font-size: var(--text-xs); } @@ -344,13 +344,13 @@ button[data-search-full] kbd { } @media (max-width: 480px) { .footer-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } } .docs-sidebar-links { display: flex; align-items: center; gap: var(--hig-space-lg); padding-top: var(--hig-space-lg); font-size: var(--text-xs); color: var(--color-fd-muted-foreground); } -.docs-sidebar-links a { transition: color var(--hig-duration-fast) var(--ease-spring); } +.docs-sidebar-links a { transition: color var(--hig-motion-duration-fast) var(--hig-motion-easing-standard); } .docs-sidebar-links a:hover { color: var(--color-fd-foreground); } .not-found { max-width: 37.5rem; padding: 7.5rem var(--hig-space-xxl); margin: 0 auto; } .not-found h1 { font-size: var(--text-4xl); margin: var(--hig-space-xxl) 0; } .not-found .hig-button { margin-top: var(--hig-space-xxxl); } .not-found-links { display: flex; flex-wrap: wrap; gap: var(--hig-space-sm) var(--hig-space-xl); margin-top: var(--hig-space-xxxl); font: var(--text-2xs) / 1.5 var(--font-mono); letter-spacing: .09em; } -.not-found-links a { color: var(--color-fd-muted-foreground); transition: color var(--hig-duration-fast) var(--ease-spring); } +.not-found-links a { color: var(--color-fd-muted-foreground); transition: color var(--hig-motion-duration-fast) var(--hig-motion-easing-standard); } .not-found-links a:hover { color: var(--color-fd-foreground); } @media (max-width: 800px) { .home { padding-top: var(--hig-space-xxxl); } @@ -371,11 +371,11 @@ button[data-search-full] kbd { } /* HIG type roles and adaptive targets; the dense chart keeps compact controls. */ -body { font-size: var(--hig-type-body-size); line-height: var(--hig-type-body-line); } +body { font-size: var(--hig-type-body-size); line-height: var(--hig-type-body-line-height); } .hero h1 { font-size: clamp(var(--hig-type-title-size), 4.4vw, var(--hig-type-display-size)); line-height: 1.0833; font-weight: var(--hig-type-display-weight); } -#nd-page h1 { font-size: var(--hig-type-title-size); line-height: var(--hig-type-title-line); font-weight: var(--hig-type-title-weight); } +#nd-page h1 { font-size: var(--hig-type-title-size); line-height: var(--hig-type-title-line-height); font-weight: var(--hig-type-title-weight); } @media (pointer: coarse) { - .docs-app-link, #nd-page .edit-on-github { min-height: var(--hig-control-comfortable); } + .docs-app-link, #nd-page .edit-on-github { min-height: var(--hig-density-comfortable-control); } } @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0ms !important; transition-duration: 0ms !important; scroll-behavior: auto !important; } @@ -396,9 +396,6 @@ body { font-size: var(--hig-type-body-size); line-height: var(--hig-type-body-li /* HIG Callout for MDX ; the body keeps prose typography. */ .docs-callout { margin-block: var(--hig-space-xxl); } .docs-callout > .hig-surface { display: flex; flex-direction: column; gap: var(--hig-space-xs); padding: var(--hig-space-lg) var(--hig-space-xl); } -/* HIG marks warning/error severities; give info/success the same edge in their roles. */ -.docs-callout > [data-severity='info'] { border-inline-start: 3px solid var(--hig-info); } -.docs-callout > [data-severity='success'] { border-inline-start: 3px solid var(--hig-positive); } .docs-callout-body > :first-child { margin-top: 0; } .docs-callout-body > :last-child { margin-bottom: 0; } diff --git a/apps/docs/components/callout.tsx b/apps/docs/components/callout.tsx index 2257c79..f4a7972 100644 --- a/apps/docs/components/callout.tsx +++ b/apps/docs/components/callout.tsx @@ -2,14 +2,14 @@ import type { ReactNode } from 'react'; import { Callout as HigCallout } from '@bloxwap/hig-react/status'; type FumadocsType = 'info' | 'note' | 'idea' | 'success' | 'warn' | 'warning' | 'error'; -const severity = { info: 'info', note: 'info', idea: 'info', success: 'success', warn: 'warning', warning: 'warning', error: 'error' } as const; -const fallbackTitle = { info: 'Note', success: 'Success', warning: 'Warning', error: 'Error' } as const; +const severity = { info: 'info', note: 'note', idea: 'tip', success: 'success', warn: 'warning', warning: 'warning', error: 'error' } as const; +const fallbackTitle = { info: 'Note', note: 'Note', tip: 'Tip', success: 'Success', warning: 'Warning', error: 'Error' } as const; -/** MDX `` keeps the Fumadocs props and renders the HIG callout. Static content is not announced. */ +/** MDX `` keeps the Fumadocs props and renders the HIG callout (ReactNode titles, note/tip edges). Static content is not announced. */ export function Callout({ type = 'info', title, children }: { type?: FumadocsType; title?: ReactNode; children?: ReactNode }) { const level = severity[type] ?? 'info'; return
- +
{children}
; diff --git a/apps/docs/components/hero-code.tsx b/apps/docs/components/hero-code.tsx index 744034d..bf9c1ea 100644 --- a/apps/docs/components/hero-code.tsx +++ b/apps/docs/components/hero-code.tsx @@ -1,7 +1,7 @@ import { readFileSync } from 'node:fs'; import { join } from 'node:path'; import type { ReactNode } from 'react'; -import { Surface, Text } from '@bloxwap/hig-react/surface'; +import { CodeWindow } from '@bloxwap/hig-react/recipes/code-window'; import { CopyButton } from './copy-button'; /* @@ -46,8 +46,8 @@ export function HeroCode() { {SNIPPET_FILE} - {/* HIG CodeWindow recipe markup (language label + focusable code) from HIG primitives; see install-command.tsx. */} - TSX
{highlight(source)}
+ {/* HIG CodeWindow recipe (language label + focusable code); its entry imports no overlay or Radix peer. */} + {highlight(source)}

; } diff --git a/apps/docs/components/install-command.tsx b/apps/docs/components/install-command.tsx index 4c33b4d..8dcbdfa 100644 --- a/apps/docs/components/install-command.tsx +++ b/apps/docs/components/install-command.tsx @@ -1,9 +1,7 @@ 'use client'; -import { useState } from 'react'; -import { Button } from '@bloxwap/hig-react/button'; import { Tabs } from '@bloxwap/hig-react/navigation'; -import { Surface } from '@bloxwap/hig-react/surface'; +import { InstallCommand as HigInstallCommand } from '@bloxwap/hig-react/recipes/install-command'; import { useClipboard } from '@/lib/clipboard'; const managers = [ @@ -13,30 +11,16 @@ const managers = [ { name: 'yarn', command: 'yarn add @bloxwap/chart' }, ]; -/* - * The HIG `InstallCommand` recipe (command + host clipboard adapter, "Copied" / manual-selection recovery), - * composed from the same HIG primitives. `@bloxwap/hig-react/recipes` also bundles the overlay entry, which - * needs the Radix dropdown-menu and tooltip peers this site does not otherwise install; switch to the recipe - * import once HIG ships it without those peers (exception recorded in docs/hig-adoption/README.md). +/** + * HIG Tabs (arrow/Home/End keys) around one HIG `InstallCommand` recipe per package manager. The recipe entry + * imports no overlay, so it needs no Radix peer; the command's focusable `pre` is the panel's only tab stop. */ -function Command({ command, onCopy }: { command: string; onCopy: () => Promise }) { - const [status, setStatus] = useState(''); - return -
{command}
- -

{status}

-
; -} - -/** HIG Tabs (arrow/Home/End keys) around one install command per package manager. */ export function InstallCommand() { const copy = useClipboard(); return
({ value: name, label: name, content: copy(command)} /> }))} + items={managers.map(({ name, command }) => ({ value: name, label: name, content: copy(command)} /> }))} />

Bun · Node.js · Browsers · Workers

; diff --git a/apps/docs/lib/og-card.tsx b/apps/docs/lib/og-card.tsx index 0f3f651..6acd349 100644 --- a/apps/docs/lib/og-card.tsx +++ b/apps/docs/lib/og-card.tsx @@ -12,9 +12,10 @@ import { socialImageSize } from './social'; const ink = hig.colors.text.srgb; // --foreground const muted = hig.colors.muted.srgb; // --muted-foreground const bar: [color: string, share: number][] = [[hig.colors.action.srgb, 62], [hig.colors.info.srgb, 14], ['#b300ff', 10], ['#ff479c', 8], ['#fffb38', 6]]; -// Static Bloxwap Sans faces for the OG renderer come from @bloxwap/hig-tokens/fonts (OFL, derived from -// @bloxwap/font 0.1.2 upstream); the docs no longer keep copied font binaries. Hoisted by the workspace lock. -const higFonts = join(process.cwd(), '../../node_modules/@bloxwap/hig-tokens/assets/fonts'); +// Static Bloxwap Sans faces for the OG renderer (Satori reads TTF, not the variable WOFF2 in @bloxwap/font) come +// from @bloxwap/hig-native-fonts (OFL, derived from @bloxwap/font 0.1.2), a build-time-only devDependency since +// @bloxwap/hig-tokens no longer ships them (hig#64). No font binaries are copied. Hoisted by the workspace lock. +const higFonts = join(process.cwd(), '../../node_modules/@bloxwap/hig-native-fonts/fonts'); const assets = Promise.all([ readFile(join(higFonts, 'BloxwapSans-Bold.ttf')), readFile(join(higFonts, 'BloxwapSans-Black.ttf')), diff --git a/apps/docs/package.json b/apps/docs/package.json index e82c355..b4feddb 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -16,9 +16,9 @@ "dependencies": { "@bloxwap/chart": "0.3.0", "@bloxwap/font": "0.1.2", - "@bloxwap/hig-contracts": "file:../../vendor/bloxwap-hig/bloxwap-hig-contracts-0.1.0-candidate.sha7c67a07c.tgz", - "@bloxwap/hig-react": "file:../../vendor/bloxwap-hig/bloxwap-hig-react-0.1.0-candidate.sha7c67a07c.tgz", - "@bloxwap/hig-tokens": "file:../../vendor/bloxwap-hig/bloxwap-hig-tokens-0.1.0-candidate.sha7c67a07c.tgz", + "@bloxwap/hig-contracts": "file:../../vendor/bloxwap-hig/bloxwap-hig-contracts-0.1.0-candidate.sha872906a9.tgz", + "@bloxwap/hig-react": "file:../../vendor/bloxwap-hig/bloxwap-hig-react-0.1.0-candidate.sha872906a9.tgz", + "@bloxwap/hig-tokens": "file:../../vendor/bloxwap-hig/bloxwap-hig-tokens-0.1.0-candidate.sha872906a9.tgz", "fumadocs-core": "16.15.14", "fumadocs-mdx": "15.4.5", "fumadocs-ui": "16.15.14", @@ -28,6 +28,7 @@ "react-dom": "^19.2.0" }, "devDependencies": { + "@bloxwap/hig-native-fonts": "file:../../vendor/bloxwap-hig/bloxwap-hig-native-fonts-0.1.0-candidate.sha872906a9.tgz", "@tailwindcss/postcss": "^4.2.0", "@types/mdx": "^2.0.13", "@types/node": "^24.13.6", diff --git a/apps/docs/scripts/hig-tokens.mjs b/apps/docs/scripts/hig-tokens.mjs index 241bab4..f6f9272 100644 --- a/apps/docs/scripts/hig-tokens.mjs +++ b/apps/docs/scripts/hig-tokens.mjs @@ -1,8 +1,9 @@ -// HIG adoption guard for the docs site. Tokens, CSS and components come from the vendored -// @bloxwap/hig-* candidate tarballs (vendor/bloxwap-hig); this script only derives the manifest roles -// that @bloxwap/hig-tokens/css does not emit as variables yet (radius, type, motion, density) and the -// canvas-compatible sRGB chart theme, and fails CI when copies of HIG sources creep back in. -import { readFile, readdir, writeFile } from 'node:fs/promises'; +// HIG adoption guard for the docs site. Tokens, roles, CSS and components come from the vendored +// @bloxwap/hig-* candidate tarballs (vendor/bloxwap-hig). @bloxwap/hig-tokens/css emits the radius, type, +// motion and density roles, so this script only derives the canvas-compatible sRGB chart theme from the +// typed manifest, and its --check fails CI when copies of HIG sources or role declarations creep back in. +import { readFile, readdir } from 'node:fs/promises'; +import { existsSync } from 'node:fs'; import { createHash } from 'node:crypto'; import { createRequire } from 'node:module'; import assert from 'node:assert/strict'; @@ -21,31 +22,23 @@ export const chartTheme = { series: { upColor: colors.buy, downColor: colors.sell, wickUpColor: colors.buy, wickDownColor: colors.sell, borderUpColor: colors.buy, borderDownColor: colors.sell }, }; -const rem = (px) => `${px / 16}rem`; -const vars = []; -for (const [key, value] of Object.entries(manifest.radius)) vars.push(` --hig-radius-${key}: ${key === 'pill' ? '9999px' : rem(value)};`); -for (const [key, role] of Object.entries(manifest.type)) vars.push(` --hig-type-${key}-size: ${rem(role.size)};`, ` --hig-type-${key}-line: ${rem(role.lineHeight)};`, ` --hig-type-${key}-weight: ${role.weight};`); -for (const [key, value] of Object.entries(manifest.motion)) vars.push(` --hig-duration-${key}: ${value}ms;`); -for (const [key, value] of Object.entries(manifest.density)) vars.push(` --hig-control-${key}: ${rem(value.control)};`); -const css = `/* Generated from @bloxwap/hig-tokens ${manifest.version} manifest by scripts/hig-tokens.mjs; do not edit. */\n:root {\n${vars.join('\n')}\n}\n@media (prefers-reduced-motion: reduce) {\n :root { ${Object.keys(manifest.motion).map((key) => `--hig-duration-${key}: 0ms;`).join(' ')} }\n}\n`; -const output = new URL('../styles/hig-roles.generated.css', import.meta.url); -if (process.argv.includes('--write')) await writeFile(output, css); - if (process.argv.includes('--check')) { - assert.equal(await readFile(output, 'utf8'), css, 'HIG role drift: run node apps/docs/scripts/hig-tokens.mjs --write'); const vendor = new URL('../../../vendor/bloxwap-hig/', import.meta.url); const provenance = JSON.parse(await readFile(new URL('provenance.json', vendor), 'utf8')); for (const [file, hash] of Object.entries(provenance.sha256)) assert.equal(createHash('sha256').update(await readFile(new URL(file, vendor))).digest('hex'), hash, `Vendored HIG tarball changed: ${file}`); const docsPackage = JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf8')); - for (const name of ['tokens', 'contracts', 'react']) { - const spec = docsPackage.dependencies[`@bloxwap/hig-${name}`]; + for (const name of ['tokens', 'contracts', 'react', 'native-fonts']) { + const field = name === 'native-fonts' ? 'devDependencies' : 'dependencies'; + const spec = docsPackage[field][`@bloxwap/hig-${name}`]; assert.ok(spec?.startsWith('file:../../vendor/bloxwap-hig/') && spec.includes(provenance.version), `@bloxwap/hig-${name} must come from the pinned vendored tarball`); assert.equal(JSON.parse(await readFile(new URL(`../../../node_modules/@bloxwap/hig-${name}/package.json`, import.meta.url), 'utf8')).version, provenance.version, `Installed @bloxwap/hig-${name} is not ${provenance.version}; run npm ci`); } - assert.deepEqual((await readdir(new URL('../styles/', import.meta.url))).sort(), ['hig-roles.generated.css'], 'Only the generated HIG role file belongs in styles/'); - // No copied palettes, token snapshots, primitive classes or font binaries in the docs shell. + assert.ok(!existsSync(new URL('../styles/', import.meta.url)), 'styles/ is gone: radius/type/motion/density roles come from @bloxwap/hig-tokens/css'); + // No copied palettes, role declarations, token snapshots, primitive classes or font binaries in the docs shell. const forbidden = [ [/--(hig|bloxwap)-[\w-]+\s*:\s*(#|oklch\(|rgb|color\()/, 'copied palette declaration'], + [/--hig-(radius|type|motion|density|font|duration|control)-[\w-]+\s*:/, 'local HIG role declaration (use the @bloxwap/hig-tokens/css role)'], + [/var\(--hig-(duration|control)-|var\(--hig-type-[a-z]+-line\)/, 'retired generated role name (use --hig-motion-duration-*, --hig-density-*-control, --hig-type-*-line-height)'], [/vendor\/hig\/|tokens\.generated\.css|@workspace\/(tokens|ui)/, 'obsolete token snapshot import'], [/(^|[\s"'.])btn(--[\w-]+)?\b(?![-\w])/m, 'duplicate .btn primitive (use @bloxwap/hig-react Button or .hig-button)'], ]; @@ -54,12 +47,12 @@ if (process.argv.includes('--check')) { const path = new URL(entry.name + (entry.isDirectory() ? '/' : ''), dir); if (entry.isDirectory()) await checkSurface(path); else if (/\.(ttf|otf|woff2?)$/.test(entry.name)) assert.fail(`Copied font binary: ${path}`); - else if (/\.(css|tsx?)$/.test(entry.name) && !entry.name.endsWith('.generated.css')) { + else if (/\.(css|tsx?)$/.test(entry.name)) { const source = await readFile(path, 'utf8'); for (const [pattern, reason] of forbidden) assert.ok(!pattern.test(source), `${reason}: ${path.pathname}`); } } } - for (const dir of ['app/', 'components/', 'lib/', 'styles/']) await checkSurface(new URL(`../${dir}`, import.meta.url)); - console.log(`HIG ${provenance.version} tarball provenance, installed versions, generated roles and copy drift checks passed.`); + for (const dir of ['app/', 'components/', 'lib/']) await checkSurface(new URL(`../${dir}`, import.meta.url)); + console.log(`HIG ${provenance.version} tarball provenance, installed versions, package-owned roles and copy drift checks passed.`); } diff --git a/apps/docs/scripts/measure-hig.mjs b/apps/docs/scripts/measure-hig.mjs index 9a9fb37..9f53b17 100644 --- a/apps/docs/scripts/measure-hig.mjs +++ b/apps/docs/scripts/measure-hig.mjs @@ -19,7 +19,7 @@ async function sizes(dir) { await walk(dir); return result; } -const git = (...args) => execFileSync('git', args, { encoding: 'utf8' }); +const git = (...args) => execFileSync('git', args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }); const [baselineDirectory, baselineCommit] = process.argv.slice(2); if (!baselineDirectory || !baselineCommit) throw new Error('Usage: node apps/docs/scripts/measure-hig.mjs '); const before = await sizes(baselineDirectory); @@ -36,7 +36,9 @@ const beforeLock = lockPackages(git('show', `${baselineCommit}:package-lock.json const afterLock = lockPackages(await readFile('package-lock.json', 'utf8')); const docsDeps = (text) => Object.keys({ ...JSON.parse(text).dependencies, ...JSON.parse(text).devDependencies }); const provenance = JSON.parse(await readFile('vendor/bloxwap-hig/provenance.json', 'utf8')); -const generated = (ref) => { try { return Buffer.byteLength(git('show', `${ref}:apps/docs/styles/${ref === baselineCommit ? 'hig.generated.css' : 'hig-roles.generated.css'}`)); } catch { return 0; } }; +// Generated token adapters: hig.generated.css (copied snapshot, step 1), hig-roles.generated.css (step 2), none since +// @bloxwap/hig-tokens/css emits the roles itself. +const generated = (ref) => ['hig.generated.css', 'hig-roles.generated.css'].reduce((sum, file) => { try { return sum + Buffer.byteLength(git('show', `${ref}:apps/docs/styles/${file}`)); } catch { return sum; } }, 0); console.log(JSON.stringify({ baselineCommit, diff --git a/apps/docs/scripts/test-hig-browser.mjs b/apps/docs/scripts/test-hig-browser.mjs index 0985f18..162cbf4 100644 --- a/apps/docs/scripts/test-hig-browser.mjs +++ b/apps/docs/scripts/test-hig-browser.mjs @@ -37,7 +37,15 @@ try { assert.equal(await page.getByRole('tab', { name: 'yarn', exact: true }).getAttribute('aria-selected'), 'true'); await page.keyboard.press('Home'); assert.equal(await page.getByRole('tab', { name: 'bun', exact: true }).getAttribute('aria-selected'), 'true'); - record('Install tabs: arrows/Home/End, selected state and focus'); + // HIG Tabs + InstallCommand recipe (hig#69): the command's focusable
 is the panel's single tab stop.
+  // Tabs drops the panel's own tabindex in an effect after the switch, so wait for that commit first.
+  await page.waitForFunction(() => !document.querySelector('[role="tabpanel"]:not([hidden])').hasAttribute('tabindex'));
+  await page.keyboard.press('Tab');
+  const afterTabs = await page.evaluate(() => { const el = document.activeElement; return { tag: el.tagName, role: el.getAttribute('role'), inPanel: !!el.closest('[role="tabpanel"]') }; });
+  assert.deepEqual(afterTabs, { tag: 'PRE', role: null, inPanel: true });
+  await page.keyboard.press('Tab');
+  assert.equal(await page.evaluate(() => document.activeElement.textContent), 'Copy install command');
+  record('Install tabs: arrows/Home/End, selected state and focus; one tab stop per install command');
   await page.evaluate(() => {
     window.copyStatusLog = [];
     const observe = () => {
@@ -77,14 +85,19 @@ try {
   await page.waitForFunction(() => window.copyStatusLog.some(entry => entry.text.includes('Could not copy.') && entry.visibleError));
   assert.equal(await copyCode.isDisabled(), false);
   record('Copy: pending suppresses repeat activation, success status, visible denial and recovery');
-  const presentation = await page.evaluate(() => {
+  const presentation = await page.evaluate(async () => {
     const css = getComputedStyle(document.documentElement);
     const button = document.querySelector('.hig-button');
-    return { surface: css.getPropertyValue('--hig-surface').trim(), font: getComputedStyle(document.body).fontFamily, buttonFont: getComputedStyle(button).fontFamily, motion: css.getPropertyValue('--hig-duration-fast').trim(), transform: getComputedStyle(button).transform, legacy: ['--card', '--bloxwap-green', '--space-md'].filter(name => css.getPropertyValue(name).trim()) };
+    await document.fonts.ready;
+    // hig#65: HIG CSS reads --hig-font-sans, so the button's first family is the Next-loaded Bloxwap face.
+    const first = (el) => getComputedStyle(el).fontFamily.split(',')[0].trim().replace(/["']/g, '');
+    const loaded = (el) => [...document.fonts].some(face => face.family.replace(/["']/g, '') === first(el) && face.status === 'loaded');
+    return { buttonFaceLoaded: loaded(button), surface: css.getPropertyValue('--hig-surface').trim(), font: getComputedStyle(document.body).fontFamily, buttonFont: getComputedStyle(button).fontFamily, motion: css.getPropertyValue('--hig-motion-duration-fast').trim(), transform: getComputedStyle(button).transform, legacy: ['--card', '--bloxwap-green', '--space-md'].filter(name => css.getPropertyValue(name).trim()) };
   });
   assert.ok(await sameColor(page, presentation.surface, cssColor('surface')), `--hig-surface ${presentation.surface}`);
   assert.match(presentation.font, /BloxwapSans|Bloxwap Sans|bloxwap/i);
   assert.match(presentation.buttonFont, /BloxwapSans|Bloxwap Sans|bloxwap/i);
+  assert.equal(presentation.buttonFaceLoaded, true, 'HIG button renders in a loaded Bloxwap face, not a fallback');
   assert.deepEqual(presentation.legacy, []);
   assert.match(presentation.motion, /^0(m?s)$/);
   assert.equal(presentation.transform, 'none');
diff --git a/apps/docs/styles/hig-roles.generated.css b/apps/docs/styles/hig-roles.generated.css
deleted file mode 100644
index d992734..0000000
--- a/apps/docs/styles/hig-roles.generated.css
+++ /dev/null
@@ -1,31 +0,0 @@
-/* Generated from @bloxwap/hig-tokens 0.1.0 manifest by scripts/hig-tokens.mjs; do not edit. */
-:root {
-  --hig-radius-sm: 0.375rem;
-  --hig-radius-md: 0.5rem;
-  --hig-radius-lg: 0.625rem;
-  --hig-radius-xl: 0.875rem;
-  --hig-radius-pill: 9999px;
-  --hig-type-caption-size: 0.75rem;
-  --hig-type-caption-line: 1.125rem;
-  --hig-type-caption-weight: 500;
-  --hig-type-label-size: 0.875rem;
-  --hig-type-label-line: 1.25rem;
-  --hig-type-label-weight: 600;
-  --hig-type-body-size: 1rem;
-  --hig-type-body-line: 1.5rem;
-  --hig-type-body-weight: 400;
-  --hig-type-title-size: 1.5rem;
-  --hig-type-title-line: 1.875rem;
-  --hig-type-title-weight: 600;
-  --hig-type-display-size: 3rem;
-  --hig-type-display-line: 3.25rem;
-  --hig-type-display-weight: 600;
-  --hig-duration-fast: 180ms;
-  --hig-duration-base: 260ms;
-  --hig-duration-slow: 320ms;
-  --hig-control-comfortable: 3rem;
-  --hig-control-compact: 2.25rem;
-}
-@media (prefers-reduced-motion: reduce) {
-  :root { --hig-duration-fast: 0ms; --hig-duration-base: 0ms; --hig-duration-slow: 0ms; }
-}
diff --git a/docs/hig-adoption/README.md b/docs/hig-adoption/README.md
index 92197a2..ae06bda 100644
--- a/docs/hig-adoption/README.md
+++ b/docs/hig-adoption/README.md
@@ -1,6 +1,6 @@
 # Chart docs adoption · HIG 0.1.0 candidate
 
-Owner: Chart maintainers. Updated 2026-10-14; exceptions reviewed by 2026-11-13.
+Owner: Chart maintainers. Updated 2026-10-15; exceptions reviewed by 2026-11-13.
 
 This repository owns the Chart row of [hig#34](https://github.com/bloxwap/hig/issues/34) and its
 repository-level evidence for [hig#36](https://github.com/bloxwap/hig/issues/36).
@@ -8,7 +8,8 @@ repository-level evidence for [hig#36](https://github.com/bloxwap/hig/issues/36)
 | Step | PR | HIG source | How HIG is consumed |
 | --- | --- | --- | --- |
 | 1 | [#52](https://github.com/bloxwap/chart/pull/52) ([evidence](https://github.com/bloxwap/chart/tree/c4dff80/docs/hig-adoption)) | [1a776b9](https://github.com/bloxwap/hig/tree/1a776b9326c6ba03702894943b3edbfd178c4b40) | Copied plain-CSS snapshot + manifest, alias adapter, local `.btn`/copy/install primitives |
-| 2 | this migration | [7c67a07](https://github.com/bloxwap/hig/tree/7c67a07ce20c02264b0fdc57e5f27d020cfd6fdc) | Packed `@bloxwap/hig-tokens`, `-contracts`, `-react` tarballs ([vendor/bloxwap-hig](../../vendor/bloxwap-hig/README.md)) |
+| 2 | [#53](https://github.com/bloxwap/chart/pull/53) | [7c67a07](https://github.com/bloxwap/hig/tree/7c67a07ce20c02264b0fdc57e5f27d020cfd6fdc) | Packed `@bloxwap/hig-tokens`, `-contracts`, `-react` tarballs ([vendor/bloxwap-hig](../../vendor/bloxwap-hig/README.md)) |
+| 3 | candidate refresh (this change) | [872906a](https://github.com/bloxwap/hig/tree/872906a9071469af8714b3b6fb5cc7bd64d2d6f3) | Same packages plus build-time `@bloxwap/hig-native-fonts`; upstream fixes replace the step 2 workarounds |
 
 ## What changed in step 2
 
@@ -30,7 +31,53 @@ repository-level evidence for [hig#36](https://github.com/bloxwap/hig/issues/36)
   `@bloxwap/hig-tokens/css` unmodified and a chart theme from the typed package manifest. Base path,
   metadata, deep links, API examples and capability wording are unchanged.
 
-## Measured before/after
+## What changed in step 3 (HIG 872906a candidate)
+
+Upstream fixes from [hig#63](https://github.com/bloxwap/hig/issues/63), [#64](https://github.com/bloxwap/hig/issues/64),
+[#65](https://github.com/bloxwap/hig/issues/65), [#66](https://github.com/bloxwap/hig/issues/66) and
+[#69](https://github.com/bloxwap/hig/issues/69) let the docs drop their workarounds:
+
+- **Roles (#66).** `@bloxwap/hig-tokens/css` emits `--hig-radius-*`, `--hig-type-*`, `--hig-motion-*` and
+  `--hig-density-*`. `styles/hig-roles.generated.css`, its generator and its lint drift guard are deleted; the shell
+  uses the package names (`--hig-motion-duration-fast`, `--hig-density-compact-control`,
+  `--hig-type-body-line-height`, …). The undefined local `--ease-spring` is replaced by `--hig-motion-easing-standard`.
+  `npm run lint` now rejects local role declarations and the retired generated names instead.
+- **Peers and recipes (#63).** `.npmrc` (`legacy-peer-deps=true`) is deleted: `npm ci` installs with one Radix tree
+  and the `packages/chart` workspace link intact. Install commands render the HIG `recipes/install-command` entry and
+  the hero code renders `recipes/code-window`; neither entry imports the overlay or a Radix peer.
+- **Fonts (#64, #65).** HIG CSS reads `--hig-font-sans`/`--hig-font-mono`, which resolve to the `@bloxwap/font` Next
+  loader variables, so the `.hig-button { font-family }` bridge is gone; the browser test asserts the button's first
+  family is a loaded Bloxwap face. The tokens tarball has no TTFs (4,428,734 → 8,108 bytes). Social cards read their
+  two static faces from the build-time `@bloxwap/hig-native-fonts` devDependency (Satori cannot read WOFF2).
+- **Callout and Tabs (#69).** MDX `` passes ReactNode titles through and maps `note`/`idea` to HIG `note`/`tip`;
+  the local info/success edge rules are deleted. HIG Tabs no longer adds a second tab stop before the install
+  command; the browser test checks that Tab from the selected tab lands on the command, then on Copy.
+- **Touch targets.** HIG buttons are now 44 px by default (hig#80). On coarse pointers the compact install/code regions
+  set `--hig-button` to the comfortable 48 px target role, so the existing 48 px touch check still holds.
+
+| Measure (baseline `a3b0f6b`, same `/chart` export settings) | Before | After | Delta |
+| --- | ---: | ---: | ---: |
+| Vendored tarballs (bytes) | 4,446,466 (3) | 4,454,889 (4) | +8,423 |
+| … web packages only (tokens + contracts + react) | 4,446,466 | 34,514 | −4,411,952 |
+| Lockfile packages | 403 | 404 | +1 (`@bloxwap/hig-native-fonts`) |
+| Lockfile Radix entries (nested) | 34 (0) | 34 (0) | 0, without `legacy-peer-deps` |
+| Emitted CSS (bytes, whole export) | 152,712 | 163,176 | +10,464 |
+| Emitted JS (bytes, whole export) | 2,188,721 | 2,191,579 | +2,858 |
+| Emitted fonts (bytes) | 14,205,712 | 14,205,712 | 0 |
+| Static export total (bytes / files) | 29,686,679 / 827 | 29,701,348 / 827 | +14,669 / 0 |
+| Hand-maintained docs CSS (lines / bytes) | 406 / 23,456 | 403 / 23,752 | −3 / +296 |
+| Generated role adapter (bytes) | 1,046 | 0 | −1,046 |
+| Repo config/workaround files | `.npmrc`, `hig-roles.generated.css` | none | −2 |
+
+CSS grows because HIG's own stylesheet now carries the role variables, density/motion selectors and Callout severities
+the docs used to generate or patch locally; JS grows by the recipe modules. Hand-written CSS bytes rise slightly
+because the package role names are longer. The repository carries about the same tarball bytes as before only
+because the OG renderer still needs two static TTFs, which now come from the native-fonts package instead of the web
+tokens package. Full numbers: [`hig-872906a9/size-report.json`](hig-872906a9/size-report.json); screenshots before/after
+at 1440×1000 and 390×844 are in [`hig-872906a9/`](hig-872906a9/) (visually equivalent; buttons are 4 px shorter on
+desktop per hig#80). Validation: [`hig-872906a9/validation.txt`](hig-872906a9/validation.txt).
+
+## Measured before/after (step 2)
 
 Baseline `c4dff80` (main), same Node 24 / Next 16.3.6 / `/chart` export settings. Full numbers are in
 [`hig-7c67a07/size-report.json`](hig-7c67a07/size-report.json); reproduce with
@@ -69,14 +116,17 @@ copy guards) and `docs:test` (browser checks).
 
 | Repository/surface | Applicability | Version | Owner | Status | Residual work / review date |
 | --- | --- | --- | --- | --- | --- |
-| chart / apps/docs | SDK docs shell and embedded DOM chart | HIG 0.1.0-candidate.sha7c67a07c (tokens, contracts, react); Font 0.1.2 | Chart maintainers | Migrated to HIG packages; automated + browser evidence | Physical touch/mobile chart gestures, VoiceOver/TalkBack, iOS/Android hardware; stable HIG publication; 2026-11-13 |
+| chart / apps/docs | SDK docs shell and embedded DOM chart | HIG 0.1.0-candidate.sha872906a9 (tokens, contracts, react; native-fonts build-time); Font 0.1.2 | Chart maintainers | Migrated to HIG packages; automated + browser evidence | Physical touch/mobile chart gestures, VoiceOver/TalkBack, iOS/Android hardware; stable HIG publication; 2026-11-13 |
 | chart / published SDK | Engine and optional DOM UI | SDK 0.3.0 | Chart maintainers | Unchanged; standalone themes stay host-configurable | Product owners configure the HIG theme in their host; 2026-11-13 |
 
 Exceptions (Chart maintainers, review 2026-11-13):
 
-- `.npmrc` `legacy-peer-deps=true`: HIG React's optional exact Radix peers conflict with Fumadocs.
-- `InstallCommand`/`CodeWindow` recipes are composed from HIG primitives because the `recipes` entry pulls
-  the overlay entry's Radix dropdown-menu/tooltip peers. Upstream follow-up: split that entry.
+- Resolved in step 3: `.npmrc` `legacy-peer-deps=true`, primitive-composed `InstallCommand`/`CodeWindow`,
+  generated role CSS, the button font bridge and local Callout edges.
+- Regenerating `package-lock.json` from scratch without `--legacy-peer-deps` drops the `packages/chart` workspace
+  (npm 11.6; `@bloxwap/hig-react`'s optional `@bloxwap/chart` peer). `npm ci` and normal installs are unaffected;
+  see [vendor/bloxwap-hig](../../vendor/bloxwap-hig/README.md#notes).
+- `@bloxwap/hig-native-fonts` (4.4 MB tarball) is vendored only for two static OG faces.
 - Decorative OG brand-spectrum colors (purple/pink/yellow) are illustration assets, not a control palette.
 - HIG tokens are unpublished candidates, vendored as packed tarballs with SHA-256 provenance.
 
@@ -84,5 +134,5 @@ The canonical org matrix lives in `bloxwap/hig/planning/adoption.json`; HIG main
 PR link and version there. This repository does not close or declare hig#34 complete; the other four
 SDK docs rows and physical/assistive-technology review stay open upstream.
 
-Rollback: revert this migration's commits together (tarballs, lockfile, `.npmrc`, imports and CSS). There
+Rollback: revert the step's commits together (tarballs, lockfile, `.npmrc`, imports and CSS). There
 are no storage or schema changes. No release, tag, npm publish or manual deployment is part of it.
diff --git a/docs/hig-adoption/hig-872906a9/after-configuration-desktop.png b/docs/hig-adoption/hig-872906a9/after-configuration-desktop.png
new file mode 100644
index 0000000..31256f8
Binary files /dev/null and b/docs/hig-adoption/hig-872906a9/after-configuration-desktop.png differ
diff --git a/docs/hig-adoption/hig-872906a9/after-configuration-mobile.png b/docs/hig-adoption/hig-872906a9/after-configuration-mobile.png
new file mode 100644
index 0000000..e58ba93
Binary files /dev/null and b/docs/hig-adoption/hig-872906a9/after-configuration-mobile.png differ
diff --git a/docs/hig-adoption/hig-872906a9/after-getting-started-desktop.png b/docs/hig-adoption/hig-872906a9/after-getting-started-desktop.png
new file mode 100644
index 0000000..5163c94
Binary files /dev/null and b/docs/hig-adoption/hig-872906a9/after-getting-started-desktop.png differ
diff --git a/docs/hig-adoption/hig-872906a9/after-getting-started-mobile.png b/docs/hig-adoption/hig-872906a9/after-getting-started-mobile.png
new file mode 100644
index 0000000..eb29727
Binary files /dev/null and b/docs/hig-adoption/hig-872906a9/after-getting-started-mobile.png differ
diff --git a/docs/hig-adoption/hig-872906a9/after-home-desktop.png b/docs/hig-adoption/hig-872906a9/after-home-desktop.png
new file mode 100644
index 0000000..f876c38
Binary files /dev/null and b/docs/hig-adoption/hig-872906a9/after-home-desktop.png differ
diff --git a/docs/hig-adoption/hig-872906a9/after-home-mobile.png b/docs/hig-adoption/hig-872906a9/after-home-mobile.png
new file mode 100644
index 0000000..53caf51
Binary files /dev/null and b/docs/hig-adoption/hig-872906a9/after-home-mobile.png differ
diff --git a/docs/hig-adoption/hig-872906a9/after-playground-desktop.png b/docs/hig-adoption/hig-872906a9/after-playground-desktop.png
new file mode 100644
index 0000000..0d8970e
Binary files /dev/null and b/docs/hig-adoption/hig-872906a9/after-playground-desktop.png differ
diff --git a/docs/hig-adoption/hig-872906a9/after-playground-mobile.png b/docs/hig-adoption/hig-872906a9/after-playground-mobile.png
new file mode 100644
index 0000000..b3aec71
Binary files /dev/null and b/docs/hig-adoption/hig-872906a9/after-playground-mobile.png differ
diff --git a/docs/hig-adoption/hig-872906a9/before-configuration-desktop.png b/docs/hig-adoption/hig-872906a9/before-configuration-desktop.png
new file mode 100644
index 0000000..31256f8
Binary files /dev/null and b/docs/hig-adoption/hig-872906a9/before-configuration-desktop.png differ
diff --git a/docs/hig-adoption/hig-872906a9/before-configuration-mobile.png b/docs/hig-adoption/hig-872906a9/before-configuration-mobile.png
new file mode 100644
index 0000000..e58ba93
Binary files /dev/null and b/docs/hig-adoption/hig-872906a9/before-configuration-mobile.png differ
diff --git a/docs/hig-adoption/hig-872906a9/before-getting-started-desktop.png b/docs/hig-adoption/hig-872906a9/before-getting-started-desktop.png
new file mode 100644
index 0000000..5163c94
Binary files /dev/null and b/docs/hig-adoption/hig-872906a9/before-getting-started-desktop.png differ
diff --git a/docs/hig-adoption/hig-872906a9/before-getting-started-mobile.png b/docs/hig-adoption/hig-872906a9/before-getting-started-mobile.png
new file mode 100644
index 0000000..eb29727
Binary files /dev/null and b/docs/hig-adoption/hig-872906a9/before-getting-started-mobile.png differ
diff --git a/docs/hig-adoption/hig-872906a9/before-home-desktop.png b/docs/hig-adoption/hig-872906a9/before-home-desktop.png
new file mode 100644
index 0000000..9343737
Binary files /dev/null and b/docs/hig-adoption/hig-872906a9/before-home-desktop.png differ
diff --git a/docs/hig-adoption/hig-872906a9/before-home-mobile.png b/docs/hig-adoption/hig-872906a9/before-home-mobile.png
new file mode 100644
index 0000000..3f902ec
Binary files /dev/null and b/docs/hig-adoption/hig-872906a9/before-home-mobile.png differ
diff --git a/docs/hig-adoption/hig-872906a9/before-playground-desktop.png b/docs/hig-adoption/hig-872906a9/before-playground-desktop.png
new file mode 100644
index 0000000..e0d1c42
Binary files /dev/null and b/docs/hig-adoption/hig-872906a9/before-playground-desktop.png differ
diff --git a/docs/hig-adoption/hig-872906a9/before-playground-mobile.png b/docs/hig-adoption/hig-872906a9/before-playground-mobile.png
new file mode 100644
index 0000000..bf94c6a
Binary files /dev/null and b/docs/hig-adoption/hig-872906a9/before-playground-mobile.png differ
diff --git a/docs/hig-adoption/hig-872906a9/browser.json b/docs/hig-adoption/hig-872906a9/browser.json
new file mode 100644
index 0000000..c58201c
--- /dev/null
+++ b/docs/hig-adoption/hig-872906a9/browser.json
@@ -0,0 +1,37 @@
+{
+  "browser": "155.0.8059.41",
+  "checks": [
+    "Install tabs: arrows/Home/End, selected state and focus; one tab stop per install command",
+    "Copy: pending suppresses repeat activation, success status, visible denial and recovery",
+    "@bloxwap/hig-tokens palette, HIG controls in Bloxwap typography, no legacy aliases, reduced motion",
+    "Distinctive demo: canvas/worker assets, offline synthetic bars, HIG chrome, locked dark theme, settings/Escape, pointer/wheel",
+    "Static search index, results and keyboard dismissal",
+    "HIG callout (not announced), keyboard focus ring, search shortcut and empty results",
+    "Search index failure shows the HIG error alert with recovery text",
+    "Representative docs: RTL and 200% root text without page overflow",
+    "Touch emulation: 48px copy target, narrow layout and mobile navigation"
+  ],
+  "demo": {
+    "stats": {
+      "symbol": "Random walk",
+      "intervalMs": 900000,
+      "bars": 500,
+      "duplicates": 0,
+      "missingBuckets": 0,
+      "exhausted": false,
+      "loading": false,
+      "renderBackend": "webgl2",
+      "bookLevels": {
+        "bids": 50,
+        "asks": 50
+      },
+      "error": null,
+      "firstTime": 1791639900,
+      "lastTime": 1792089000
+    },
+    "light": false,
+    "font": "\"Bloxwap Sans\", system-ui, sans-serif",
+    "panel": "oklch(0.205 0 0)",
+    "themeButtons": 0
+  }
+}
diff --git a/docs/hig-adoption/hig-872906a9/size-report.json b/docs/hig-adoption/hig-872906a9/size-report.json
new file mode 100644
index 0000000..f2879e8
--- /dev/null
+++ b/docs/hig-adoption/hig-872906a9/size-report.json
@@ -0,0 +1,75 @@
+{
+  "baselineCommit": "a3b0f6b",
+  "hig": {
+    "commit": "872906a9071469af8714b3b6fb5cc7bd64d2d6f3",
+    "version": "0.1.0-candidate.sha872906a9"
+  },
+  "toolchain": {
+    "node": "v24.10.0",
+    "next": "16.3.6",
+    "basePath": "/chart"
+  },
+  "measurement": "uncompressed bytes of the complete static export (not transfer size or per-route bundles)",
+  "export": {
+    "before": {
+      "css": 152712,
+      "javascript": 2188721,
+      "fonts": 14205712,
+      "total": 29686679,
+      "files": 827
+    },
+    "after": {
+      "css": 163176,
+      "javascript": 2191579,
+      "fonts": 14205712,
+      "total": 29701348,
+      "files": 827
+    },
+    "delta": {
+      "css": 10464,
+      "javascript": 2858,
+      "fonts": 0,
+      "total": 14669,
+      "files": 0
+    }
+  },
+  "docsCss": {
+    "handWritten": {
+      "before": {
+        "files": 1,
+        "lines": 406,
+        "bytes": 23456
+      },
+      "after": {
+        "files": 1,
+        "lines": 403,
+        "bytes": 23752
+      }
+    },
+    "generatedAdapterBytes": {
+      "before": 1046,
+      "after": 0
+    }
+  },
+  "removedDocsFiles": {
+    "count": 1,
+    "bytes": 1046,
+    "files": [
+      "apps/docs/styles/hig-roles.generated.css"
+    ]
+  },
+  "dependencies": {
+    "docsDirect": {
+      "before": 19,
+      "after": 20
+    },
+    "lockfilePackages": {
+      "before": 403,
+      "after": 404,
+      "added": [
+        "node_modules/@bloxwap/hig-native-fonts"
+      ],
+      "removed": []
+    }
+  }
+}
diff --git a/docs/hig-adoption/hig-872906a9/validation.txt b/docs/hig-adoption/hig-872906a9/validation.txt
new file mode 100644
index 0000000..c6afe1e
--- /dev/null
+++ b/docs/hig-adoption/hig-872906a9/validation.txt
@@ -0,0 +1,40 @@
+Chart docs HIG candidate refresh (step 3), validation record (2026-10-15)
+
+Environment: macOS (Darwin 27.2), Node v24.10.0, npm 11.6.1, Next 16.3.6, Google Chrome 155.0.8059.41
+driven by Playwright (playwright-core, channel "chrome"). Evidence kind: automated + browser. No iOS
+Simulator, Android emulator or physical device was used; none is claimed.
+HIG: bloxwap/hig@872906a9071469af8714b3b6fb5cc7bd64d2d6f3, 0.1.0-candidate.sha872906a9 tarballs built locally
+with npm ci && npm run build && npm run generate:fonts && npm run release:dry-run -- --channel candidate
+(dry run: published false, no tag/release).
+Baseline: bloxwap/chart@a3b0f6b (main, HIG 7c67a07), built with the same toolchain and settings.
+
+Env for builds: NEXT_PUBLIC_BASE_PATH=/chart NEXT_PUBLIC_SITE_URL=https://bloxwap.github.io NEXT_TELEMETRY_DISABLED=1
+
+rm -rf node_modules && npm ci   exit 0, no .npmrc / legacy-peer-deps; npm ls shows @bloxwap/chart -> ./packages/chart,
+                                no invalid/missing entries; lockfile 404 packages, 34 Radix, 0 nested
+npm run docs:build          exit 0 - Static export verified: 30 HTML pages, 1530 local links/assets,
+                            25 social cards, basePath "/chart"
+npm run docs:check          exit 0 (next typegen + tsc --noEmit)
+npm run lint                exit 0 - 137 icons clean; HIG 0.1.0-candidate.sha872906a9 tarball provenance,
+                            installed versions, package-owned roles and copy drift checks passed
+npm run typecheck           exit 0 (SDK tsc build + docs:check)
+npm test                    exit 0 - tests 1869, pass 1869, fail 0
+npm run docs:test           exit 0 (HIG_PREVIEW_PORT=5300), checks (see browser.json):
+  - Install tabs (HIG Tabs): ArrowRight/End/Home, aria-selected and roving focus; Tab from the selected
+    tab lands on the InstallCommand 
 (no tabpanel stop), then on "Copy install command" (hig#69)
+  - Copy: HIG InstallCommand recipe joins a pending write (1 clipboard call for 2 activations) then
+    "Copied"; code-window IconButton busy/disabled while pending; visible recovery text on denial
+  - Palette from @bloxwap/hig-tokens, reduced motion zeroes --hig-motion-duration-fast, HIG button's first
+    font family is a loaded Bloxwap face (no font bridge, hig#65)
+  - Distinctive demo (docs/playground): offline synthetic market, bars > 0, 0 duplicates, locked dark
+    theme, HIG panel color, settings dialog + Escape, pointer hover + wheel zoom
+  - Static search, Ctrl/Cmd+K, empty results, HIG error Alert when search.json fails
+  - HIG Callout on getting-started (warning, not a live region), visible keyboard focus ring
+  - RTL + 200% root text without horizontal overflow
+  - 390x844 touch emulation: >=48px copy target, no horizontal overflow, mobile sidebar navigation
+node apps/docs/scripts/capture-hig.mjs  before (baseline export, PORT 5302) and after (PORT 5303):
+  home, getting-started, api/configuration, playground at 1440x1000 and 390x844 (16 PNGs here)
+node apps/docs/scripts/measure-hig.mjs  a3b0f6b > size-report.json
+
+Not performed: physical touch/mobile chart gestures, iOS/Android hardware, VoiceOver/TalkBack,
+wide-gamut display review. Playwright touch emulation is not physical-device evidence.
diff --git a/package-lock.json b/package-lock.json
index a1eab26..525ab62 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -20,9 +20,9 @@
       "dependencies": {
         "@bloxwap/chart": "0.3.0",
         "@bloxwap/font": "0.1.2",
-        "@bloxwap/hig-contracts": "file:../../vendor/bloxwap-hig/bloxwap-hig-contracts-0.1.0-candidate.sha7c67a07c.tgz",
-        "@bloxwap/hig-react": "file:../../vendor/bloxwap-hig/bloxwap-hig-react-0.1.0-candidate.sha7c67a07c.tgz",
-        "@bloxwap/hig-tokens": "file:../../vendor/bloxwap-hig/bloxwap-hig-tokens-0.1.0-candidate.sha7c67a07c.tgz",
+        "@bloxwap/hig-contracts": "file:../../vendor/bloxwap-hig/bloxwap-hig-contracts-0.1.0-candidate.sha872906a9.tgz",
+        "@bloxwap/hig-react": "file:../../vendor/bloxwap-hig/bloxwap-hig-react-0.1.0-candidate.sha872906a9.tgz",
+        "@bloxwap/hig-tokens": "file:../../vendor/bloxwap-hig/bloxwap-hig-tokens-0.1.0-candidate.sha872906a9.tgz",
         "fumadocs-core": "16.15.14",
         "fumadocs-mdx": "15.4.5",
         "fumadocs-ui": "16.15.14",
@@ -32,6 +32,7 @@
         "react-dom": "^19.2.0"
       },
       "devDependencies": {
+        "@bloxwap/hig-native-fonts": "file:../../vendor/bloxwap-hig/bloxwap-hig-native-fonts-0.1.0-candidate.sha872906a9.tgz",
         "@tailwindcss/postcss": "^4.2.0",
         "@types/mdx": "^2.0.13",
         "@types/node": "^24.13.6",
@@ -94,26 +95,33 @@
       }
     },
     "node_modules/@bloxwap/hig-contracts": {
-      "version": "0.1.0-candidate.sha7c67a07c",
-      "resolved": "file:vendor/bloxwap-hig/bloxwap-hig-contracts-0.1.0-candidate.sha7c67a07c.tgz",
-      "integrity": "sha512-QJ2gdBNpzzDM8pmGM3Vtw0nPPSdCFGXFtZvAaRhGZmGRw4asG4MIcIhfceXJaa3StuweZsxD/DSwzKNl71MwmA==",
+      "version": "0.1.0-candidate.sha872906a9",
+      "resolved": "file:vendor/bloxwap-hig/bloxwap-hig-contracts-0.1.0-candidate.sha872906a9.tgz",
+      "integrity": "sha512-fSeW+NnmNdkChw0klhVaiG1531uXrayVGpRvQcAd+kzLWckrK0aORmqzIV1Rh7fawQUY6OBs6E3K5swhG/JsBA==",
+      "license": "MIT"
+    },
+    "node_modules/@bloxwap/hig-native-fonts": {
+      "version": "0.1.0-candidate.sha872906a9",
+      "resolved": "file:vendor/bloxwap-hig/bloxwap-hig-native-fonts-0.1.0-candidate.sha872906a9.tgz",
+      "integrity": "sha512-9+5Dn5wU2xTXqu7gOICxLluPlynz9eitVGSAspKMxk8IEzryXR9bkd6ur8Oo82Knes3ZjjYa0m1iBmy4+2BcQA==",
+      "dev": true,
       "license": "MIT"
     },
     "node_modules/@bloxwap/hig-react": {
-      "version": "0.1.0-candidate.sha7c67a07c",
-      "resolved": "file:vendor/bloxwap-hig/bloxwap-hig-react-0.1.0-candidate.sha7c67a07c.tgz",
-      "integrity": "sha512-muTFRY9Co49PO9ZmmMeZDNscjtpnLamaa/zSOOazoQjjoBIQVpqa13jJw9x1qguPNAGaV9AN4v/UlBfIPEwiQA==",
+      "version": "0.1.0-candidate.sha872906a9",
+      "resolved": "file:vendor/bloxwap-hig/bloxwap-hig-react-0.1.0-candidate.sha872906a9.tgz",
+      "integrity": "sha512-9lbojjexf0AEqrQesxhm8KBUBsLNgczPg/ISGhT7/bPznYpscTaIHsp0t/wn5zsiPxK8UlkOhGcab63zEZEisQ==",
       "license": "MIT",
       "dependencies": {
-        "@bloxwap/hig-contracts": "0.1.0-candidate.sha7c67a07c",
-        "@bloxwap/hig-tokens": "0.1.0-candidate.sha7c67a07c"
+        "@bloxwap/hig-contracts": "0.1.0-candidate.sha872906a9",
+        "@bloxwap/hig-tokens": "0.1.0-candidate.sha872906a9"
       },
       "peerDependencies": {
         "@bloxwap/chart": "^0.3.0",
-        "@radix-ui/react-dialog": "1.1.15",
-        "@radix-ui/react-dropdown-menu": "2.1.16",
-        "@radix-ui/react-popover": "1.1.15",
-        "@radix-ui/react-tooltip": "1.2.8",
+        "@radix-ui/react-dialog": "^1.1.15",
+        "@radix-ui/react-dropdown-menu": "^2.1.16",
+        "@radix-ui/react-popover": "^1.1.15",
+        "@radix-ui/react-tooltip": "^1.2.8",
         "react": "^19.1.0",
         "react-dom": "^19.1.0"
       },
@@ -136,9 +144,9 @@
       }
     },
     "node_modules/@bloxwap/hig-tokens": {
-      "version": "0.1.0-candidate.sha7c67a07c",
-      "resolved": "file:vendor/bloxwap-hig/bloxwap-hig-tokens-0.1.0-candidate.sha7c67a07c.tgz",
-      "integrity": "sha512-GEgLcN1IQgqJfMvfmK88tn0B8y5ixGbzjnj7WO+oN9IHXJ7GC1TM7FQwDssanOaxrpDJisBLURR99WwE+rEHGw==",
+      "version": "0.1.0-candidate.sha872906a9",
+      "resolved": "file:vendor/bloxwap-hig/bloxwap-hig-tokens-0.1.0-candidate.sha872906a9.tgz",
+      "integrity": "sha512-EX6mTsJP+YVzDknQNIoFzWwjNsCwm1WVhkJQ6GZ3h6xn9+ylh5itGnQ5MlrlQxba72SXyLMGwDckimnpFYvvKg==",
       "license": "MIT"
     },
     "node_modules/@esbuild/aix-ppc64": {
@@ -1523,6 +1531,7 @@
       "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.23.tgz",
       "integrity": "sha512-Ksw4WeROkO4rC9k/onilX/Ao2Cr1ku1unMNH+XSCcP4jSXYu7HDsg9n4ojMjVb22XpYjAQ9qfrFlVbru1vXDUA==",
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "@radix-ui/primitive": "1.1.7",
         "@radix-ui/react-compose-refs": "1.1.5",
@@ -1696,6 +1705,7 @@
       "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.23.tgz",
       "integrity": "sha512-mw58MrBlyHWFisTOYignD0vf/3gdcgAR+9of1s9G/38CbFiUwH1nCDkc0AUM9IrXFgN5Ue8n45j9WCgyM1sbiQ==",
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "@radix-ui/primitive": "1.1.7",
         "@radix-ui/react-compose-refs": "1.1.5",
@@ -2574,6 +2584,7 @@
       "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz",
       "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==",
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "@types/unist": "*"
       }
@@ -2582,7 +2593,8 @@
       "version": "2.0.14",
       "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.14.tgz",
       "integrity": "sha512-T48PeuJtvLosNTPVhfnIp3i/n3a4g4Bad7YCq5k64D4u7NwDrAotikQ+5+sjtUvBmxCMlbo3dVL+C2dP0rWHzg==",
-      "license": "MIT"
+      "license": "MIT",
+      "peer": true
     },
     "node_modules/@types/ms": {
       "version": "2.1.0",
@@ -2604,8 +2616,9 @@
       "version": "19.3.0",
       "resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz",
       "integrity": "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==",
-      "dev": true,
+      "devOptional": true,
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "csstype": "^3.2.2"
       }
@@ -2614,8 +2627,9 @@
       "version": "19.3.0",
       "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.3.0.tgz",
       "integrity": "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==",
-      "dev": true,
+      "devOptional": true,
       "license": "MIT",
+      "peer": true,
       "peerDependencies": {
         "@types/react": "^19.3.0"
       }
@@ -2816,6 +2830,7 @@
       "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz",
       "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==",
       "license": "MIT",
+      "peer": true,
       "bin": {
         "acorn": "bin/acorn"
       },
@@ -3042,7 +3057,7 @@
       "version": "3.2.3",
       "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
       "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
-      "dev": true,
+      "devOptional": true,
       "license": "MIT"
     },
     "node_modules/debug": {
@@ -3378,6 +3393,7 @@
       "resolved": "https://registry.npmjs.org/fumadocs-core/-/fumadocs-core-16.15.14.tgz",
       "integrity": "sha512-2Jov3x/wCkz8C8MWpmWHoWyLn+pBNrER49m35qqAV0OZekVE8mgs71TS6OfgJHzO00jICkIB+NFhv/XQx/yf/Q==",
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "@fumari/image-size": "^0.1.1",
         "estree-util-value-to-estree": "^3.5.0",
@@ -4210,6 +4226,7 @@
       "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.545.0.tgz",
       "integrity": "sha512-7r1/yUuflQDSt4f1bpn5ZAocyIxcTyVyBBChSVtBKn5M+392cPmI5YJMWOJKk/HUWGm5wg83chlAZtCcGbEZtw==",
       "license": "ISC",
+      "peer": true,
       "peerDependencies": {
         "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
       }
@@ -5337,6 +5354,7 @@
       "resolved": "https://registry.npmjs.org/next/-/next-16.3.6.tgz",
       "integrity": "sha512-L+otWM/aQbYTx98aZhgEoMb4bZAXx1YVW4UMA/vuCyCoWG5HJyZUili8QAkqzrcC+5///tsz3s0M+SlyB5bLMw==",
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "@next/env": "16.3.6",
         "@swc/helpers": "0.5.23",
@@ -5564,6 +5582,7 @@
       "resolved": "https://registry.npmjs.org/react/-/react-19.3.0.tgz",
       "integrity": "sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==",
       "license": "MIT",
+      "peer": true,
       "engines": {
         "node": ">=0.10.0"
       }
@@ -5573,6 +5592,7 @@
       "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.3.0.tgz",
       "integrity": "sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q==",
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "scheduler": "^0.28.0"
       },
@@ -6063,8 +6083,9 @@
       "version": "4.3.3",
       "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz",
       "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==",
-      "dev": true,
-      "license": "MIT"
+      "devOptional": true,
+      "license": "MIT",
+      "peer": true
     },
     "node_modules/tapable": {
       "version": "2.3.3",
diff --git a/vendor/bloxwap-hig/README.md b/vendor/bloxwap-hig/README.md
index 12790b8..ecb2882 100644
--- a/vendor/bloxwap-hig/README.md
+++ b/vendor/bloxwap-hig/README.md
@@ -4,31 +4,38 @@
 `file:` dependencies (`apps/docs/package.json`); no HIG source, component folders, palettes, fonts or
 feedback engines are copied into this repository.
 
-| Tarball | Used for |
-| --- | --- |
-| `bloxwap-hig-tokens-0.1.0-candidate.sha7c67a07c.tgz` | `@bloxwap/hig-tokens/css` (shell and chart demo), typed manifest (chart theme, generated roles, OG colors), static OG font faces |
-| `bloxwap-hig-contracts-0.1.0-candidate.sha7c67a07c.tgz` | runtime dependency of `@bloxwap/hig-react` |
-| `bloxwap-hig-react-0.1.0-candidate.sha7c67a07c.tgz` | `@bloxwap/hig-react/css`, `button`, `navigation`, `surface`, `status` entries |
+| Tarball | Bytes | Used for |
+| --- | ---: | --- |
+| `bloxwap-hig-tokens-0.1.0-candidate.sha872906a9.tgz` | 8,108 | `@bloxwap/hig-tokens/css` (palette plus radius/type/motion/density and font roles) for the shell and chart demo; typed manifest (chart theme, OG colors) |
+| `bloxwap-hig-contracts-0.1.0-candidate.sha872906a9.tgz` | 6,119 | runtime dependency of `@bloxwap/hig-react` |
+| `bloxwap-hig-react-0.1.0-candidate.sha872906a9.tgz` | 20,287 | `@bloxwap/hig-react/css`, `button`, `navigation`, `surface`, `status`, `recipes/install-command`, `recipes/code-window` entries |
+| `bloxwap-hig-native-fonts-0.1.0-candidate.sha872906a9.tgz` | 4,420,375 | build-time only (devDependency): static `BloxwapSans-{Bold,Black}.ttf` for the Satori social-card renderer |
 
-Provenance: [bloxwap/hig@7c67a07](https://github.com/bloxwap/hig/tree/7c67a07ce20c02264b0fdc57e5f27d020cfd6fdc),
-built with `npm ci && npm run build && npm run release:dry-run -- --channel candidate` (plus
-`npm run generate:fonts` for the native TTFs the dry run packs). SHA-256 hashes are in `provenance.json`
-and checked by `npm run lint`. MIT licenses travel inside each tarball.
+Provenance: [bloxwap/hig@872906a](https://github.com/bloxwap/hig/tree/872906a9071469af8714b3b6fb5cc7bd64d2d6f3),
+built with `npm ci && npm run build && npm run generate:fonts && npm run release:dry-run -- --channel candidate`
+(`generate:fonts` needs fonttools 4.61.1 and brotli; it produces the faces packed into
+`@bloxwap/hig-native-fonts`). The dry run reported `published: false`, no tags or releases. SHA-256 hashes
+are in `provenance.json` and checked by `npm run lint`. MIT/OFL licenses travel inside each tarball.
 
 ## Notes
 
-- `.npmrc` sets `legacy-peer-deps=true`. `@bloxwap/hig-react` declares optional exact Radix peers
-  (`react-dialog`/`react-popover` 1.1.15) that conflict with Fumadocs' 1.1.23. Without the flag npm
-  nests a second Radix tree and drops the `packages/chart` workspace link in favor of the registry
-  build. The docs import no HIG overlay entry, so the peers are unused.
-- `@bloxwap/hig-react/recipes` imports the overlay entry, which needs `@radix-ui/react-dropdown-menu`
-  and `@radix-ui/react-tooltip`. Instead of adding those, the docs compose the `InstallCommand` and
-  `CodeWindow` recipe markup from the `button`/`surface` primitives. Switch to the recipe imports when
-  HIG ships them without overlay peers.
+- No `.npmrc` override. Since hig#63 the optional Radix peers are caret ranges, so `npm ci` installs
+  next to Fumadocs with one Radix tree (34 lockfile Radix entries, none nested) and keeps the
+  `packages/chart` workspace link.
+- Known npm 11.6 behaviour: regenerating the lockfile **from scratch** (deleting `package-lock.json`)
+  without `--legacy-peer-deps` drops the `packages/chart` workspace, because `@bloxwap/hig-react` declares an
+  optional `@bloxwap/chart` peer with the same name. `npm ci`, `npm install` against the committed lock and
+  adding dependencies are unaffected. If you must regenerate the lock, run
+  `npm install --legacy-peer-deps` once, then `npm install` (which reports "up to date") and confirm
+  `npm ls @bloxwap/chart` shows `-> ./packages/chart`.
+- `@bloxwap/hig-tokens` no longer ships native TTFs (hig#64). Satori reads TTF/OTF/WOFF, not the variable
+  WOFF2 in `@bloxwap/font`, so the social cards take their two static faces from `@bloxwap/hig-native-fonts`
+  as the HIG native migration guide recommends. That tarball is not imported by any page and nothing from it
+  reaches the static export except the rendered PNG cards.
 
 ## Upgrade or roll back
 
-Rebuild the tarballs from a reviewed HIG commit, replace them here, update `provenance.json` and the
-three `file:` specs, run `npm install`, `node apps/docs/scripts/hig-tokens.mjs --write`, then
-`npm run lint`, `npm run typecheck`, the docs build and `npm run docs:test`. Rolling back is an atomic
-revert of the commit that changed this folder, the lockfile and the docs imports together.
+Rebuild the tarballs from a reviewed HIG commit, replace them here, update `provenance.json` and the four
+`file:` specs, run `npm install`, then `npm run lint`, `npm run typecheck`, the docs build and
+`npm run docs:test`. Rolling back is an atomic revert of the commit that changed this folder, the lockfile and
+the docs imports together.
diff --git a/vendor/bloxwap-hig/bloxwap-hig-contracts-0.1.0-candidate.sha7c67a07c.tgz b/vendor/bloxwap-hig/bloxwap-hig-contracts-0.1.0-candidate.sha7c67a07c.tgz
deleted file mode 100644
index 751a262..0000000
Binary files a/vendor/bloxwap-hig/bloxwap-hig-contracts-0.1.0-candidate.sha7c67a07c.tgz and /dev/null differ
diff --git a/vendor/bloxwap-hig/bloxwap-hig-contracts-0.1.0-candidate.sha872906a9.tgz b/vendor/bloxwap-hig/bloxwap-hig-contracts-0.1.0-candidate.sha872906a9.tgz
new file mode 100644
index 0000000..d53be99
Binary files /dev/null and b/vendor/bloxwap-hig/bloxwap-hig-contracts-0.1.0-candidate.sha872906a9.tgz differ
diff --git a/vendor/bloxwap-hig/bloxwap-hig-native-fonts-0.1.0-candidate.sha872906a9.tgz b/vendor/bloxwap-hig/bloxwap-hig-native-fonts-0.1.0-candidate.sha872906a9.tgz
new file mode 100644
index 0000000..5282b59
Binary files /dev/null and b/vendor/bloxwap-hig/bloxwap-hig-native-fonts-0.1.0-candidate.sha872906a9.tgz differ
diff --git a/vendor/bloxwap-hig/bloxwap-hig-react-0.1.0-candidate.sha7c67a07c.tgz b/vendor/bloxwap-hig/bloxwap-hig-react-0.1.0-candidate.sha7c67a07c.tgz
deleted file mode 100644
index 0c3b865..0000000
Binary files a/vendor/bloxwap-hig/bloxwap-hig-react-0.1.0-candidate.sha7c67a07c.tgz and /dev/null differ
diff --git a/vendor/bloxwap-hig/bloxwap-hig-react-0.1.0-candidate.sha872906a9.tgz b/vendor/bloxwap-hig/bloxwap-hig-react-0.1.0-candidate.sha872906a9.tgz
new file mode 100644
index 0000000..99aafeb
Binary files /dev/null and b/vendor/bloxwap-hig/bloxwap-hig-react-0.1.0-candidate.sha872906a9.tgz differ
diff --git a/vendor/bloxwap-hig/bloxwap-hig-tokens-0.1.0-candidate.sha7c67a07c.tgz b/vendor/bloxwap-hig/bloxwap-hig-tokens-0.1.0-candidate.sha7c67a07c.tgz
deleted file mode 100644
index c30feb2..0000000
Binary files a/vendor/bloxwap-hig/bloxwap-hig-tokens-0.1.0-candidate.sha7c67a07c.tgz and /dev/null differ
diff --git a/vendor/bloxwap-hig/bloxwap-hig-tokens-0.1.0-candidate.sha872906a9.tgz b/vendor/bloxwap-hig/bloxwap-hig-tokens-0.1.0-candidate.sha872906a9.tgz
new file mode 100644
index 0000000..3473134
Binary files /dev/null and b/vendor/bloxwap-hig/bloxwap-hig-tokens-0.1.0-candidate.sha872906a9.tgz differ
diff --git a/vendor/bloxwap-hig/provenance.json b/vendor/bloxwap-hig/provenance.json
index b9fc6dc..d28d7a9 100644
--- a/vendor/bloxwap-hig/provenance.json
+++ b/vendor/bloxwap-hig/provenance.json
@@ -1,12 +1,13 @@
 {
   "repository": "bloxwap/hig",
-  "commit": "7c67a07ce20c02264b0fdc57e5f27d020cfd6fdc",
-  "version": "0.1.0-candidate.sha7c67a07c",
-  "producedBy": "npm ci && npm run build && npm run release:dry-run -- --channel candidate (Node 22.22.0; native TTFs generated with scripts/generate-native-fonts.py, fonttools 4.61.1)",
+  "commit": "872906a9071469af8714b3b6fb5cc7bd64d2d6f3",
+  "version": "0.1.0-candidate.sha872906a9",
+  "producedBy": "npm ci && npm run build && npm run generate:fonts && npm run release:dry-run -- --channel candidate (Node 22.22.0, npm 11.6.1; fonttools 4.61.1 + brotli for the native faces in @bloxwap/hig-native-fonts)",
   "distribution": "unpublished candidate tarballs; no npm release, tag or GitHub release exists",
   "sha256": {
-    "bloxwap-hig-contracts-0.1.0-candidate.sha7c67a07c.tgz": "4519befc8d35c787b2f91302cf46801c82295f6dd33cbf84331be11308d71ef7",
-    "bloxwap-hig-react-0.1.0-candidate.sha7c67a07c.tgz": "6f3d7cc721ff246c647a2479ba39e898f4ba9d2ba75177b800a9942f5dab11f9",
-    "bloxwap-hig-tokens-0.1.0-candidate.sha7c67a07c.tgz": "701750f9b85f148533995d9b603f2ea1a6acbe31ac475ac7f8ecce8cd3cd61dc"
+    "bloxwap-hig-contracts-0.1.0-candidate.sha872906a9.tgz": "ca8d5e0fe7e6138c056e9b45182e664c69d7217295264dff56a09290acabd352",
+    "bloxwap-hig-native-fonts-0.1.0-candidate.sha872906a9.tgz": "2de088737f6f76c594e95de237342dbe4457387ed9134628ef12049266195f77",
+    "bloxwap-hig-react-0.1.0-candidate.sha872906a9.tgz": "5835fea5e4f701815fb4152cf079901d0f8af634ef3fbb537b40fc48500670c4",
+    "bloxwap-hig-tokens-0.1.0-candidate.sha872906a9.tgz": "62b4007b5e06ee571352f8a33bd7e14025745fe42234d8532092739ff49f6196"
   }
 }