diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index cdf4f77..21f9e67 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -3,9 +3,9 @@ name: Documentation
on:
push:
branches: [main]
- paths: ['apps/**', 'packages/**', 'package.json', 'package-lock.json', '.github/workflows/docs.yml']
+ paths: ['apps/**', 'packages/**', 'vendor/**', '.npmrc', 'package.json', 'package-lock.json', '.github/workflows/docs.yml']
pull_request:
- paths: ['apps/**', 'packages/**', 'package.json', 'package-lock.json', '.github/workflows/docs.yml']
+ paths: ['apps/**', 'packages/**', 'vendor/**', '.npmrc', 'package.json', 'package-lock.json', '.github/workflows/docs.yml']
workflow_dispatch:
permissions:
diff --git a/.gitignore b/.gitignore
index fcdd193..e5d0363 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,3 +11,4 @@ apps/docs/next-env.d.ts
apps/docs/public/chart-demo/
*.tgz
packages/chart/assets/icons/preview.html
+!vendor/bloxwap-hig/*.tgz
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 0000000..c46cf20
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1,4 @@
+# @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 45639bb..d290747 100644
--- a/apps/docs/README.md
+++ b/apps/docs/README.md
@@ -25,24 +25,24 @@ 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 are local repository assets, so builds do not fetch Google Fonts.
-
-The design follows `bloxwap/src/monorepo/workers/docs`: dark surfaces, green
-accents, Bloxwap Sans body copy, Space Grotesk headings, and Bloxwap Mono code. The theme
-is intentionally dark, matching the main Bloxwap docs.
-
-- `styles/tokens.generated.css` is a vendored copy of the monorepo's
- `packages/ui/src/styles/tokens.generated.css`. Update it from that source;
- do not hand-edit generated tokens.
-- `app/global.css` maps those tokens to Fumadocs and the playground wrapper,
- using the same typography and navigation rules as the monorepo docs. The
- embedded demo keeps the library's own appearance and controls.
+`search.json`. Fonts come from the installed `@bloxwap/font` and `@bloxwap/hig-tokens` packages, 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).
+ `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.
+- 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.
- `public/logos/bloxwap-wordmark-white.svg` and `public/icon.svg` are the supplied
monorepo documentation artwork. Preserve the marks instead of typesetting them.
-- `fonts/README.md` records font sources and licenses.
-These copies keep this repository's GitHub Pages build independent of a sibling
-monorepo checkout.
## Social cards
@@ -94,7 +94,7 @@ 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.
-Static social-image font provenance is recorded in `fonts/README.md`.
+Social cards use the static Bloxwap Sans faces shipped in `@bloxwap/hig-tokens/fonts` (OFL).
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/developers/page.tsx b/apps/docs/app/developers/page.tsx
index 6d01b13..9e9364d 100644
--- a/apps/docs/app/developers/page.tsx
+++ b/apps/docs/app/developers/page.tsx
@@ -39,7 +39,7 @@ export default function Developers() {
DEVELOPER PORTAL
Build with theChart SDK.
Zero-dependency financial charts for your interface. Install the package, render your first candlestick chart in minutes, and let agents discover the API through the machine-readable resources below.
-
+
diff --git a/apps/docs/app/global.css b/apps/docs/app/global.css
index dc875fa..583db9b 100644
--- a/apps/docs/app/global.css
+++ b/apps/docs/app/global.css
@@ -1,8 +1,9 @@
@import 'tailwindcss';
@import 'fumadocs-ui/css/neutral.css';
@import 'fumadocs-ui/css/preset.css';
-@import '../vendor/hig/tokens.css';
-@import '../styles/hig.generated.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;
@@ -13,22 +14,22 @@
:root,
.dark,
.dark #nd-sidebar {
- --color-fd-background: var(--bloxwap-black);
- --color-fd-foreground: var(--foreground);
- --color-fd-card: var(--bloxwap-black);
- --color-fd-card-foreground: var(--foreground);
- --color-fd-popover: var(--popover);
- --color-fd-popover-foreground: var(--popover-foreground);
- --color-fd-muted: var(--muted);
- --color-fd-muted-foreground: var(--muted-foreground);
- --color-fd-secondary: var(--card);
- --color-fd-secondary-foreground: var(--foreground);
- --color-fd-border: var(--border);
- --color-fd-primary: var(--bloxwap-green);
- --color-fd-primary-foreground: var(--bloxwap-black);
- --color-fd-accent: var(--card);
- --color-fd-accent-foreground: var(--foreground);
- --color-fd-ring: var(--bloxwap-green);
+ --color-fd-background: var(--hig-background);
+ --color-fd-foreground: var(--hig-text);
+ --color-fd-card: var(--hig-background);
+ --color-fd-card-foreground: var(--hig-text);
+ --color-fd-popover: var(--hig-nested);
+ --color-fd-popover-foreground: var(--hig-text);
+ --color-fd-muted: var(--hig-nested);
+ --color-fd-muted-foreground: var(--hig-muted);
+ --color-fd-secondary: var(--hig-surface);
+ --color-fd-secondary-foreground: var(--hig-text);
+ --color-fd-border: var(--hig-border);
+ --color-fd-primary: var(--hig-action);
+ --color-fd-primary-foreground: var(--hig-background);
+ --color-fd-accent: var(--hig-surface);
+ --color-fd-accent-foreground: var(--hig-text);
+ --color-fd-ring: var(--hig-action);
}
html {
@@ -45,12 +46,12 @@ html > body[data-scroll-locked] {
}
::selection {
- background: color-mix(in srgb, var(--bloxwap-green) 25%, transparent);
- color: var(--foreground);
+ background: color-mix(in srgb, var(--hig-action) 25%, transparent);
+ color: var(--hig-text);
}
:focus-visible {
- outline: 2px solid var(--bloxwap-green);
+ outline: 2px solid var(--hig-action);
outline-offset: 3px;
}
@@ -71,8 +72,8 @@ html > body[data-scroll-locked] {
}
#nd-page > p {
- font-size: var(--type-body-size);
- line-height: var(--type-body-line);
+ font-size: var(--hig-type-body-size);
+ line-height: var(--hig-type-body-line);
}
.prose {
@@ -96,7 +97,7 @@ html > body[data-scroll-locked] {
}
.prose a:not([data-card]):hover {
- color: var(--bloxwap-green);
+ color: var(--hig-action);
}
.prose [data-card='true'] {
@@ -105,7 +106,7 @@ html > body[data-scroll-locked] {
}
.prose [data-card='true']:hover {
- border-color: color-mix(in srgb, var(--bloxwap-green) 40%, var(--border));
+ border-color: color-mix(in srgb, var(--hig-action) 40%, var(--hig-border));
}
.prose [data-card='true'] h3 {
@@ -114,12 +115,12 @@ html > body[data-scroll-locked] {
}
#nd-sidebar {
- border-color: var(--border);
+ border-color: var(--hig-border);
}
#nd-sidebar a[data-active='true'] {
- background: color-mix(in srgb, var(--bloxwap-green) 8%, transparent);
- color: var(--bloxwap-green);
+ background: color-mix(in srgb, var(--hig-action) 8%, transparent);
+ color: var(--hig-action);
}
/* Fumadocs' sidebar icon-link bar ships as a stroked box; with GitHub its only link, drop the box and keep the icon. */
@@ -129,7 +130,7 @@ html > body[data-scroll-locked] {
}
.docs-sidebar-footer {
- padding-top: var(--space-xs);
+ padding-top: var(--hig-space-xs);
}
.docs-app-link {
@@ -138,117 +139,92 @@ html > body[data-scroll-locked] {
justify-content: space-between;
min-height: 2.5rem;
padding: 0.5rem 0.75rem;
- border-radius: var(--radius-full);
- transition: background-color var(--duration-fast) var(--ease-spring), color var(--duration-fast) var(--ease-spring);
+ border-radius: var(--hig-radius-pill);
+ transition: background-color var(--hig-duration-fast) var(--ease-spring), color var(--hig-duration-fast) var(--ease-spring);
font-size: 0.875rem;
font-weight: 600;
}
.docs-app-link:hover {
- background: var(--card);
- color: var(--bloxwap-green);
+ background: var(--hig-surface);
+ color: var(--hig-action);
}
/*
- * Buttons: plain-UI's .btn family (www button.tsx: pill, h-12, text-base semibold, pressed scale).
- * The primary fill is brand green with black text, as in www's .nav-cta/.submit and the app's CTAs.
+ * 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.
*/
-.btn {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- gap: var(--space-sm);
- min-height: var(--control-h);
- min-width: var(--control-min-w);
- padding: 0 var(--space-xl);
- border: 1px solid transparent;
- border-radius: var(--radius-full);
- background: var(--bloxwap-green);
- color: var(--bloxwap-black);
- font: 600 var(--text-base) / 1 var(--font-sans);
- white-space: normal;
- overflow-wrap: anywhere;
- cursor: pointer;
- text-decoration: none;
- touch-action: manipulation;
- transition:
- background-color var(--duration-fast) var(--ease-spring),
- color var(--duration-fast) var(--ease-spring),
- border-color var(--duration-fast) var(--ease-spring),
- transform var(--duration-fast) var(--ease-spring);
-}
-.btn:hover { background: color-mix(in oklab, var(--bloxwap-green) 90%, transparent); color: var(--bloxwap-black); }
-.btn:active { transform: scale(0.98); }
-.btn:disabled, .btn[aria-disabled='true'] { opacity: 0.5; pointer-events: none; }
-.btn svg { flex: none; width: var(--icon-md); height: var(--icon-md); }
-.btn--secondary { background: var(--card); color: var(--foreground); }
-.btn--secondary:hover { background: var(--muted); color: var(--foreground); }
-.btn--ghost { background: transparent; color: var(--muted-foreground); min-width: 0; }
-.btn--ghost:hover { background: var(--card); color: var(--foreground); }
-.btn--sm { height: var(--control-h-sm); min-width: 5rem; padding: 0 var(--space-md); font-size: var(--text-sm); }
-.btn--ghost.btn--sm { min-width: 0; padding: 0 var(--space-sm); }
-.btn--icon { width: var(--control-h-sm); height: var(--control-h-sm); min-width: 0; padding: 0; }
+.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 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-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); }
+}
/* Chart-specific layout on the scale above; typography, surfaces, and states follow workers/docs. */
-.brand { display: inline-flex; align-items: center; gap: var(--space-md); }
+.brand { display: inline-flex; align-items: center; gap: var(--hig-space-md); }
.brand img { flex: none; width: 28px; height: 28px; }
-.brand-product { border-left: 1px solid var(--border); padding-left: var(--space-md); color: var(--muted-foreground); font-size: var(--text-sm); }
+.brand-product { border-left: 1px solid var(--hig-border); padding-left: var(--hig-space-md); color: var(--hig-muted); font-size: var(--text-sm); }
.hero h1, .principles h2, .start-grid h2, .guide-links h3, .not-found h1 {
font-family: var(--font-sans);
}
/* Same box as the Fumadocs header (max-w --fd-layout-width, 16px padding), so content lines up with the nav. */
-.home { width: 100%; max-width: var(--fd-layout-width, 1400px); padding: var(--space-5xl) var(--space-lg) 0; margin: 0 auto; }
-.hero { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); align-items: center; gap: var(--space-6xl); padding-bottom: var(--space-5xl); }
+.home { width: 100%; max-width: var(--fd-layout-width, 1400px); padding: var(--hig-space-section) var(--hig-space-lg) 0; margin: 0 auto; }
+.hero { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); align-items: center; gap: var(--hig-space-section); padding-bottom: var(--hig-space-section); }
.hero--single { grid-template-columns: minmax(0, 1fr); max-width: 47.5rem; }
.hero-copy { min-width: 0; }
-.eyebrow { display: flex; align-items: center; gap: var(--space-sm); color: var(--color-fd-muted-foreground); font: var(--text-2xs) / 1.5 var(--font-mono); letter-spacing: .09em; }
-.status-dot { width: 6px; height: 6px; border-radius: var(--radius-full); background: var(--color-fd-primary); }
-.hero h1 { margin: var(--space-xl) 0 0; font-size: clamp(var(--text-5xl), 4.4vw, 4rem); line-height: 1.07; font-weight: 600; letter-spacing: -.04em; }
+.eyebrow { display: flex; align-items: center; gap: var(--hig-space-sm); color: var(--color-fd-muted-foreground); font: var(--text-2xs) / 1.5 var(--font-mono); letter-spacing: .09em; }
+.status-dot { width: 6px; height: 6px; border-radius: var(--hig-radius-pill); background: var(--color-fd-primary); }
+.hero h1 { margin: var(--hig-space-xl) 0 0; font-size: clamp(var(--text-5xl), 4.4vw, 4rem); line-height: 1.07; font-weight: 600; letter-spacing: -.04em; }
.hero h1 span { color: var(--color-fd-foreground); }
-.hero-description { max-width: 35rem; margin-top: var(--space-xl); color: var(--color-fd-muted-foreground); line-height: 1.5; font-size: var(--text-lg); }
-.hero-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-md); margin: var(--space-2xl) 0 var(--space-xl); }
-.package-install { width: fit-content; max-width: 100%; margin-top: var(--space-2xl); }
-.install-tabs { display: inline-flex; max-width: 100%; gap: var(--space-xs); padding: var(--space-xs); border-radius: var(--radius-full); background: var(--card); }
-.install-tabs button {
- min-width: 0; height: 2rem; padding: 0 var(--space-lg); border-radius: var(--radius-full);
- color: var(--muted-foreground); font: 600 var(--text-sm) / 1 var(--font-mono); cursor: pointer;
- transition: background-color var(--duration-fast) var(--ease-spring), color var(--duration-fast) var(--ease-spring);
-}
-.install-tabs button:hover { color: var(--foreground); }
-.install-tabs button[aria-selected='true'] { background: var(--muted); color: var(--foreground); }
-.install-command {
- display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: var(--space-lg); min-width: 0; margin-top: var(--space-md);
- padding: var(--space-xs) var(--space-xs) var(--space-xs) var(--space-xl); border-radius: var(--radius-full);
- background: var(--card); font: 600 var(--text-sm) / 1.5 var(--font-mono);
-}
-.install-prompt { flex: none; color: var(--muted-foreground); user-select: none; }
-.install-command code { min-width: 0; overflow-wrap: anywhere; user-select: all; }
-.install-command .btn { margin-left: auto; }
-.install-platforms, .install-feedback { margin-top: var(--space-lg); color: var(--muted-foreground); font-size: var(--text-sm); line-height: 1.5; }
-.install-feedback { display: block; }
+.hero-description { max-width: 35rem; margin-top: var(--hig-space-xl); color: var(--color-fd-muted-foreground); line-height: 1.5; font-size: var(--text-lg); }
+.hero-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--hig-space-md); margin: var(--hig-space-xxl) 0 var(--hig-space-xl); }
+.package-install { width: fit-content; max-width: 100%; margin-top: var(--hig-space-xxl); }
+.package-install [role='tablist'] { gap: var(--hig-space-xs); }
+.package-install [role='tablist'] > .hig-button { min-width: 0; font-family: var(--font-mono); }
+.package-install [role='tablist'] > .hig-button[aria-selected='false'] { background: transparent; color: var(--hig-muted); }
+.package-install [role='tablist'] > .hig-button[aria-selected='true'] { background: var(--hig-nested); color: var(--hig-text); }
+.package-install [role='tabpanel'] { margin-top: var(--hig-space-md); border-radius: var(--hig-radius-xl); }
+/* HIG InstallCommand recipe: command and copy action share a row; the status spans it when present. */
+.package-install .hig-surface { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: var(--hig-space-sm) var(--hig-space-lg); padding: var(--hig-space-xs) var(--hig-space-xs) var(--hig-space-xs) var(--hig-space-xl); border-radius: var(--hig-radius-xl); }
+.package-install .hig-surface pre { margin: 0; overflow-wrap: anywhere; white-space: pre-wrap; font: 600 var(--text-sm) / 1.5 var(--font-mono); }
+.package-install .hig-surface pre::before { content: '$ '; color: var(--hig-muted); user-select: none; }
+.package-install .hig-surface [role='status'] { grid-column: 1 / -1; margin: 0; font-size: var(--hig-type-caption-size); color: var(--hig-muted); }
+.package-install .hig-surface [role='status']:empty { display: none; }
+.install-platforms { margin-top: var(--hig-space-lg); color: var(--hig-muted); font-size: var(--text-sm); line-height: 1.5; }
/* Hero code window: zero-stroke fill card with the Bloxwap code palette (as on the Hyperliquid SDK site). */
-.code-window { min-width: 0; margin: 0; overflow: hidden; border-radius: var(--radius-xl); background: var(--card); color: var(--foreground); }
+.code-window { min-width: 0; margin: 0; overflow: hidden; border-radius: var(--hig-radius-xl); background: var(--hig-surface); color: var(--hig-text); }
.code-toolbar {
- display: flex; align-items: center; justify-content: space-between; gap: var(--space-md);
- padding: var(--space-lg) var(--space-2xl) 0; font: var(--text-2xs) / 1.5 var(--font-mono); color: var(--muted-foreground);
-}
-.window-dots { display: flex; gap: var(--space-xs); }
-.window-dots > i { width: 6px; height: 6px; border-radius: var(--radius-full); background: var(--muted); }
-.code-language { color: color-mix(in oklab, var(--bloxwap-blue) 60%, var(--foreground)); }
-.code-actions { display: flex; align-items: center; gap: var(--space-xs); margin-right: calc(-1 * var(--space-md)); }
-.code-toolbar { min-height: var(--control-h-sm); }
-.code-window pre { margin: 0; overflow-x: auto; padding: var(--space-xl) var(--space-2xl); font: var(--text-xs) / 1.85 var(--font-mono); tab-size: 2; scrollbar-width: none; }
+ display: flex; align-items: center; justify-content: space-between; gap: var(--hig-space-md);
+ padding: var(--hig-space-lg) var(--hig-space-xxl) 0; font: var(--text-2xs) / 1.5 var(--font-mono); color: var(--hig-muted);
+}
+.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); }
+/* 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)); }
+.code-window pre { margin: 0; overflow-x: auto; padding: var(--hig-space-md) var(--hig-space-xxl) var(--hig-space-xl); font: var(--text-xs) / 1.85 var(--font-mono); tab-size: 2; scrollbar-width: none; }
.code-window pre::-webkit-scrollbar { display: none; }
-.syntax-keyword { color: color-mix(in oklab, var(--bloxwap-pink) 70%, var(--foreground)); }
-.syntax-string { color: var(--bloxwap-green); }
-.syntax-type { color: color-mix(in oklab, var(--bloxwap-blue) 60%, var(--foreground)); }
-.syntax-muted { color: var(--muted-foreground); }
+.syntax-keyword { color: color-mix(in oklab, var(--hig-warning) 70%, var(--hig-text)); }
+.syntax-string { color: var(--hig-action); }
+.syntax-type { color: color-mix(in oklab, var(--hig-info) 60%, var(--hig-text)); }
+.syntax-muted { color: var(--hig-muted); }
.code-status {
- display: flex; align-items: center; gap: var(--space-sm); margin: 0;
- padding: 0 var(--space-2xl) var(--space-lg); font: var(--text-2xs) / 1.5 var(--font-mono); color: var(--muted-foreground);
+ display: flex; align-items: center; gap: var(--hig-space-sm); margin: 0;
+ padding: 0 var(--hig-space-xxl) var(--hig-space-lg); font: var(--text-2xs) / 1.5 var(--font-mono); color: var(--hig-muted);
}
-.code-status > span { width: 6px; height: 6px; border-radius: var(--radius-full); background: var(--bloxwap-green); }
+.code-status > span { width: 6px; height: 6px; border-radius: var(--hig-radius-pill); background: var(--hig-action); }
/*
* Entrance: three attention layers ~200 ms apart on one curve (MOT-2), as on the Hyperliquid SDK site:
@@ -268,156 +244,162 @@ 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(--control-h-sm); gap: var(--space-sm); padding: 0 var(--space-xs) 0 var(--space-md);
- border: 0; border-radius: var(--radius-full); background: var(--card); color: var(--muted-foreground); font-size: var(--text-sm);
- transition: background-color var(--duration-fast) var(--ease-spring), color var(--duration-fast) var(--ease-spring);
+ height: var(--hig-control-compact); 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);
}
-button[data-search-full]:hover { background: var(--muted); color: var(--foreground); }
+button[data-search-full]:hover { background: var(--hig-nested); color: var(--hig-text); }
button[data-search-full] kbd {
- display: inline-grid; place-items: center; min-width: 1.5rem; height: 1.5rem; padding: 0 var(--space-xs);
- border: 0; border-radius: var(--radius-full); background: color-mix(in oklab, var(--card) 80%, var(--foreground)); color: var(--foreground);
+ display: inline-grid; place-items: center; min-width: 1.5rem; height: 1.5rem; padding: 0 var(--hig-space-xs);
+ border: 0; border-radius: var(--hig-radius-pill); background: color-mix(in oklab, var(--hig-surface) 80%, var(--hig-text)); color: var(--hig-text);
font: var(--text-2xs) / 1 var(--font-mono);
}
.hero-preview {
min-width: 0;
- padding-bottom: var(--space-6xl);
+ padding-bottom: var(--hig-space-section);
scroll-margin-top: 5rem;
}
-.preview-intro { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-lg); margin-bottom: var(--space-2xl); }
+.preview-intro { display: flex; flex-direction: column; align-items: flex-start; gap: var(--hig-space-lg); margin-bottom: var(--hig-space-xxl); }
.preview-intro h2 { margin: 0; font-size: var(--text-3xl); letter-spacing: -.04em; line-height: 1.2; font-weight: 600; }
.preview-intro > p:not(.eyebrow) { margin: 0; max-width: 25rem; color: var(--color-fd-muted-foreground); font-size: var(--text-sm); line-height: 1.7; }
.preview-intro--section { align-items: center; text-align: center; }
-.preview-actions { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-xs); }
-.preview-intro > .preview-actions:not(:first-child) { margin-top: var(--space-xs); }
+.preview-actions { display: flex; align-items: center; flex-wrap: wrap; gap: var(--hig-space-xs); }
+.preview-intro > .preview-actions:not(:first-child) { margin-top: var(--hig-space-xs); }
.chart-preview {
overflow: hidden;
width: 100%;
- border: 1px solid var(--border);
- border-radius: var(--radius-xl);
- background: var(--bloxwap-black);
- color: var(--foreground);
+ border: 1px solid var(--hig-border);
+ border-radius: var(--hig-radius-xl);
+ background: var(--hig-background);
+ color: var(--hig-text);
font-family: var(--font-sans);
}
.chart-frame {
display: block;
width: 100%;
height: clamp(320px, 72svh, 900px);
- scroll-margin-block: var(--space-6xl) var(--space-lg);
+ scroll-margin-block: var(--hig-space-section) var(--hig-space-lg);
border: 0;
- background: var(--bloxwap-black);
+ background: var(--hig-background);
}
.chart-preview:fullscreen { display: flex; flex-direction: column; height: 100%; border: 0; border-radius: 0; }
.chart-preview:fullscreen .chart-frame { flex: 1; min-height: 0; height: 100%; }
-/* Cards, not rules: zero-stroke fill cards (plain-UI .card fill, --radius-xl, --space-xl, no border); hover shifts the fill. */
-.principles { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-md); padding: var(--space-lg) 0 var(--space-3xl); }
+/* Cards, not rules: zero-stroke fill cards (plain-UI .card fill, --hig-radius-xl, --space-xl, no border); hover shifts the fill. */
+.principles { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--hig-space-md); padding: var(--hig-space-lg) 0 var(--hig-space-xxxl); }
.principles > div, .guide-links a {
- background: var(--card);
- border-radius: var(--radius-xl);
- padding: var(--space-xl);
+ background: var(--hig-surface);
+ border-radius: var(--hig-radius-xl);
+ padding: var(--hig-space-xl);
}
.feature-number { color: var(--color-fd-primary); font: var(--text-2xs) var(--font-mono); }
-.principles h2 { font-size: var(--text-base); font-weight: 600; margin: var(--space-md) 0 var(--space-sm); }
+.principles h2 { font-size: var(--text-base); font-weight: 600; margin: var(--hig-space-md) 0 var(--hig-space-sm); }
.principles p { font-size: var(--text-sm); color: var(--color-fd-muted-foreground); line-height: 1.7; }
-.start-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-6xl); padding: var(--space-5xl) 0 var(--space-6xl); }
-.start-grid h2 { margin: var(--space-xl) 0; font-size: var(--text-3xl); letter-spacing: -.04em; line-height: 1.2; font-weight: 600; }
+.start-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--hig-space-section); padding: var(--hig-space-section) 0 var(--hig-space-section); }
+.start-grid h2 { margin: var(--hig-space-xl) 0; font-size: var(--text-3xl); letter-spacing: -.04em; line-height: 1.2; font-weight: 600; }
.start-grid > div > p:last-child { color: var(--color-fd-muted-foreground); font-size: var(--text-sm); max-width: 25rem; line-height: 1.7; }
-.guide-links { display: grid; gap: var(--space-md); }
+.guide-links { display: grid; gap: var(--hig-space-md); }
.guide-links a {
- display: flex; align-items: center; gap: var(--space-xl);
- transition: background-color var(--duration-fast) var(--ease-spring);
+ display: flex; align-items: center; gap: var(--hig-space-xl);
+ transition: background-color var(--hig-duration-fast) var(--ease-spring);
}
-.guide-links a:hover { background: var(--muted); }
+.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: var(--icon-md); height: var(--icon-md); color: var(--color-fd-muted-foreground); transition: color var(--duration-fast) var(--ease-spring), transform var(--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-duration-fast) var(--ease-spring), transform var(--hig-duration-fast) var(--ease-spring); }
.guide-links a:hover > svg { color: var(--color-fd-primary); transform: translateX(2px); }
-.guide-links h3 { font-size: var(--text-sm); margin-bottom: var(--space-xs); font-weight: 600; transition: color var(--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-duration-fast) var(--ease-spring); }
.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. */
/* The bloxwap.com footer (monorepo workers/www), inset to the header's box. The home page ends with it. */
-.site-footer { margin-top: auto; padding: var(--space-5xl) max(var(--space-lg), calc((100% - var(--fd-layout-width, 1400px)) / 2 + var(--space-lg))) var(--space-4xl); }
+.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(--control-h-sm); padding: 0 var(--space-md); border: 0; border-radius: var(--radius-full);
- background: transparent; color: var(--muted-foreground); font-size: var(--text-sm);
- transition: background-color var(--duration-fast) var(--ease-spring), color var(--duration-fast) var(--ease-spring);
+ align-self: flex-start; height: var(--hig-control-compact); 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);
}
-#nd-page .edit-on-github:hover { background: var(--card); color: var(--foreground); }
+#nd-page .edit-on-github:hover { background: var(--hig-surface); color: var(--hig-text); }
.page-nav > a {
- padding: var(--space-xl) var(--space-2xl); border: 0; border-radius: var(--radius-xl); background: var(--card);
- transition: background-color var(--duration-fast) var(--ease-spring), color var(--duration-fast) var(--ease-spring);
+ 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);
}
-.page-nav > a:hover { background: var(--muted); color: var(--bloxwap-green); }
-.footer-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); column-gap: var(--space-2xl); row-gap: var(--space-4xl); }
-.footer-brand { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-md); }
+.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); }
+.footer-brand { display: flex; flex-direction: column; align-items: flex-start; gap: var(--hig-space-md); }
.footer-brand p { margin: 0; color: var(--color-fd-muted-foreground); font-size: var(--text-sm); }
-.footer-lockup { display: flex; align-items: center; gap: var(--space-sm); }
+.footer-lockup { display: flex; align-items: center; gap: var(--hig-space-sm); }
.footer-icon { width: 36px; height: 36px; border-radius: 10px; }
/* 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(--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(--duration-fast) var(--ease-spring), opacity var(--duration-fast) var(--ease-spring); }
-.footer-col a { display: block; width: fit-content; padding: var(--space-xs) 0; color: var(--color-fd-foreground); font-size: var(--text-sm); opacity: .8; }
+.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 { 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(--space-sm); margin-top: var(--space-4xl); color: var(--color-fd-muted-foreground); font-size: var(--text-xs); }
-.footer-legal { display: inline-flex; align-items: center; gap: var(--space-sm); }
+.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); }
+.footer-legal { display: inline-flex; align-items: center; gap: var(--hig-space-sm); }
@media (max-width: 860px) {
- .footer-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); column-gap: var(--space-xl); }
+ .footer-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); column-gap: var(--hig-space-xl); }
.footer-brand { grid-column: 1 / -1; }
- .footer-col a { padding: var(--space-md) 0; }
+ .footer-col a { padding: var(--hig-space-md) 0; }
}
@media (max-width: 480px) { .footer-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
-.docs-sidebar-links { display: flex; align-items: center; gap: var(--space-lg); padding-top: var(--space-lg); font-size: var(--text-xs); color: var(--color-fd-muted-foreground); }
-.docs-sidebar-links a { transition: color var(--duration-fast) var(--ease-spring); }
+.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:hover { color: var(--color-fd-foreground); }
-.not-found { max-width: 37.5rem; padding: 7.5rem var(--space-2xl); margin: 0 auto; }
-.not-found h1 { font-size: var(--text-4xl); margin: var(--space-2xl) 0; }
-.not-found .btn { margin-top: var(--space-3xl); }
-.not-found-links { display: flex; flex-wrap: wrap; gap: var(--space-sm) var(--space-xl); margin-top: var(--space-3xl); 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(--duration-fast) var(--ease-spring); }
+.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:hover { color: var(--color-fd-foreground); }
@media (max-width: 800px) {
- .home { padding-top: var(--space-3xl); }
- .hero { grid-template-columns: minmax(0, 1fr); gap: var(--space-3xl); padding-bottom: var(--space-3xl); }
+ .home { padding-top: var(--hig-space-xxxl); }
+ .hero { grid-template-columns: minmax(0, 1fr); gap: var(--hig-space-xxxl); padding-bottom: var(--hig-space-xxxl); }
.hero h1 { font-size: var(--text-6xl); }
}
@media (max-width: 600px) {
- .home { padding: var(--space-3xl) var(--space-lg) 0; }
+ .home { padding: var(--hig-space-xxxl) var(--hig-space-lg) 0; }
.hero h1 { font-size: var(--text-5xl); }
/* Share the row when both fit; otherwise each takes a full-width row (app pattern: full-width primary). */
- .hero-actions > .btn { flex: 1 1 auto; }
- .install-command { gap: var(--space-sm); padding-left: var(--space-lg); font-size: clamp(var(--text-xs), 3.4vw, var(--text-sm)); }
+ .hero-actions > .hig-button { flex: 1 1 auto; }
+ .package-install .hig-surface { gap: var(--hig-space-sm); padding-left: var(--hig-space-lg); }
+ .package-install .hig-surface pre { font-size: clamp(var(--text-xs), 3.4vw, var(--text-sm)); }
.principles { grid-template-columns: 1fr; }
- .start-grid { grid-template-columns: 1fr; gap: var(--space-3xl); }
- .start-grid { padding: var(--space-4xl) 0; }
+ .start-grid { grid-template-columns: 1fr; gap: var(--hig-space-xxxl); }
+ .start-grid { padding: var(--hig-space-section) 0; }
.chart-frame { height: clamp(320px, 72svh, 600px); }
}
/* HIG type roles and adaptive targets; the dense chart keeps compact controls. */
-body { font-size: var(--type-body-size); line-height: var(--type-body-line); }
-.hero h1 { font-size: clamp(var(--type-title-size), 4.4vw, var(--type-display-size)); line-height: 1.0833; font-weight: var(--type-display-weight); }
-#nd-page h1 { font-size: var(--type-title-size); line-height: var(--type-title-line); font-weight: var(--type-title-weight); }
-.install-tabs button { min-height: var(--control-h-sm); }
-.install-feedback, .copy-feedback { color: var(--destructive); font-size: var(--type-caption-size); }
-.btn--icon { min-height: var(--control-h-sm); }
+body { font-size: var(--hig-type-body-size); line-height: var(--hig-type-body-line); }
+.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); }
@media (pointer: coarse) {
- .btn--sm, .btn--icon, .install-tabs button, .docs-app-link, #nd-page .edit-on-github { min-height: var(--control-h); }
- .btn--icon { min-width: var(--control-h); }
+ .docs-app-link, #nd-page .edit-on-github { min-height: var(--hig-control-comfortable); }
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { animation-duration: 0ms !important; transition-duration: 0ms !important; scroll-behavior: auto !important; }
- .btn:active, .guide-links a:hover > svg { transform: none; }
+ .hig-button:active, .guide-links a:hover > svg { transform: none; }
}
@theme inline {
- --color-background: var(--background); --color-foreground: var(--foreground);
- --color-card: var(--card); --color-card-foreground: var(--card-foreground);
- --color-muted: var(--muted); --color-muted-foreground: var(--muted-foreground);
- --color-border: var(--border); --color-primary: var(--primary);
- --color-bloxwap-green: var(--hig-action); --color-bloxwap-black: var(--hig-background);
--text-3xs: 0.625rem; --text-2xs: 0.6875rem;
}
-.search-error { color: var(--destructive); padding: var(--space-lg); }
+.search-error { padding: var(--hig-space-lg); }
+.search-error .hig-surface { display: flex; flex-direction: column; gap: var(--hig-space-xs); padding: var(--hig-space-md) var(--hig-space-lg); background: var(--hig-nested); }
+.search-empty { padding: var(--hig-space-lg); text-align: center; }
+.search-empty .hig-surface { background: transparent; }
+.search-empty h3 { margin: 0 0 var(--hig-space-xs); font-size: var(--hig-type-label-size); font-weight: var(--hig-type-label-weight); }
+.search-empty p, .search-error p { margin: 0; color: var(--hig-muted); font-size: var(--text-sm); }
+
+/* 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; }
-.install-command .copy-feedback { grid-column: 1 / -1; padding-inline-end: var(--space-lg); }
+.preview-summary, .preview-status { margin: 0 0 var(--hig-space-md); color: var(--hig-muted); font-size: var(--text-sm); line-height: 1.6; }
diff --git a/apps/docs/app/not-found.tsx b/apps/docs/app/not-found.tsx
index cdb6762..a5e7e0b 100644
--- a/apps/docs/app/not-found.tsx
+++ b/apps/docs/app/not-found.tsx
@@ -15,7 +15,7 @@ export default function NotFound() {
404 / OFF THE CHART
That page is missing.
The page you requested does not exist on this site. It may have moved, or the URL may be wrong. Head back to the documentation to find your next step, or use one of the machine-readable indexes below.
- Open documentation
+ Open documentation
{RESOURCES.map(([label, href]) => {label} )}
diff --git a/apps/docs/app/page.tsx b/apps/docs/app/page.tsx
index 37c6591..fc8c6c5 100644
--- a/apps/docs/app/page.tsx
+++ b/apps/docs/app/page.tsx
@@ -17,7 +17,7 @@ export default function Home() {
DEVELOPER PREVIEW · v0.3.0
Financial charts.Your interface.
Your next trading interface starts here. Candles, live order books, and volume profiles, drawn with WebGL2. Try the full chart below: draw a trend line, add indicators, change timeframes, and make it your own.
-
+
diff --git a/apps/docs/components/callout.tsx b/apps/docs/components/callout.tsx
new file mode 100644
index 0000000..2257c79
--- /dev/null
+++ b/apps/docs/components/callout.tsx
@@ -0,0 +1,16 @@
+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;
+
+/** MDX `` keeps the Fumadocs props and renders the HIG callout. Static content is not announced. */
+export function Callout({ type = 'info', title, children }: { type?: FumadocsType; title?: ReactNode; children?: ReactNode }) {
+ const level = severity[type] ?? 'info';
+ return ;
+}
diff --git a/apps/docs/components/chart-preview.tsx b/apps/docs/components/chart-preview.tsx
index 015b3a6..0fb0649 100644
--- a/apps/docs/components/chart-preview.tsx
+++ b/apps/docs/components/chart-preview.tsx
@@ -2,6 +2,8 @@
import { useEffect, useRef, useState, type ReactNode } from 'react';
import { ArrowUpRight, Maximize2, Minimize2 } from 'lucide-react';
+import { Button } from '@bloxwap/hig-react/button';
+import { Spinner } from '@bloxwap/hig-react/status';
import { assetUrl } from '@/lib/site';
/**
@@ -61,16 +63,16 @@ export function ChartPreview({ children }: { children?: ReactNode }) {
return
{children}
-
-
Open playground
- {canFullscreen &&
+
+
Open playground
+ {canFullscreen &&
{fullscreen ? : }
{fullscreen ? 'Exit fullscreen' : 'Fullscreen'}
- }
+ }
Interactive candlestick chart with price and volume readouts, interval selection, drawings, pan and zoom. Live market data may be unavailable; choose a synthetic market in the playground to explore offline.
- {!loaded &&
Loading chart playground…
}
+ {!loaded &&
}
diff --git a/apps/docs/components/copy-button.tsx b/apps/docs/components/copy-button.tsx
index e921ddf..18774e9 100644
--- a/apps/docs/components/copy-button.tsx
+++ b/apps/docs/components/copy-button.tsx
@@ -1,11 +1,13 @@
'use client';
-import { useEffect, useRef, useState } from 'react';
+import { useEffect, useState } from 'react';
import { Check, Copy } from 'lucide-react';
+import { IconButton } from '@bloxwap/hig-react/button';
+import { useClipboard } from '@/lib/clipboard';
-/** A quiet icon button that copies `text`, with a check mark and a screen-reader status on success. */
+/** HIG icon button that copies `text`, with a check mark, a screen-reader status and visible failure recovery. */
export function CopyButton({ text, label }: { text: string; label: string }) {
- const pending = useRef(false);
+ const copyText = useClipboard();
const [busy, setBusy] = useState(false);
const [result, setResult] = useState<'copied' | 'failed' | null>(null);
@@ -16,25 +18,23 @@ export function CopyButton({ text, label }: { text: string; label: string }) {
}, [result]);
async function copy() {
- if (pending.current) return;
- pending.current = true;
+ if (busy) return;
setBusy(true);
try {
- await navigator.clipboard.writeText(text);
+ await copyText(text);
setResult('copied');
} catch {
setResult('failed');
} finally {
- pending.current = false;
setBusy(false);
}
}
return <>
-
- {result === 'copied' ? : }
-
-
+
+ {result === 'copied' ? : }
+
+
{result === 'copied' ? 'Code copied to clipboard.' : result === 'failed' ? 'Could not copy. Select the code to copy it manually.' : ''}
>;
diff --git a/apps/docs/components/hero-code.tsx b/apps/docs/components/hero-code.tsx
index 20075d7..744034d 100644
--- a/apps/docs/components/hero-code.tsx
+++ b/apps/docs/components/hero-code.tsx
@@ -1,6 +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 { CopyButton } from './copy-button';
/*
@@ -39,13 +40,14 @@ function highlight(code: string): ReactNode[] {
export function HeroCode() {
// Read per render (build time for the static export), so dev edits to the snippet show up.
const source = readFileSync(join(process.cwd(), 'snippets', SNIPPET_FILE), 'utf8').trimEnd();
- return
+ return
{SNIPPET_FILE}
- TSX
+
- {highlight(source)}
+ {/* HIG CodeWindow recipe markup (language label + focusable code) from HIG primitives; see install-command.tsx. */}
+ TSX {highlight(source)}
A candlestick chart that follows the system theme and fills its box
;
}
diff --git a/apps/docs/components/install-command.tsx b/apps/docs/components/install-command.tsx
index f1bc0b3..4c33b4d 100644
--- a/apps/docs/components/install-command.tsx
+++ b/apps/docs/components/install-command.tsx
@@ -1,7 +1,10 @@
'use client';
-import { useId, useState } from 'react';
-import { CopyButton } from './copy-button';
+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 { useClipboard } from '@/lib/clipboard';
const managers = [
{ name: 'bun', command: 'bun add @bloxwap/chart' },
@@ -10,44 +13,31 @@ const managers = [
{ name: 'yarn', command: 'yarn add @bloxwap/chart' },
];
-export function InstallCommand() {
- const id = useId();
- const [selected, setSelected] = useState(0);
- const { command } = managers[selected];
-
- function select(index: number) {
- setSelected(index);
- }
+/*
+ * 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).
+ */
+function Command({ command, onCopy }: { command: string; onCopy: () => Promise }) {
+ const [status, setStatus] = useState('');
+ return
+ {command}
+ {
+ Promise.resolve().then(onCopy).then(() => setStatus('Copied')).catch(() => setStatus('Copy failed. Select the command and copy manually.'));
+ }}>Copy install command
+ {status}
+ ;
+}
- return
-
- {managers.map(({ name }, index) => select(index)}
- onKeyDown={(event) => {
- let next: number;
- if (event.key === 'ArrowRight') next = (index + 1) % managers.length;
- else if (event.key === 'ArrowLeft') next = (index + managers.length - 1) % managers.length;
- else if (event.key === 'Home') next = 0;
- else if (event.key === 'End') next = managers.length - 1;
- else return;
- event.preventDefault();
- select(next);
- document.getElementById(`${id}-tab-${next}`)?.focus();
- }}
- >{name} )}
-
-
- $
- {command}
-
-
+/** 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)} /> }))}
+ />
Bun · Node.js · Browsers · Workers
;
}
diff --git a/apps/docs/components/search.tsx b/apps/docs/components/search.tsx
index 11d05a7..f476015 100644
--- a/apps/docs/components/search.tsx
+++ b/apps/docs/components/search.tsx
@@ -7,8 +7,13 @@ import {
SearchDialogIcon, SearchDialogInput, SearchDialogList, SearchDialogOverlay,
type SharedProps,
} from 'fumadocs-ui/components/dialog/search';
+import { Alert, EmptyState } from '@bloxwap/hig-react/status';
import { assetUrl } from '@/lib/site';
+function NoResults() {
+ return
;
+}
+
export default function StaticSearch(props: SharedProps) {
const { search, setSearch, query } = useDocsSearch({
client: staticClient({ from: assetUrl('/search.json') }),
@@ -18,8 +23,8 @@ export default function StaticSearch(props: SharedProps) {
- {query.error && Search is unavailable. Use the documentation navigation or close search and try again.
}
-
+ {query.error && Use the documentation navigation, or close search and try again.
}
+
);
diff --git a/apps/docs/content/docs/getting-started.mdx b/apps/docs/content/docs/getting-started.mdx
index b1b000a..1533a6c 100644
--- a/apps/docs/content/docs/getting-started.mdx
+++ b/apps/docs/content/docs/getting-started.mdx
@@ -9,7 +9,11 @@ description: Build the package locally and put your first candlestick chart on s
npm install @bloxwap/chart
```
-`0.3.0` is an early developer preview, so expect breaking changes before `1.0`. To try unreleased changes, build a local archive from the repository with `npm ci && npm run pack:chart` and install the resulting `packages/chart/bloxwap-chart-
.tgz`.
+
+`0.3.0` is an early developer preview, so expect breaking changes before `1.0`.
+
+
+To try unreleased changes, build a local archive from the repository with `npm ci && npm run pack:chart` and install the resulting `packages/chart/bloxwap-chart-.tgz`.
## Give the chart a box
diff --git a/apps/docs/fonts/Bloxwap-OFL.txt b/apps/docs/fonts/Bloxwap-OFL.txt
deleted file mode 100644
index 5acdf82..0000000
--- a/apps/docs/fonts/Bloxwap-OFL.txt
+++ /dev/null
@@ -1,93 +0,0 @@
-Copyright 2026 Bloxwap, Inc. (https://bloxwap.com), with Reserved Font Name "Bloxwap".
-
-This Font Software is licensed under the SIL Open Font License, Version 1.1.
-This license is copied below, and is also available with a FAQ at:
-https://openfontlicense.org
-
-
------------------------------------------------------------
-SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
------------------------------------------------------------
-
-PREAMBLE
-The goals of the Open Font License (OFL) are to stimulate worldwide
-development of collaborative font projects, to support the font creation
-efforts of academic and linguistic communities, and to provide a free and
-open framework in which fonts may be shared and improved in partnership
-with others.
-
-The OFL allows the licensed fonts to be used, studied, modified and
-redistributed freely as long as they are not sold by themselves. The
-fonts, including any derivative works, can be bundled, embedded,
-redistributed and/or sold with any software provided that any reserved
-names are not used by derivative works. The fonts and derivatives,
-however, cannot be released under any other type of license. The
-requirement for fonts to remain under this license does not apply
-to any document created using the fonts or their derivatives.
-
-DEFINITIONS
-"Font Software" refers to the set of files released by the Copyright
-Holder(s) under this license and clearly marked as such. This may
-include source files, build scripts and documentation.
-
-"Reserved Font Name" refers to any names specified as such after the
-copyright statement(s).
-
-"Original Version" refers to the collection of Font Software components as
-distributed by the Copyright Holder(s).
-
-"Modified Version" refers to any derivative made by adding to, deleting,
-or substituting -- in part or in whole -- any of the components of the
-Original Version, by changing formats or by porting the Font Software to a
-new environment.
-
-"Author" refers to any designer, engineer, programmer, technical
-writer or other person who contributed to the Font Software.
-
-PERMISSION & CONDITIONS
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of the Font Software, to use, study, copy, merge, embed, modify,
-redistribute, and sell modified and unmodified copies of the Font
-Software, subject to the following conditions:
-
-1) Neither the Font Software nor any of its individual components,
-in Original or Modified Versions, may be sold by itself.
-
-2) Original or Modified Versions of the Font Software may be bundled,
-redistributed and/or sold with any software, provided that each copy
-contains the above copyright notice and this license. These can be
-included either as stand-alone text files, human-readable headers or
-in the appropriate machine-readable metadata fields within text or
-binary files as long as those fields can be easily viewed by the user.
-
-3) No Modified Version of the Font Software may use the Reserved Font
-Name(s) unless explicit written permission is granted by the corresponding
-Copyright Holder. This restriction only applies to the primary font name as
-presented to the users.
-
-4) The name(s) of the Copyright Holder(s) and the Author(s) of the Font
-Software shall not be used to promote, endorse or advertise any
-Modified Version, except to acknowledge the contribution(s) of the
-Copyright Holder(s) and the Author(s) or with their explicit written
-permission.
-
-5) The Font Software, modified or unmodified, in part or in whole,
-must be distributed entirely under this license, and must not be
-distributed under any other license. The requirement for fonts to
-remain under this license does not apply to any document created
-using the Font Software.
-
-TERMINATION
-This license becomes null and void if any of the above conditions are
-not met.
-
-DISCLAIMER
-THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
-OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
-COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
-INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
-DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
-OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/apps/docs/fonts/BloxwapSans-Black.ttf b/apps/docs/fonts/BloxwapSans-Black.ttf
deleted file mode 100644
index 2b61bf1..0000000
Binary files a/apps/docs/fonts/BloxwapSans-Black.ttf and /dev/null differ
diff --git a/apps/docs/fonts/BloxwapSans-Bold.ttf b/apps/docs/fonts/BloxwapSans-Bold.ttf
deleted file mode 100644
index df41fa9..0000000
Binary files a/apps/docs/fonts/BloxwapSans-Bold.ttf and /dev/null differ
diff --git a/apps/docs/fonts/README.md b/apps/docs/fonts/README.md
deleted file mode 100644
index 45fc4e5..0000000
--- a/apps/docs/fonts/README.md
+++ /dev/null
@@ -1,12 +0,0 @@
-# Documentation fonts
-
-Web body text uses Bloxwap Sans and code uses Bloxwap Mono from the exact
-`@bloxwap/font@0.1.2` dependency. The Next.js exports self-host variable weights
-100–900, italics, and script companions without requests to Google Fonts.
-HIG headings use Bloxwap Sans too; the unused Space Grotesk face and notice were removed.
-
-`BloxwapSans-Bold.ttf` (700) and `BloxwapSans-Black.ttf` (900) are static
-instances of `fonts/BloxwapSans/BloxwapSans-Variable.woff2` from the published
-`@bloxwap/font@0.1.2` npm tarball (byte-identical to 0.1.1, so the derived files are unchanged). FontTools 4.66.1 `instantiateVariableFont`
-sets `wght` to 700/900 and removes WOFF2 compression (`font.flavor = None`)
-for the Open Graph renderer. Keep `Bloxwap-OFL.txt` with these derived faces.
diff --git a/apps/docs/lib/clipboard.ts b/apps/docs/lib/clipboard.ts
new file mode 100644
index 0000000..6ffd6e6
--- /dev/null
+++ b/apps/docs/lib/clipboard.ts
@@ -0,0 +1,17 @@
+'use client';
+
+import { useRef } from 'react';
+
+/**
+ * One clipboard write at a time: a repeat activation while a write is pending joins it instead of
+ * starting another. Rejections (denied permission, insecure context) reach the caller's recovery text.
+ */
+export function useClipboard(): (text: string) => Promise {
+ const pending = useRef | null>(null);
+ return (text) => {
+ pending.current ??= Promise.resolve()
+ .then(() => navigator.clipboard.writeText(text))
+ .finally(() => { pending.current = null; });
+ return pending.current;
+ };
+}
diff --git a/apps/docs/lib/og-card.tsx b/apps/docs/lib/og-card.tsx
index cf74939..0f3f651 100644
--- a/apps/docs/lib/og-card.tsx
+++ b/apps/docs/lib/og-card.tsx
@@ -1,4 +1,4 @@
-import hig from '@/vendor/hig/manifest.json';
+import { tokens as hig } from '@bloxwap/hig-tokens';
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
import { ImageResponse } from 'next/og';
@@ -12,9 +12,12 @@ 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');
const assets = Promise.all([
- readFile(join(process.cwd(), 'fonts/BloxwapSans-Bold.ttf')),
- readFile(join(process.cwd(), 'fonts/BloxwapSans-Black.ttf')),
+ readFile(join(higFonts, 'BloxwapSans-Bold.ttf')),
+ readFile(join(higFonts, 'BloxwapSans-Black.ttf')),
readFile(join(process.cwd(), '../../packages/chart/package.json'), 'utf8'),
]);
diff --git a/apps/docs/mdx-components.tsx b/apps/docs/mdx-components.tsx
index 4772ee1..1211853 100644
--- a/apps/docs/mdx-components.tsx
+++ b/apps/docs/mdx-components.tsx
@@ -1,8 +1,9 @@
import defaults from 'fumadocs-ui/mdx';
import type { MDXComponents } from 'mdx/types';
+import { Callout } from '@/components/callout';
import { ChartPreview } from '@/components/chart-preview';
export function getMDXComponents(components?: MDXComponents): MDXComponents {
- return { ...defaults, ChartPreview, ...components };
+ return { ...defaults, Callout, ChartPreview, ...components };
}
export const useMDXComponents = getMDXComponents;
diff --git a/apps/docs/package.json b/apps/docs/package.json
index 594db6f..e82c355 100644
--- a/apps/docs/package.json
+++ b/apps/docs/package.json
@@ -16,6 +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",
"fumadocs-core": "16.15.14",
"fumadocs-mdx": "15.4.5",
"fumadocs-ui": "16.15.14",
diff --git a/apps/docs/scripts/capture-hig.mjs b/apps/docs/scripts/capture-hig.mjs
new file mode 100644
index 0000000..d971933
--- /dev/null
+++ b/apps/docs/scripts/capture-hig.mjs
@@ -0,0 +1,55 @@
+// Before/after evidence: screenshots of representative pages and the chart demo at desktop and 390px.
+// Usage: node apps/docs/scripts/capture-hig.mjs
+// External requests are blocked so the playground uses its offline synthetic market in both captures.
+import { chromium } from 'playwright-core';
+import { createServer } from 'node:http';
+import { mkdir, readFile, stat } from 'node:fs/promises';
+import { extname, join, resolve, sep } from 'node:path';
+
+const [outDir, evidenceDir, label] = process.argv.slice(2);
+if (!outDir || !evidenceDir || !label) throw new Error('Usage: capture-hig.mjs ');
+const root = resolve(outDir);
+const basePath = JSON.parse(await readFile(join(root, '_docs-config.json'), 'utf8')).basePath ?? '';
+const mime = { '.html': 'text/html; charset=utf-8', '.js': 'text/javascript', '.css': 'text/css', '.json': 'application/json', '.svg': 'image/svg+xml', '.png': 'image/png', '.woff2': 'font/woff2', '.wasm': 'application/wasm' };
+const server = createServer(async (request, response) => {
+ try {
+ const path = decodeURIComponent(new URL(request.url ?? '/', 'http://localhost').pathname);
+ if (!path.startsWith(`${basePath}/`)) throw new Error('Not found');
+ let file = resolve(root, `.${path.slice(basePath.length)}`);
+ if (file !== root && !file.startsWith(root + sep)) throw new Error('Not found');
+ if ((await stat(file)).isDirectory()) file = join(file, 'index.html');
+ response.writeHead(200, { 'Content-Type': mime[extname(file)] ?? 'application/octet-stream' });
+ response.end(await readFile(file));
+ } catch {
+ response.writeHead(404, { 'Content-Type': 'text/html; charset=utf-8' });
+ response.end(await readFile(join(root, '404.html')));
+ }
+});
+const port = Number(process.env.PORT ?? 4111);
+await new Promise(done => server.listen(port, '127.0.0.1', done));
+const origin = `http://127.0.0.1:${port}`;
+const pages = [['home', '/'], ['getting-started', '/docs/getting-started/'], ['configuration', '/docs/api/configuration/'], ['playground', '/docs/playground/']];
+const viewports = [['desktop', { width: 1440, height: 1000 }, false], ['mobile', { width: 390, height: 844 }, true]];
+await mkdir(evidenceDir, { recursive: true });
+const browser = await chromium.launch({ channel: 'chrome' });
+try {
+ for (const [name, viewport, isMobile] of viewports) {
+ const context = await browser.newContext({ viewport, isMobile, hasTouch: isMobile, reducedMotion: 'reduce' });
+ await context.route('**/*', route => route.request().url().startsWith(origin) ? route.continue() : route.abort());
+ const page = await context.newPage();
+ for (const [slug, path] of pages) {
+ await page.goto(`${origin}${basePath}${path}`, { waitUntil: 'networkidle' });
+ await page.evaluate(() => document.fonts.ready);
+ if (slug === 'playground') {
+ await page.waitForFunction(() => document.querySelector('iframe')?.contentWindow?.__chartStats?.().bars > 0, null, { timeout: 60000 });
+ await page.waitForTimeout(500);
+ }
+ await page.screenshot({ path: join(evidenceDir, `${label}-${slug}-${name}.png`), fullPage: slug !== 'configuration' });
+ console.log(`${label}-${slug}-${name}.png`);
+ }
+ await context.close();
+ }
+} finally {
+ await browser.close();
+ server.close();
+}
diff --git a/apps/docs/scripts/hig-tokens.mjs b/apps/docs/scripts/hig-tokens.mjs
index 0a90241..241bab4 100644
--- a/apps/docs/scripts/hig-tokens.mjs
+++ b/apps/docs/scripts/hig-tokens.mjs
@@ -1,46 +1,65 @@
-import { readFile, writeFile } from 'node:fs/promises';
+// 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';
import { createHash } from 'node:crypto';
+import { createRequire } from 'node:module';
import assert from 'node:assert/strict';
-const vendor = new URL('../vendor/hig/', import.meta.url);
-export const manifest = JSON.parse(await readFile(new URL('manifest.json', vendor), 'utf8'));
+import { tokens } from '@bloxwap/hig-tokens';
+
+const require = createRequire(import.meta.url);
+export const manifest = tokens;
+export const tokensCssPath = require.resolve('@bloxwap/hig-tokens/css');
export const colors = Object.fromEntries(Object.entries(manifest.colors).map(([key, value]) => [key, value.srgb]));
+/** CSS value of a role as a modern browser computes it from @bloxwap/hig-tokens/css. */
+export const cssColor = (role) => manifest.colors[role].oklch ?? manifest.colors[role].srgb;
export const chartTheme = {
theme: { background: colors.surface, textColor: colors.text, borderColor: colors.border, fontFamily: '"Bloxwap Sans", system-ui, sans-serif', monoFamily: '"Bloxwap Mono", monospace', scaleFontFamily: '"Bloxwap Mono", monospace' },
grid: { color: colors.border },
crosshair: { color: colors.muted, labelBackground: colors.action, labelColor: colors.onAction },
series: { upColor: colors.buy, downColor: colors.sell, wickUpColor: colors.buy, wickDownColor: colors.sell, borderUpColor: colors.buy, borderDownColor: colors.sell },
};
-const aliases = { 'bloxwap-black': 'background', 'bloxwap-green': 'action', background: 'background', foreground: 'text', card: 'surface', 'card-foreground': 'text', popover: 'nested', 'popover-foreground': 'text', primary: 'action', 'primary-foreground': 'onAction', secondary: 'nested', 'secondary-foreground': 'text', muted: 'nested', 'muted-foreground': 'muted', accent: 'nested', 'accent-foreground': 'text', destructive: 'negative', border: 'border', input: 'border', ring: 'focus' };
-const vars = Object.entries(aliases).map(([key, role]) => ` --${key}: var(--hig-${role});`);
-for (const [key, value] of Object.entries(manifest.radius)) vars.push(` --radius-${key === 'pill' ? 'full' : key}: ${value / 16}rem;`);
-vars.push(' --radius: var(--radius-lg);', ' --radius-2xl: var(--radius-xl);');
-for (const [key, role] of Object.entries({ xs:'xs', sm:'sm', md:'md', lg:'lg', xl:'xl', '2xl':'xxl', '3xl':'xxxl', '4xl':'section', '5xl':'section', '6xl':'section' })) vars.push(` --space-${key}: var(--hig-space-${role});`);
-for (const [key, value] of Object.entries(manifest.motion)) vars.push(` --duration-${key}: ${value}ms;`);
-for (const [key, role] of Object.entries(manifest.type)) {
- vars.push(` --type-${key}-size: ${role.size / 16}rem;`, ` --type-${key}-line: ${role.lineHeight / 16}rem;`, ` --type-${key}-weight: ${role.weight};`);
-}
-vars.push(` --control-h: ${manifest.density.comfortable.control / 16}rem;`, ` --control-h-sm: ${manifest.density.compact.control / 16}rem;`, ' --ease-spring: cubic-bezier(0.22, 1, 0.36, 1);', ' --ease-standard: ease-out;', ' --icon-md: 16px;', ' --control-min-w: 6rem;');
-const css = `/* Generated from pinned HIG manifest by scripts/hig-tokens.mjs. */\n:root {\n${vars.join('\n')}\n}\n@media (prefers-reduced-motion: reduce) {\n :root { --duration-fast: 0ms; --duration-base: 0ms; --duration-slow: 0ms; }\n}\n`;
-const output = new URL('../styles/hig.generated.css', import.meta.url);
+
+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 adapter drift: run node apps/docs/scripts/hig-tokens.mjs --write');
+ 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, `Pinned HIG artifact changed: ${file}`);
- const { readdir } = await import('node:fs/promises');
- assert.deepEqual((await readdir(new URL('../styles/', import.meta.url))).sort(), ['hig.generated.css']);
- const { readdir: list } = await import('node:fs/promises');
+ 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}`];
+ 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.
+ const forbidden = [
+ [/--(hig|bloxwap)-[\w-]+\s*:\s*(#|oklch\(|rgb|color\()/, 'copied palette declaration'],
+ [/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)'],
+ ];
async function checkSurface(dir) {
- for (const entry of await list(dir, { withFileTypes: true })) {
+ for (const entry of await readdir(dir, { withFileTypes: true })) {
const path = new URL(entry.name + (entry.isDirectory() ? '/' : ''), dir);
if (entry.isDirectory()) await checkSurface(path);
- else if (/\.(css|tsx?)$/.test(entry.name)) {
+ 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')) {
const source = await readFile(path, 'utf8');
- assert.ok(!/@workspace\/(tokens|ui)|tokens\.generated\.css|--bloxwap-\w+\s*:\s*(#|oklch|color\()/.test(source), `Obsolete imports or copied palette: ${path}`);
+ for (const [pattern, reason] of forbidden) assert.ok(!pattern.test(source), `${reason}: ${path.pathname}`);
}
}
}
- await checkSurface(new URL('../app/', import.meta.url));
- await checkSurface(new URL('../components/', import.meta.url));
- console.log('HIG provenance, generated adapter and copied-palette drift checks passed.');
+ 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.`);
}
diff --git a/apps/docs/scripts/measure-hig.mjs b/apps/docs/scripts/measure-hig.mjs
index ba2dffd..9a9fb37 100644
--- a/apps/docs/scripts/measure-hig.mjs
+++ b/apps/docs/scripts/measure-hig.mjs
@@ -1,20 +1,53 @@
-import { readdir, stat } from 'node:fs/promises';
+// Before/after size report for the HIG package migration.
+// Usage (from the repo root, after `npm run docs:build` with the same base path for both exports):
+// node apps/docs/scripts/measure-hig.mjs
+import { readdir, readFile, stat } from 'node:fs/promises';
import { execFileSync } from 'node:child_process';
+
async function sizes(dir) {
- const result = { css: 0, javascript: 0, fonts: 0 };
+ const result = { css: 0, javascript: 0, fonts: 0, total: 0, files: 0 };
async function walk(path) {
for (const file of await readdir(path, { withFileTypes: true })) {
const next = `${path}/${file.name}`;
- if (file.isDirectory()) await walk(next);
- else { const kind = file.name.endsWith('.css') ? 'css' : file.name.endsWith('.js') ? 'javascript' : /\.(woff2?|ttf)$/.test(file.name) ? 'fonts' : null; if (kind) result[kind] += (await stat(next)).size; }
+ if (file.isDirectory()) { await walk(next); continue; }
+ const size = (await stat(next)).size;
+ result.total += size; result.files++;
+ const kind = file.name.endsWith('.css') ? 'css' : file.name.endsWith('.js') ? 'javascript' : /\.(woff2?|ttf)$/.test(file.name) ? 'fonts' : null;
+ if (kind) result[kind] += size;
}
}
- await walk(dir); return result;
+ await walk(dir);
+ return result;
}
-const baselineDirectory = process.argv[2];
-const baselineCommit = process.argv[3];
+const git = (...args) => execFileSync('git', args, { encoding: 'utf8' });
+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);
const after = await sizes('apps/docs/out');
-const report = { baselineCommit, higCommit:'1a776b9326c6ba03702894943b3edbfd178c4b40', toolchain: {node:process.version, next:'16.3.6', basePath:'/chart'}, measurement:'sum of uncompressed emitted CSS/JS/font files in static export (not transfer or per-route bundle sizes)', before, after, delta:Object.fromEntries(Object.keys(before).map(k => [k,after[k]-before[k]])), removedSource:{paletteCssBytes:execFileSync('git',['show',`${baselineCommit}:apps/docs/styles/tokens.generated.css`]).length, unusedHeadingFontBytes:116056}, notes:['No runtime dependency added; chart SDK API and engine unchanged.','OG static Bloxwap faces remain referenced; their licenses are retained.','Decorative OG spectrum colors are brand assets, not semantic UI palette; exception reviewed 2026-11-13.']};
-console.log(JSON.stringify(report,null,2));
+const delta = (a, b) => Object.fromEntries(Object.keys(a).map((key) => [key, b[key] - a[key]]));
+
+// Hand-maintained docs CSS (excludes generated output) and docs source files removed by the migration.
+const cssFiles = (ref) => git('ls-tree', '-r', '--name-only', ref, 'apps/docs').split('\n').filter((f) => f.endsWith('.css') && !f.includes('.generated.'));
+const cssStats = (ref) => cssFiles(ref).reduce((sum, file) => { const text = git('show', `${ref}:${file}`); return { files: sum.files + 1, lines: sum.lines + text.split('\n').length, bytes: sum.bytes + Buffer.byteLength(text) }; }, { files: 0, lines: 0, bytes: 0 });
+const removed = git('diff', '--name-only', '--diff-filter=D', baselineCommit, '--', 'apps/docs').split('\n').filter(Boolean);
+const removedBytes = removed.reduce((sum, file) => sum + Number(git('cat-file', '-s', `${baselineCommit}:${file}`)), 0);
+const lockPackages = (text) => Object.keys(JSON.parse(text).packages).filter(Boolean);
+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; } };
+
+console.log(JSON.stringify({
+ baselineCommit,
+ hig: { commit: provenance.commit, version: provenance.version },
+ toolchain: { node: process.version, next: JSON.parse(await readFile('node_modules/next/package.json', 'utf8')).version, basePath: JSON.parse(await readFile('apps/docs/out/_docs-config.json', 'utf8')).basePath },
+ measurement: 'uncompressed bytes of the complete static export (not transfer size or per-route bundles)',
+ export: { before, after, delta: delta(before, after) },
+ docsCss: { handWritten: { before: cssStats(baselineCommit), after: cssStats('HEAD') }, generatedAdapterBytes: { before: generated(baselineCommit), after: generated('HEAD') } },
+ removedDocsFiles: { count: removed.length, bytes: removedBytes, files: removed },
+ dependencies: {
+ docsDirect: { before: docsDeps(git('show', `${baselineCommit}:apps/docs/package.json`)).length, after: docsDeps(await readFile('apps/docs/package.json', 'utf8')).length },
+ lockfilePackages: { before: beforeLock.length, after: afterLock.length, added: afterLock.filter((p) => !beforeLock.includes(p)), removed: beforeLock.filter((p) => !afterLock.includes(p)) },
+ },
+}, null, 2));
diff --git a/apps/docs/scripts/prepare-demo.mjs b/apps/docs/scripts/prepare-demo.mjs
index eeeef2b..5bce7fd 100644
--- a/apps/docs/scripts/prepare-demo.mjs
+++ b/apps/docs/scripts/prepare-demo.mjs
@@ -1,4 +1,4 @@
-import { chartTheme } from './hig-tokens.mjs';
+import { chartTheme, tokensCssPath } from './hig-tokens.mjs';
import { cp, mkdir, rm, readFile, writeFile } from 'node:fs/promises';
const repository = new URL('../../../', import.meta.url);
@@ -12,7 +12,7 @@ await rm(output, { recursive: true, force: true });
await mkdir(new URL('demo/', output), { recursive: true });
await Promise.all([
cp(new URL('apps/playground/index.html', repository), new URL('demo/index.html', output)),
- cp(new URL('../vendor/hig/tokens.css', import.meta.url), new URL('assets/hig-tokens.css', output)),
+ cp(tokensCssPath, new URL('assets/hig-tokens.css', output)), // @bloxwap/hig-tokens/css, unmodified
cp(new URL('packages/chart/dist/', repository), new URL('dist/', output), { recursive: true }),
]);
diff --git a/apps/docs/scripts/test-hig-browser.mjs b/apps/docs/scripts/test-hig-browser.mjs
index bee3727..0985f18 100644
--- a/apps/docs/scripts/test-hig-browser.mjs
+++ b/apps/docs/scripts/test-hig-browser.mjs
@@ -2,12 +2,17 @@ import { chromium } from 'playwright-core';
import assert from 'node:assert/strict';
import { mkdir, writeFile } from 'node:fs/promises';
import { spawn } from 'node:child_process';
-import { manifest, colors } from './hig-tokens.mjs';
-const server = spawn(process.execPath, ['apps/docs/scripts/preview.mjs'], { env: { ...process.env, PORT: '0' }, stdio: 'pipe' });
+import { manifest, cssColor } from './hig-tokens.mjs';
+const server = spawn(process.execPath, ['apps/docs/scripts/preview.mjs'], { env: { ...process.env, PORT: process.env.HIG_PREVIEW_PORT ?? '0' }, stdio: 'pipe' });
const origin = await new Promise((resolve, reject) => { server.stdout.once('data', chunk => resolve(`http://127.0.0.1:${chunk.toString().match(/localhost:(\d+)/)[1]}`)); server.once('error', reject); server.once('exit', code => reject(new Error(`Preview exited: ${code}`))); });
let browser;
const evidence = process.env.HIG_EVIDENCE_DIR;
const checks = [];
+/** Resolve two CSS colors in the page (or frame) so oklch(0.205 0 0) and oklch(20.5% 0 0) compare equal. */
+const sameColor = (target, actual, expected) => target.evaluate(([a, b]) => {
+ const probe = (value) => { const el = document.createElement('i'); el.style.color = value; document.body.append(el); const out = getComputedStyle(el).color; el.remove(); return out; };
+ return probe(a) === probe(b) && probe(a) !== '';
+}, [actual, expected]);
function record(check) { checks.push(check); console.log(check); }
try {
browser = await chromium.launch({ channel: 'chrome', args: ['--no-sandbox'] });
@@ -37,7 +42,7 @@ try {
window.copyStatusLog = [];
const observe = () => {
for (const el of document.querySelectorAll('[role="status"]')) {
- if (el.textContent) window.copyStatusLog.push({ text: el.textContent, visibleError: el.classList.contains('copy-feedback') && el.getBoundingClientRect().height > 1 });
+ if (el.textContent) window.copyStatusLog.push({ text: el.textContent, visibleError: /Could not copy|Copy failed/.test(el.textContent) && el.getBoundingClientRect().height > 1 });
}
};
new MutationObserver(observe).observe(document.body, { childList: true, subtree: true, characterData: true, attributes: true });
@@ -47,29 +52,43 @@ try {
return new Promise(resolve => { window.finishCopy = resolve; });
} } });
});
+ // HIG InstallCommand composition: a repeat activation joins the pending clipboard write.
const copy = page.getByRole('button', { name: 'Copy install command', exact: true });
await copy.click();
- assert.equal(await copy.isDisabled(), true);
- assert.equal(await copy.getAttribute('aria-busy'), 'true');
- await copy.evaluate(el => el.click());
+ await copy.click();
assert.equal(await page.evaluate(() => window.copyCalls), 1);
await page.evaluate(() => window.finishCopy());
- await page.waitForFunction(() => window.copyStatusLog.some(entry => entry.text.includes('Code copied to clipboard.')));
+ await page.waitForFunction(() => window.copyStatusLog.some(entry => entry.text === 'Copied'));
assert.equal(await page.evaluate(() => window.copiedText), 'bun add @bloxwap/chart');
+ // HIG IconButton copy for the code window: disabled and busy while pending, then announced.
+ const copyCode = page.getByRole('button', { name: 'Copy code', exact: true });
+ await copyCode.click();
+ assert.equal(await copyCode.isDisabled(), true);
+ assert.equal(await copyCode.getAttribute('aria-busy'), 'true');
+ await copyCode.evaluate(el => el.click());
+ assert.equal(await page.evaluate(() => window.copyCalls), 2);
+ await page.evaluate(() => window.finishCopy());
+ await page.waitForFunction(() => window.copyStatusLog.some(entry => entry.text.includes('Code copied to clipboard.')));
+ assert.match(await page.evaluate(() => window.copiedText), /from '@bloxwap\/chart\/react'/);
await page.evaluate(() => Object.defineProperty(navigator, 'clipboard', { configurable: true, value: { writeText: async () => { throw new Error('denied'); } } }));
await copy.click();
+ await page.waitForFunction(() => window.copyStatusLog.some(entry => entry.text.includes('Copy failed. Select the command') && entry.visibleError));
+ await copyCode.click();
await page.waitForFunction(() => window.copyStatusLog.some(entry => entry.text.includes('Could not copy.') && entry.visibleError));
- assert.equal(await copy.isDisabled(), false);
+ assert.equal(await copyCode.isDisabled(), false);
record('Copy: pending suppresses repeat activation, success status, visible denial and recovery');
const presentation = await page.evaluate(() => {
const css = getComputedStyle(document.documentElement);
- return { surface: css.getPropertyValue('--hig-surface').trim(), font: getComputedStyle(document.body).fontFamily, motion: css.getPropertyValue('--duration-fast').trim(), transform: getComputedStyle(document.querySelector('.btn')).transform };
+ 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()) };
});
- assert.equal(presentation.surface, colors.surface);
+ 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.deepEqual(presentation.legacy, []);
assert.match(presentation.motion, /^0(m?s)$/);
assert.equal(presentation.transform, 'none');
- record('Pinned HIG palette, Bloxwap typography and reduced motion');
+ record('@bloxwap/hig-tokens palette, HIG controls in Bloxwap typography, no legacy aliases, reduced motion');
await page.getByRole('tab', { name: 'npm', exact: true }).click();
await page.getByRole('tab', { name: 'bun', exact: true }).click();
if (evidence) { await mkdir(evidence, { recursive: true }); await page.screenshot({ path: `${evidence}/after-home.png`, fullPage: true }); }
@@ -88,7 +107,7 @@ try {
assert.equal(demo.light, false);
assert.equal(demo.themeButtons, 0);
assert.match(demo.font, /Bloxwap Sans/);
- assert.equal(demo.panel, colors.surface);
+ assert.ok(await sameColor(page.frames().find(f => f !== page.mainFrame()), demo.panel, cssColor('surface')), `demo panel ${demo.panel}`);
await frame.locator('button[title="Settings"]').click();
await frame.getByRole('dialog', { name: 'Chart settings' }).waitFor();
await page.keyboard.press('Escape');
@@ -103,6 +122,32 @@ try {
await page.getByRole('dialog').getByRole('option').first().waitFor();
await page.keyboard.press('Escape');
record('Static search index, results and keyboard dismissal');
+ await page.goto(`${origin}/chart/docs/getting-started/`, { waitUntil: 'domcontentloaded' });
+ const callout = page.locator('.docs-callout [data-severity="warning"]');
+ assert.match(await callout.innerText(), /Developer preview/);
+ assert.equal(await callout.getAttribute('role'), null);
+ await page.keyboard.press('Tab');
+ await page.keyboard.press('Tab');
+ const focused = await page.evaluate(() => { const el = document.activeElement; return { tag: el.tagName, outline: getComputedStyle(el).outlineStyle }; });
+ assert.ok(['A', 'BUTTON'].includes(focused.tag));
+ assert.notEqual(focused.outline, 'none');
+ // Fumadocs binds the shortcut after hydration; retry until the dialog opens.
+ for (let attempt = 0; attempt < 20 && !await page.getByRole('combobox', { name: 'Search' }).isVisible(); attempt++) {
+ await page.keyboard.press('ControlOrMeta+k');
+ await page.waitForTimeout(250);
+ }
+ await page.getByRole('combobox', { name: 'Search' }).fill('qxzqxzqxz');
+ await page.getByRole('dialog').getByRole('status').filter({ hasText: 'No results' }).waitFor();
+ await page.keyboard.press('Escape');
+ record('HIG callout (not announced), keyboard focus ring, search shortcut and empty results');
+ await context.route('**/search.json', route => route.abort());
+ await page.goto(`${origin}/chart/docs/guides/data/`, { waitUntil: 'domcontentloaded' });
+ await page.getByRole('button', { name: /search/i }).first().click();
+ await page.getByRole('combobox', { name: 'Search' }).fill('datafeed');
+ await page.getByRole('dialog').getByRole('alert').filter({ hasText: 'Search is unavailable' }).waitFor();
+ await page.keyboard.press('Escape');
+ await context.unroute('**/search.json');
+ record('Search index failure shows the HIG error alert with recovery text');
await page.evaluate(() => { document.documentElement.dir = 'rtl'; document.documentElement.style.fontSize = '200%'; });
assert.equal(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth), true);
record('Representative docs: RTL and 200% root text without page overflow');
@@ -125,6 +170,6 @@ try {
record('Touch emulation: 48px copy target, narrow layout and mobile navigation');
if (evidence) await phone.screenshot({ path: `${evidence}/after-mobile.png`, fullPage: true });
assert.deepEqual(errors, []);
- console.log(JSON.stringify({ browser: await browser.version(), checks, demo, limitations: ['Linux Chromium emulation only; no physical touch/haptics, iOS, VoiceOver or TalkBack certification'] }, null, 2));
+ console.log(JSON.stringify({ browser: await browser.version(), checks, demo, limitations: ['Automated Chrome (Playwright) with touch emulation only; no physical touch/haptics, iOS, Android, VoiceOver or TalkBack certification'] }, null, 2));
if (evidence) await writeFile(`${evidence}/browser.json`, JSON.stringify({ browser: await browser.version(), checks, demo }, null, 2) + '\n');
} finally { await browser?.close(); server.kill(); }
diff --git a/apps/docs/styles/hig-roles.generated.css b/apps/docs/styles/hig-roles.generated.css
new file mode 100644
index 0000000..d992734
--- /dev/null
+++ b/apps/docs/styles/hig-roles.generated.css
@@ -0,0 +1,31 @@
+/* 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/apps/docs/styles/hig.generated.css b/apps/docs/styles/hig.generated.css
deleted file mode 100644
index e64f889..0000000
--- a/apps/docs/styles/hig.generated.css
+++ /dev/null
@@ -1,67 +0,0 @@
-/* Generated from pinned HIG manifest by scripts/hig-tokens.mjs. */
-:root {
- --bloxwap-black: var(--hig-background);
- --bloxwap-green: var(--hig-action);
- --background: var(--hig-background);
- --foreground: var(--hig-text);
- --card: var(--hig-surface);
- --card-foreground: var(--hig-text);
- --popover: var(--hig-nested);
- --popover-foreground: var(--hig-text);
- --primary: var(--hig-action);
- --primary-foreground: var(--hig-onAction);
- --secondary: var(--hig-nested);
- --secondary-foreground: var(--hig-text);
- --muted: var(--hig-nested);
- --muted-foreground: var(--hig-muted);
- --accent: var(--hig-nested);
- --accent-foreground: var(--hig-text);
- --destructive: var(--hig-negative);
- --border: var(--hig-border);
- --input: var(--hig-border);
- --ring: var(--hig-focus);
- --radius-sm: 0.375rem;
- --radius-md: 0.5rem;
- --radius-lg: 0.625rem;
- --radius-xl: 0.875rem;
- --radius-full: 624.9375rem;
- --radius: var(--radius-lg);
- --radius-2xl: var(--radius-xl);
- --space-xs: var(--hig-space-xs);
- --space-sm: var(--hig-space-sm);
- --space-md: var(--hig-space-md);
- --space-lg: var(--hig-space-lg);
- --space-xl: var(--hig-space-xl);
- --space-2xl: var(--hig-space-xxl);
- --space-3xl: var(--hig-space-xxxl);
- --space-4xl: var(--hig-space-section);
- --space-5xl: var(--hig-space-section);
- --space-6xl: var(--hig-space-section);
- --duration-fast: 180ms;
- --duration-base: 260ms;
- --duration-slow: 320ms;
- --type-caption-size: 0.75rem;
- --type-caption-line: 1.125rem;
- --type-caption-weight: 500;
- --type-label-size: 0.875rem;
- --type-label-line: 1.25rem;
- --type-label-weight: 600;
- --type-body-size: 1rem;
- --type-body-line: 1.5rem;
- --type-body-weight: 400;
- --type-title-size: 1.5rem;
- --type-title-line: 1.875rem;
- --type-title-weight: 600;
- --type-display-size: 3rem;
- --type-display-line: 3.25rem;
- --type-display-weight: 600;
- --control-h: 3rem;
- --control-h-sm: 2.25rem;
- --ease-spring: cubic-bezier(0.22, 1, 0.36, 1);
- --ease-standard: ease-out;
- --icon-md: 16px;
- --control-min-w: 6rem;
-}
-@media (prefers-reduced-motion: reduce) {
- :root { --duration-fast: 0ms; --duration-base: 0ms; --duration-slow: 0ms; }
-}
diff --git a/apps/docs/vendor/hig/LICENSE b/apps/docs/vendor/hig/LICENSE
deleted file mode 100644
index daec4c1..0000000
--- a/apps/docs/vendor/hig/LICENSE
+++ /dev/null
@@ -1,5 +0,0 @@
-MIT License
-
-Copyright (c) 2026 Bloxwap
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to inclusion of this notice. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED.
diff --git a/apps/docs/vendor/hig/manifest.json b/apps/docs/vendor/hig/manifest.json
deleted file mode 100644
index 2d92b5a..0000000
--- a/apps/docs/vendor/hig/manifest.json
+++ /dev/null
@@ -1,151 +0,0 @@
-{
- "version": "0.1.0",
- "mode": "dark",
- "provenance": "font/apps/docs/styles/tokens.css; monorepo inaccessible; authored OKLCH unavailable",
- "colors": {
- "background": {
- "srgb": "#0a0a0a",
- "p3": [
- 0.04,
- 0.04,
- 0.04
- ]
- },
- "surface": {
- "srgb": "#171717"
- },
- "nested": {
- "srgb": "#262626"
- },
- "text": {
- "srgb": "#fafafa"
- },
- "muted": {
- "srgb": "#a1a1a1"
- },
- "faint": {
- "srgb": "#858585"
- },
- "border": {
- "srgb": "#3a3a3a"
- },
- "action": {
- "srgb": "#00ff3f",
- "p3": [
- 0,
- 1,
- 0.24
- ]
- },
- "onAction": {
- "srgb": "#0a0a0a",
- "p3": [
- 0.04,
- 0.04,
- 0.04
- ]
- },
- "focus": {
- "srgb": "#00ff3f",
- "p3": [
- 0,
- 1,
- 0.24
- ]
- },
- "buy": {
- "srgb": "#00ff3f",
- "p3": [
- 0,
- 1,
- 0.24
- ]
- },
- "sell": {
- "srgb": "#ff5c5c"
- },
- "positive": {
- "srgb": "#00ff3f",
- "p3": [
- 0,
- 1,
- 0.24
- ]
- },
- "negative": {
- "srgb": "#ff5c5c"
- },
- "warning": {
- "srgb": "#f5b300"
- },
- "info": {
- "srgb": "#35b5ff",
- "p3": [
- 0.18,
- 0.72,
- 1
- ]
- }
- },
- "space": {
- "xs": 4,
- "sm": 8,
- "md": 12,
- "lg": 16,
- "xl": 20,
- "xxl": 24,
- "xxxl": 32,
- "section": 48
- },
- "radius": {
- "sm": 6,
- "md": 8,
- "lg": 10,
- "xl": 14,
- "pill": 9999
- },
- "density": {
- "comfortable": {
- "control": 48,
- "target": 48,
- "gap": 12
- },
- "compact": {
- "control": 36,
- "target": 36,
- "gap": 8
- }
- },
- "motion": {
- "fast": 180,
- "base": 260,
- "slow": 320
- },
- "type": {
- "caption": {
- "size": 12,
- "lineHeight": 18,
- "weight": 500
- },
- "label": {
- "size": 14,
- "lineHeight": 20,
- "weight": 600
- },
- "body": {
- "size": 16,
- "lineHeight": 24,
- "weight": 400
- },
- "title": {
- "size": 24,
- "lineHeight": 30,
- "weight": 600
- },
- "display": {
- "size": 48,
- "lineHeight": 52,
- "weight": 600
- }
- }
-}
diff --git a/apps/docs/vendor/hig/provenance.json b/apps/docs/vendor/hig/provenance.json
deleted file mode 100644
index 32d18a5..0000000
--- a/apps/docs/vendor/hig/provenance.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "repository": "bloxwap/hig",
- "commit": "1a776b9326c6ba03702894943b3edbfd178c4b40",
- "version": "0.1.0 candidate",
- "distribution": "approved plain CSS + generated manifest; no npm release exists",
- "sha256": {
- "tokens.css": "1c60086af561070fdc1ee6056d6bc43bd8eed78614c318d466ee3850dc317609",
- "manifest.json": "1f7383521cf9d3a559ae4abb91e64aa26f3fba03df22768aaeb26f6df822a454",
- "LICENSE": "3e120a937544f1299495b556d63e040f2ee4678d0def27cfbf71ac62e75b022e"
- }
-}
diff --git a/apps/docs/vendor/hig/tokens.css b/apps/docs/vendor/hig/tokens.css
deleted file mode 100644
index 3b26998..0000000
--- a/apps/docs/vendor/hig/tokens.css
+++ /dev/null
@@ -1,35 +0,0 @@
-:root { color-scheme: dark;
- --hig-background: #0a0a0a;
- --hig-surface: #171717;
- --hig-nested: #262626;
- --hig-text: #fafafa;
- --hig-muted: #a1a1a1;
- --hig-faint: #858585;
- --hig-border: #3a3a3a;
- --hig-action: #00ff3f;
- --hig-onAction: #0a0a0a;
- --hig-focus: #00ff3f;
- --hig-buy: #00ff3f;
- --hig-sell: #ff5c5c;
- --hig-positive: #00ff3f;
- --hig-negative: #ff5c5c;
- --hig-warning: #f5b300;
- --hig-info: #35b5ff;
- --hig-space-xs: 0.25rem;
- --hig-space-sm: 0.5rem;
- --hig-space-md: 0.75rem;
- --hig-space-lg: 1rem;
- --hig-space-xl: 1.25rem;
- --hig-space-xxl: 1.5rem;
- --hig-space-xxxl: 2rem;
- --hig-space-section: 3rem;
-}
-@supports (color: color(display-p3 1 1 1)) { :root {
- --hig-background: color(display-p3 0.04 0.04 0.04);
- --hig-action: color(display-p3 0 1 0.24);
- --hig-onAction: color(display-p3 0.04 0.04 0.04);
- --hig-focus: color(display-p3 0 1 0.24);
- --hig-buy: color(display-p3 0 1 0.24);
- --hig-positive: color(display-p3 0 1 0.24);
- --hig-info: color(display-p3 0.18 0.72 1);
-} }
diff --git a/docs/hig-adoption/README.md b/docs/hig-adoption/README.md
index 7d78270..92197a2 100644
--- a/docs/hig-adoption/README.md
+++ b/docs/hig-adoption/README.md
@@ -1,44 +1,88 @@
-# Chart adoption · HIG 0.1.0 candidate
-
-Owning PR: https://github.com/bloxwap/chart/pull/52.
-
-Owner: Chart maintainers. Recorded 2026-10-13; 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). It supplies a token-themed embedded chart for the #33 Pro pilot, but cannot certify any product origin. #35's React Native application is outside this repository; the HIG candidate explicitly does not support a native canvas chart.
-
-## Version and installation
-
-HIG source: [1a776b9326c6ba03702894943b3edbfd178c4b40](https://github.com/bloxwap/hig/tree/1a776b9326c6ba03702894943b3edbfd178c4b40), 0.1.0 candidate. Read its `migrations/README.md`, `docs/migration/sdk-docs.md`, `docs/components/chart.md` and `docs/governance/adoption.md`. The candidate is unpublished: `apps/docs/vendor/hig` retains the approved plain CSS distribution, generated manifest and MIT license byte-for-byte, with SHA-256 provenance. It does not pretend to be a stable npm package. No React/native/feedback dependency is added to the SDK.
-
-The shell imports the distribution explicitly. `scripts/hig-tokens.mjs` derives the Fumadocs aliases, spacing, geometry, density, type roles, durations and canvas-compatible sRGB chart theme from that manifest. Fonts stay on the existing exact `@bloxwap/font@0.1.2` dependency; Bloxwap Sans now also supplies headings. The chart iframe receives its own CSS/font assets and the same theme for initial config, toolbar theme mapping and settings reset. Docs embed dark mode explicitly; the standalone SDK playground still owns its theme preferences.
-
-## Migrated surfaces and preserved behavior
-
-Landing/docs shell, navigation/search palette, code/install/copy actions, callout surfaces, typography, focus rings and reduced-motion presentation follow HIG. Custom docs controls use compact 36px desktop targets and comfortable 48px coarse-pointer targets. Copy actions share one implementation, protect pending activation, announce success and show actionable failure text. Search exposes index failure and keeps Fumadocs' keyboard/focus behavior. The chart preview has a text description and loading status; the chart's existing market failure status and offline random-walk recovery remain intact.
-
-The existing SDK engine, exports, API examples, static base paths, metadata, deep links, drawings, settings, pan/zoom/crosshair and persisted preferences remain owned by Chart. No audio/haptic adapter is installed and no cues are emitted on chart ticks, focus or control actions. Existing saved choices are not rewritten. The copied 5,921-byte palette and unused 116,056-byte Space Grotesk source font were removed after reference checks. The two static Bloxwap OG font instances remain used and retain their OFL notice.
-
-## Validation and measured evidence
-
-Run on Node 24: `npm ci`, `npm run lint`, `npm run typecheck`, `npm run coverage`, and `NEXT_PUBLIC_BASE_PATH=/chart NEXT_PUBLIC_SITE_URL=https://bloxwap.github.io npm run docs:build`, then `npm run docs:test`. The browser tests use installed Chrome (Linux) and an ephemeral static-preview port. They cover install keyboard selection, copy success/denial/pending protection, HIG variables/fonts/reduced motion, offline chart data and settings dismissal, search results, RTL/200% root text, coarse-pointer copy targets and mobile navigation. Landing control and mobile shell checks intentionally block the iframe; the distinctive demo is validated separately on its docs page. Mobile shell checks they do not certify mobile chart gestures.
-
-See `browser.json`, `size-report.json` and 1440×1000 before/after landing/playground captures (landing shell isolates the iframe; playground uses the same offline synthetic market and browser, with live timestamps and interaction state rather than a pixel-diff assertion) in this folder. Reproduce totals with `node apps/docs/scripts/measure-hig.mjs e08198bcff27451defeca238e832d4be54be1896`. Size totals count uncompressed emitted CSS/JavaScript/font files across the complete export, not transfer size or per-route bundles. The baseline is `e08198bcff27451defeca238e832d4be54be1896`, built using the same Node/Next/base-path settings. All 1,869 SDK tests pass; coverage is 100% line/branch/function across 138 reported files. Static-export verification checks 30 HTML pages, links/assets, search, social cards and the configured `/chart` paths.
+# Chart docs adoption · HIG 0.1.0 candidate
+
+Owner: Chart maintainers. Updated 2026-10-14; 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).
+
+| 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)) |
+
+## What changed in step 2
+
+- **Tokens and fonts.** The shell imports `@bloxwap/hig-tokens/css` and `@bloxwap/hig-react/css` and
+ uses `--hig-*` roles directly. The shadcn-style alias layer (`--card`, `--bloxwap-green`, `--space-*`, …)
+ and the copied token snapshot are gone. Radius/type/motion/density roles, which the CSS distribution
+ does not emit yet, are generated from the package manifest (`styles/hig-roles.generated.css`). Body and
+ code fonts stay on `@bloxwap/font@0.1.2`; the social-card renderer now reads the static Bloxwap Sans faces
+ from `@bloxwap/hig-tokens/fonts` instead of copied TTFs.
+- **Shell, navigation and search.** Fumadocs keeps layout, sidebar, mobile navigation and the static
+ search index. HIG roles theme it; search failure renders HIG `Alert`, no results renders HIG `EmptyState`.
+- **Code, install and copy.** Install tabs are HIG `Tabs`; each command follows the HIG `InstallCommand`
+ recipe (HIG `Surface` + `Button`, "Copied" / manual-selection recovery). The code-window copy is a HIG
+ `IconButton` with `busy`. A shared `useClipboard` joins repeat activations to the pending write.
+- **Callouts, forms and states.** MDX `` renders HIG `Callout` (static, not a live region); the
+ quick start uses it for the developer-preview notice. The playground loading status is HIG `Spinner`.
+ The docs have no forms besides Fumadocs search; the chart settings forms belong to the SDK demo.
+- **Distinctive demo.** The iframe playground still runs the SDK's own controls and engine. It receives
+ `@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
+
+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
+`node apps/docs/scripts/measure-hig.mjs c4dff80`.
+
+| Measure | Before | After | Delta |
+| --- | ---: | ---: | ---: |
+| Emitted CSS (bytes, whole export) | 145,759 | 152,712 | +6,953 |
+| Emitted JS (bytes, whole export) | 2,172,631 | 2,188,721 | +16,090 |
+| Emitted fonts (bytes) | 14,205,712 | 14,205,712 | 0 |
+| Static export total (bytes / files) | 29,653,520 / 826 | 29,686,679 / 827 | +33,159 / +1 |
+| Hand-maintained docs CSS (files / lines) | 2 / 460 | 1 / 406 | −1 / −54 |
+| Generated token adapter (bytes) | 2,172 | 1,046 | −1,126 |
+| Docs source files removed | | 9 files, 904,638 bytes | copied tokens, manifest, adapter, 2 TTFs + notices |
+| Docs direct dependencies | 16 | 19 | +3 (`@bloxwap/hig-*`) |
+| Lockfile packages | 400 | 403 | +3, no transitive additions |
+
+The output grows slightly: HIG's component stylesheet and its OKLCH token block now ship (+7 KB CSS),
+and HIG `Button`/`Tabs`/`Surface`/`status` modules replace the local primitives (+16 KB JS, uncompressed,
+whole export). In exchange, nine copied files and the alias layer are gone, and tokens/controls now
+update with the HIG package instead of by hand. The repository also carries the 4.2 MB tokens tarball,
+most of which is native TTFs the docs do not use.
+
+Screenshots in [`hig-7c67a07/`](hig-7c67a07/) show home, quick start, configuration reference and
+playground at 1440×1000 and 390×844, before and after (Chrome via Playwright, external requests
+blocked so both playground captures use the offline synthetic market; timestamps differ).
+
+## Validation
+
+See [`hig-7c67a07/validation.txt`](hig-7c67a07/validation.txt) and
+[`hig-7c67a07/browser.json`](hig-7c67a07/browser.json). CI (`.github/workflows/docs.yml`) runs
+`npm ci`, the `/chart` production export with link/asset checks, `docs:check`, `lint` (HIG provenance and
+copy guards) and `docs:test` (browser checks).
## Matrix row, exceptions and rollback
| 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; Font 0.1.2 | Chart maintainers | Owning implementation and automated Linux evidence in this PR | Physical touch/mobile chart gestures, assistive technology and iOS hardware review; 2026-11-13 |
-| chart / published SDK | Engine and optional DOM UI | SDK 0.3.0 | Chart maintainers | Public SDK remains host-configurable; docs-only HIG presentation | Product owners must configure HIG theme for their host; 2026-11-13 |
-
-The canonical org matrix is in `bloxwap/hig/planning/adoption.json`; its Chart row needs this owning PR link and validated version copied upstream by HIG maintainers. No other repository is modified. The decorative OG spectrum retains purple/pink/yellow brand colors absent from the candidate's semantic manifest; these are illustration assets, not a second control palette (Chart maintainers, review 2026-11-13). Fumadocs remains the shell/search primitive owner; wholesale folder copying is unnecessary.
-
-Linux Chromium screenshots and touch emulation are not physical-device or assistive-technology proof. iOS hardware, VoiceOver, TalkBack, tactile feedback, real mobile chart gesture responsiveness and wide-gamut display appearance are unperformed. The candidate still lacks stable publication and the product source audit. #33 needs owning app/Pro/marketing migrations and evidence; #34 needs the other four SDK docs rows and physical/AT follow-up; #35 needs real native migrations/builds/device evidence; #36 needs the canonical matrix update and organization-wide completion review. None of those whole issues is declared satisfied here.
+| 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 / 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 |
-Rollback is an atomic revert of this PR to the baseline commit; restore its CSS/font references and controls together. There are no storage/schema changes. During rollout the host can use the baseline static export; production deployment is not a step in this migration procedure.
+Exceptions (Chart maintainers, review 2026-11-13):
-## Contribution and upgrade contract
+- `.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.
+- 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.
-HIG owns semantic tokens/type/density/motion/state contracts; Font owns fonts; Chart owns the engine/demo. Upgrade by selecting a reviewed HIG source commit, replacing only the canonical CSS/manifest/license, updating provenance hashes, regenerating the adapter, running the checks above and comparing screenshots/bytes. Once stable packages exist, replace the vendored distribution with pinned `@bloxwap/hig-tokens/css` and manifest imports; do not copy component folders or feedback engines. Token/type/geometry or interaction timing changes require HIG semver review.
+The canonical org matrix lives in `bloxwap/hig/planning/adoption.json`; HIG maintainers copy this row,
+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.
-`npm run lint` checks pinned hashes, generated adapter freshness, obsolete imports and copied palette declarations in the shell/components. New exceptions require an owner, exact scope, reason and expiry in this report and a narrowly scoped check update. Run browser tests whenever shell, controls, assets or demo preparation change; CI gates the production export, typecheck, drift checks and browser suite. Link the owning PR and preserve explicit gaps during HIG's v1 completion review rather than closing upstream issues from this repo.
+Rollback: revert this migration'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/after-home.png b/docs/hig-adoption/after-home.png
deleted file mode 100644
index 4803e90..0000000
Binary files a/docs/hig-adoption/after-home.png and /dev/null differ
diff --git a/docs/hig-adoption/after-mobile.png b/docs/hig-adoption/after-mobile.png
deleted file mode 100644
index 2cebc0e..0000000
Binary files a/docs/hig-adoption/after-mobile.png and /dev/null differ
diff --git a/docs/hig-adoption/after-playground.png b/docs/hig-adoption/after-playground.png
deleted file mode 100644
index 257d610..0000000
Binary files a/docs/hig-adoption/after-playground.png and /dev/null differ
diff --git a/docs/hig-adoption/before-home.png b/docs/hig-adoption/before-home.png
deleted file mode 100644
index 6aca8d7..0000000
Binary files a/docs/hig-adoption/before-home.png and /dev/null differ
diff --git a/docs/hig-adoption/before-playground.png b/docs/hig-adoption/before-playground.png
deleted file mode 100644
index 8ab08be..0000000
Binary files a/docs/hig-adoption/before-playground.png and /dev/null differ
diff --git a/docs/hig-adoption/hig-7c67a07/after-configuration-desktop.png b/docs/hig-adoption/hig-7c67a07/after-configuration-desktop.png
new file mode 100644
index 0000000..31256f8
Binary files /dev/null and b/docs/hig-adoption/hig-7c67a07/after-configuration-desktop.png differ
diff --git a/docs/hig-adoption/hig-7c67a07/after-configuration-mobile.png b/docs/hig-adoption/hig-7c67a07/after-configuration-mobile.png
new file mode 100644
index 0000000..e58ba93
Binary files /dev/null and b/docs/hig-adoption/hig-7c67a07/after-configuration-mobile.png differ
diff --git a/docs/hig-adoption/hig-7c67a07/after-getting-started-desktop.png b/docs/hig-adoption/hig-7c67a07/after-getting-started-desktop.png
new file mode 100644
index 0000000..5163c94
Binary files /dev/null and b/docs/hig-adoption/hig-7c67a07/after-getting-started-desktop.png differ
diff --git a/docs/hig-adoption/hig-7c67a07/after-getting-started-mobile.png b/docs/hig-adoption/hig-7c67a07/after-getting-started-mobile.png
new file mode 100644
index 0000000..eb29727
Binary files /dev/null and b/docs/hig-adoption/hig-7c67a07/after-getting-started-mobile.png differ
diff --git a/docs/hig-adoption/hig-7c67a07/after-home-desktop.png b/docs/hig-adoption/hig-7c67a07/after-home-desktop.png
new file mode 100644
index 0000000..b666019
Binary files /dev/null and b/docs/hig-adoption/hig-7c67a07/after-home-desktop.png differ
diff --git a/docs/hig-adoption/hig-7c67a07/after-home-mobile.png b/docs/hig-adoption/hig-7c67a07/after-home-mobile.png
new file mode 100644
index 0000000..56ab45e
Binary files /dev/null and b/docs/hig-adoption/hig-7c67a07/after-home-mobile.png differ
diff --git a/docs/hig-adoption/hig-7c67a07/after-playground-desktop.png b/docs/hig-adoption/hig-7c67a07/after-playground-desktop.png
new file mode 100644
index 0000000..8f01903
Binary files /dev/null and b/docs/hig-adoption/hig-7c67a07/after-playground-desktop.png differ
diff --git a/docs/hig-adoption/hig-7c67a07/after-playground-mobile.png b/docs/hig-adoption/hig-7c67a07/after-playground-mobile.png
new file mode 100644
index 0000000..cbe5351
Binary files /dev/null and b/docs/hig-adoption/hig-7c67a07/after-playground-mobile.png differ
diff --git a/docs/hig-adoption/hig-7c67a07/before-configuration-desktop.png b/docs/hig-adoption/hig-7c67a07/before-configuration-desktop.png
new file mode 100644
index 0000000..1c4c5a4
Binary files /dev/null and b/docs/hig-adoption/hig-7c67a07/before-configuration-desktop.png differ
diff --git a/docs/hig-adoption/hig-7c67a07/before-configuration-mobile.png b/docs/hig-adoption/hig-7c67a07/before-configuration-mobile.png
new file mode 100644
index 0000000..069835c
Binary files /dev/null and b/docs/hig-adoption/hig-7c67a07/before-configuration-mobile.png differ
diff --git a/docs/hig-adoption/hig-7c67a07/before-getting-started-desktop.png b/docs/hig-adoption/hig-7c67a07/before-getting-started-desktop.png
new file mode 100644
index 0000000..edf7001
Binary files /dev/null and b/docs/hig-adoption/hig-7c67a07/before-getting-started-desktop.png differ
diff --git a/docs/hig-adoption/hig-7c67a07/before-getting-started-mobile.png b/docs/hig-adoption/hig-7c67a07/before-getting-started-mobile.png
new file mode 100644
index 0000000..11c47a3
Binary files /dev/null and b/docs/hig-adoption/hig-7c67a07/before-getting-started-mobile.png differ
diff --git a/docs/hig-adoption/hig-7c67a07/before-home-desktop.png b/docs/hig-adoption/hig-7c67a07/before-home-desktop.png
new file mode 100644
index 0000000..e8c1bd8
Binary files /dev/null and b/docs/hig-adoption/hig-7c67a07/before-home-desktop.png differ
diff --git a/docs/hig-adoption/hig-7c67a07/before-home-mobile.png b/docs/hig-adoption/hig-7c67a07/before-home-mobile.png
new file mode 100644
index 0000000..a4af6fc
Binary files /dev/null and b/docs/hig-adoption/hig-7c67a07/before-home-mobile.png differ
diff --git a/docs/hig-adoption/hig-7c67a07/before-playground-desktop.png b/docs/hig-adoption/hig-7c67a07/before-playground-desktop.png
new file mode 100644
index 0000000..1011a86
Binary files /dev/null and b/docs/hig-adoption/hig-7c67a07/before-playground-desktop.png differ
diff --git a/docs/hig-adoption/hig-7c67a07/before-playground-mobile.png b/docs/hig-adoption/hig-7c67a07/before-playground-mobile.png
new file mode 100644
index 0000000..076c57d
Binary files /dev/null and b/docs/hig-adoption/hig-7c67a07/before-playground-mobile.png differ
diff --git a/docs/hig-adoption/browser.json b/docs/hig-adoption/hig-7c67a07/browser.json
similarity index 70%
rename from docs/hig-adoption/browser.json
rename to docs/hig-adoption/hig-7c67a07/browser.json
index d1f4d27..ed45a94 100644
--- a/docs/hig-adoption/browser.json
+++ b/docs/hig-adoption/hig-7c67a07/browser.json
@@ -1,11 +1,13 @@
{
- "browser": "154.0.8037.97",
+ "browser": "155.0.8059.41",
"checks": [
"Install tabs: arrows/Home/End, selected state and focus",
"Copy: pending suppresses repeat activation, success status, visible denial and recovery",
- "Pinned HIG palette, Bloxwap typography and reduced motion",
+ "@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"
],
@@ -24,12 +26,12 @@
"asks": 50
},
"error": null,
- "firstTime": 1791494100,
- "lastTime": 1791943200
+ "firstTime": 1791581400,
+ "lastTime": 1792030500
},
"light": false,
"font": "\"Bloxwap Sans\", system-ui, sans-serif",
- "panel": "#171717",
+ "panel": "oklch(0.205 0 0)",
"themeButtons": 0
}
}
diff --git a/docs/hig-adoption/hig-7c67a07/size-report.json b/docs/hig-adoption/hig-7c67a07/size-report.json
new file mode 100644
index 0000000..f0b0576
--- /dev/null
+++ b/docs/hig-adoption/hig-7c67a07/size-report.json
@@ -0,0 +1,85 @@
+{
+ "baselineCommit": "c4dff80",
+ "hig": {
+ "commit": "7c67a07ce20c02264b0fdc57e5f27d020cfd6fdc",
+ "version": "0.1.0-candidate.sha7c67a07c"
+ },
+ "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": 145759,
+ "javascript": 2172631,
+ "fonts": 14205712,
+ "total": 29653520,
+ "files": 826
+ },
+ "after": {
+ "css": 152712,
+ "javascript": 2188721,
+ "fonts": 14205712,
+ "total": 29686679,
+ "files": 827
+ },
+ "delta": {
+ "css": 6953,
+ "javascript": 16090,
+ "fonts": 0,
+ "total": 33159,
+ "files": 1
+ }
+ },
+ "docsCss": {
+ "handWritten": {
+ "before": {
+ "files": 2,
+ "lines": 460,
+ "bytes": 23147
+ },
+ "after": {
+ "files": 1,
+ "lines": 406,
+ "bytes": 23456
+ }
+ },
+ "generatedAdapterBytes": {
+ "before": 2172,
+ "after": 1046
+ }
+ },
+ "removedDocsFiles": {
+ "count": 9,
+ "bytes": 904638,
+ "files": [
+ "apps/docs/fonts/Bloxwap-OFL.txt",
+ "apps/docs/fonts/BloxwapSans-Black.ttf",
+ "apps/docs/fonts/BloxwapSans-Bold.ttf",
+ "apps/docs/fonts/README.md",
+ "apps/docs/styles/hig.generated.css",
+ "apps/docs/vendor/hig/LICENSE",
+ "apps/docs/vendor/hig/manifest.json",
+ "apps/docs/vendor/hig/provenance.json",
+ "apps/docs/vendor/hig/tokens.css"
+ ]
+ },
+ "dependencies": {
+ "docsDirect": {
+ "before": 16,
+ "after": 19
+ },
+ "lockfilePackages": {
+ "before": 400,
+ "after": 403,
+ "added": [
+ "node_modules/@bloxwap/hig-contracts",
+ "node_modules/@bloxwap/hig-react",
+ "node_modules/@bloxwap/hig-tokens"
+ ],
+ "removed": []
+ }
+ }
+}
diff --git a/docs/hig-adoption/hig-7c67a07/validation.txt b/docs/hig-adoption/hig-7c67a07/validation.txt
new file mode 100644
index 0000000..6ae19c7
--- /dev/null
+++ b/docs/hig-adoption/hig-7c67a07/validation.txt
@@ -0,0 +1,40 @@
+Chart docs HIG package migration, validation record (2026-10-14)
+
+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@7c67a07ce20c02264b0fdc57e5f27d020cfd6fdc, 0.1.0-candidate.sha7c67a07c tarballs.
+Baseline: bloxwap/chart@c4dff80 (main), 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
+
+npm ci exit 0 (no --legacy-peer-deps flag needed; the lock is consistent)
+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.sha7c67a07c tarball
+ provenance, installed versions, generated roles and copy drift checks passed
+npm run typecheck exit 0 (SDK tsc build + docs:check)
+npm run coverage exit 0 - tests 1869, pass 1869, fail 0; 100% line/branch/function (138 rows)
+npm run docs:test exit 0 (HIG_PREVIEW_PORT=4113), checks:
+ - Install tabs (HIG Tabs): ArrowRight/End/Home, aria-selected and roving focus
+ - Copy: install command joins a pending write (1 clipboard call for 2 activations) then "Copied";
+ code-window HIG IconButton is disabled/aria-busy while pending, announces success; both show
+ visible recovery text on clipboard denial
+ - @bloxwap/hig-tokens palette resolved in page, HIG buttons in Bloxwap Sans, no legacy
+ --card/--bloxwap-green/--space-* aliases, reduced motion zeroes HIG durations
+ - Distinctive demo (docs/playground): offline synthetic market, 500 bars, 0 duplicates,
+ 0 missing buckets, WebGL2 backend, 50/50 book levels, locked dark theme, HIG panel color,
+ settings dialog + Escape, pointer hover + wheel zoom
+ - Static search: results for "datafeed", Escape dismissal, Ctrl/Cmd+K shortcut, HIG EmptyState
+ for no results, HIG error Alert when search.json fails to load
+ - HIG Callout on getting-started (warning severity, 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 opens and
+ navigates to /chart/docs/playground/
+node apps/docs/scripts/capture-hig.mjs before (baseline export) and after, PORT 4111/4114:
+ home, getting-started, api/configuration, playground at 1440x1000 and 390x844 (16 PNGs here)
+node apps/docs/scripts/measure-hig.mjs ../chart-baseline/out c4dff80 > 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/docs/hig-adoption/size-report.json b/docs/hig-adoption/size-report.json
deleted file mode 100644
index 38ce19c..0000000
--- a/docs/hig-adoption/size-report.json
+++ /dev/null
@@ -1,34 +0,0 @@
-{
- "baselineCommit": "e08198bcff27451defeca238e832d4be54be1896",
- "higCommit": "1a776b9326c6ba03702894943b3edbfd178c4b40",
- "toolchain": {
- "node": "v24.21.0",
- "next": "16.3.6",
- "basePath": "/chart"
- },
- "measurement": "sum of uncompressed emitted CSS/JS/font files in static export (not transfer or per-route bundle sizes)",
- "before": {
- "css": 146652,
- "javascript": 2183639,
- "fonts": 14321768
- },
- "after": {
- "css": 145759,
- "javascript": 2172631,
- "fonts": 14205712
- },
- "delta": {
- "css": -893,
- "javascript": -11008,
- "fonts": -116056
- },
- "removedSource": {
- "paletteCssBytes": 5921,
- "unusedHeadingFontBytes": 116056
- },
- "notes": [
- "No runtime dependency added; chart SDK API and engine unchanged.",
- "OG static Bloxwap faces remain referenced; their licenses are retained.",
- "Decorative OG spectrum colors are brand assets, not semantic UI palette; exception reviewed 2026-11-13."
- ]
-}
diff --git a/docs/hig-adoption/validation.txt b/docs/hig-adoption/validation.txt
deleted file mode 100644
index f0172a7..0000000
--- a/docs/hig-adoption/validation.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-Local validation on Linux, Node v24.21.0, Next 16.3.6, Chrome 154.0.8037.97
-
-npm run lint: passed (137 icons; HIG provenance/generation/scoped drift checks).
-npm run typecheck: passed (SDK tsc + Next route generation + docs tsc).
-npm run coverage: passed; 1869 tests, 0 failures; 100% line/branch/function, 138 covered files.
-NEXT_PUBLIC_BASE_PATH=/chart NEXT_PUBLIC_SITE_URL=https://bloxwap.github.io npm run docs:build: passed.
-Static export verified: 30 HTML pages, 1530 local links/assets, 25 social cards, /chart base path.
-npm run docs:check: passed on final production build.
-npm run docs:test: passed; see browser.json for observed WebGL2 offline data and individual checks.
-No physical device/iOS/VoiceOver/TalkBack/tactile/wide-gamut certification; see README.md.
diff --git a/package-lock.json b/package-lock.json
index ef93433..a1eab26 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -20,6 +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",
"fumadocs-core": "16.15.14",
"fumadocs-mdx": "15.4.5",
"fumadocs-ui": "16.15.14",
@@ -90,6 +93,54 @@
}
}
},
+ "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==",
+ "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==",
+ "license": "MIT",
+ "dependencies": {
+ "@bloxwap/hig-contracts": "0.1.0-candidate.sha7c67a07c",
+ "@bloxwap/hig-tokens": "0.1.0-candidate.sha7c67a07c"
+ },
+ "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",
+ "react": "^19.1.0",
+ "react-dom": "^19.1.0"
+ },
+ "peerDependenciesMeta": {
+ "@bloxwap/chart": {
+ "optional": true
+ },
+ "@radix-ui/react-dialog": {
+ "optional": true
+ },
+ "@radix-ui/react-dropdown-menu": {
+ "optional": true
+ },
+ "@radix-ui/react-popover": {
+ "optional": true
+ },
+ "@radix-ui/react-tooltip": {
+ "optional": true
+ }
+ }
+ },
+ "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==",
+ "license": "MIT"
+ },
"node_modules/@esbuild/aix-ppc64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz",
@@ -2523,7 +2574,6 @@
"resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz",
"integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@types/unist": "*"
}
@@ -2532,8 +2582,7 @@
"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",
- "peer": true
+ "license": "MIT"
},
"node_modules/@types/ms": {
"version": "2.1.0",
@@ -2555,9 +2604,8 @@
"version": "19.3.0",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz",
"integrity": "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==",
- "devOptional": true,
+ "dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"csstype": "^3.2.2"
}
@@ -2566,9 +2614,8 @@
"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==",
- "devOptional": true,
+ "dev": true,
"license": "MIT",
- "peer": true,
"peerDependencies": {
"@types/react": "^19.3.0"
}
@@ -2769,7 +2816,6 @@
"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"
},
@@ -2996,7 +3042,7 @@
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
- "devOptional": true,
+ "dev": true,
"license": "MIT"
},
"node_modules/debug": {
@@ -3332,7 +3378,6 @@
"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",
@@ -4165,7 +4210,6 @@
"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"
}
@@ -5293,7 +5337,6 @@
"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",
@@ -5521,7 +5564,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.3.0.tgz",
"integrity": "sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -5531,7 +5573,6 @@
"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"
},
@@ -6022,9 +6063,8 @@
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz",
"integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==",
- "devOptional": true,
- "license": "MIT",
- "peer": true
+ "dev": true,
+ "license": "MIT"
},
"node_modules/tapable": {
"version": "2.3.3",
diff --git a/vendor/bloxwap-hig/README.md b/vendor/bloxwap-hig/README.md
new file mode 100644
index 0000000..12790b8
--- /dev/null
+++ b/vendor/bloxwap-hig/README.md
@@ -0,0 +1,34 @@
+# Vendored Bloxwap HIG candidate packages
+
+`@bloxwap/hig-*` is not published to npm. The docs site installs these packed candidate tarballs with
+`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 |
+
+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.
+
+## 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.
+
+## 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.
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
new file mode 100644
index 0000000..751a262
Binary files /dev/null and b/vendor/bloxwap-hig/bloxwap-hig-contracts-0.1.0-candidate.sha7c67a07c.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
new file mode 100644
index 0000000..0c3b865
Binary files /dev/null and b/vendor/bloxwap-hig/bloxwap-hig-react-0.1.0-candidate.sha7c67a07c.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
new file mode 100644
index 0000000..c30feb2
Binary files /dev/null and b/vendor/bloxwap-hig/bloxwap-hig-tokens-0.1.0-candidate.sha7c67a07c.tgz differ
diff --git a/vendor/bloxwap-hig/provenance.json b/vendor/bloxwap-hig/provenance.json
new file mode 100644
index 0000000..b9fc6dc
--- /dev/null
+++ b/vendor/bloxwap-hig/provenance.json
@@ -0,0 +1,12 @@
+{
+ "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)",
+ "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"
+ }
+}