@orangecheck/design 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/tokens.d.mts CHANGED
@@ -38,4 +38,11 @@ interface OcThemePickerProps {
38
38
  }
39
39
  declare function OcThemePicker({ className, triggerClassName, popoverClassName }: OcThemePickerProps): react_jsx_runtime.JSX.Element;
40
40
 
41
- export { DEFAULT_OC_THEME, OC_SKIN_COOKIE, OC_THEMES, type OcTheme, OcThemeBridge, type OcThemeId, OcThemePicker, type OcThemePickerProps, OcThemeProvider, type OcThemeProviderProps, cn, getOcThemeInitScript, isKnownTheme, resolveTheme, useOcSkin };
41
+ interface OcAppearanceMenuProps {
42
+ className?: string;
43
+ triggerClassName?: string;
44
+ popoverClassName?: string;
45
+ }
46
+ declare function OcAppearanceMenu({ className, triggerClassName, popoverClassName, }: OcAppearanceMenuProps): react_jsx_runtime.JSX.Element;
47
+
48
+ export { DEFAULT_OC_THEME, OC_SKIN_COOKIE, OC_THEMES, OcAppearanceMenu, type OcAppearanceMenuProps, type OcTheme, OcThemeBridge, type OcThemeId, OcThemePicker, type OcThemePickerProps, OcThemeProvider, type OcThemeProviderProps, cn, getOcThemeInitScript, isKnownTheme, resolveTheme, useOcSkin };
package/dist/tokens.d.ts CHANGED
@@ -38,4 +38,11 @@ interface OcThemePickerProps {
38
38
  }
39
39
  declare function OcThemePicker({ className, triggerClassName, popoverClassName }: OcThemePickerProps): react_jsx_runtime.JSX.Element;
40
40
 
41
- export { DEFAULT_OC_THEME, OC_SKIN_COOKIE, OC_THEMES, type OcTheme, OcThemeBridge, type OcThemeId, OcThemePicker, type OcThemePickerProps, OcThemeProvider, type OcThemeProviderProps, cn, getOcThemeInitScript, isKnownTheme, resolveTheme, useOcSkin };
41
+ interface OcAppearanceMenuProps {
42
+ className?: string;
43
+ triggerClassName?: string;
44
+ popoverClassName?: string;
45
+ }
46
+ declare function OcAppearanceMenu({ className, triggerClassName, popoverClassName, }: OcAppearanceMenuProps): react_jsx_runtime.JSX.Element;
47
+
48
+ export { DEFAULT_OC_THEME, OC_SKIN_COOKIE, OC_THEMES, OcAppearanceMenu, type OcAppearanceMenuProps, type OcTheme, OcThemeBridge, type OcThemeId, OcThemePicker, type OcThemePickerProps, OcThemeProvider, type OcThemeProviderProps, cn, getOcThemeInitScript, isKnownTheme, resolveTheme, useOcSkin };
package/dist/tokens.js CHANGED
@@ -250,10 +250,120 @@ function OcThemePicker({ className, triggerClassName, popoverClassName }) {
250
250
  )
251
251
  ] });
252
252
  }
253
+ var MODES = [
254
+ { id: "light", label: "light", Icon: lucideReact.Sun },
255
+ { id: "dark", label: "dark", Icon: lucideReact.Moon },
256
+ { id: "system", label: "system", Icon: lucideReact.Monitor }
257
+ ];
258
+ function OcAppearanceMenu({
259
+ className,
260
+ triggerClassName,
261
+ popoverClassName
262
+ }) {
263
+ const { skin, setSkin, themes } = useOcSkin();
264
+ const { theme, setTheme } = nextThemes.useTheme();
265
+ const [open, setOpen] = react.useState(false);
266
+ const [mounted, setMounted] = react.useState(false);
267
+ const rootRef = react.useRef(null);
268
+ react.useEffect(() => setMounted(true), []);
269
+ react.useEffect(() => {
270
+ if (!open) return;
271
+ function onDown(e) {
272
+ if (rootRef.current && !rootRef.current.contains(e.target)) setOpen(false);
273
+ }
274
+ function onKey(e) {
275
+ if (e.key === "Escape") setOpen(false);
276
+ }
277
+ document.addEventListener("mousedown", onDown);
278
+ document.addEventListener("keydown", onKey);
279
+ return () => {
280
+ document.removeEventListener("mousedown", onDown);
281
+ document.removeEventListener("keydown", onKey);
282
+ };
283
+ }, [open]);
284
+ const activeMode = mounted ? theme ?? "system" : null;
285
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: rootRef, className: cn("relative", className), children: [
286
+ /* @__PURE__ */ jsxRuntime.jsx(
287
+ "button",
288
+ {
289
+ type: "button",
290
+ "aria-label": "appearance settings",
291
+ "aria-haspopup": "menu",
292
+ "aria-expanded": open,
293
+ title: "appearance \xB7 mode + theme",
294
+ onClick: () => setOpen((v) => !v),
295
+ className: cn(
296
+ "text-muted-foreground hover:text-foreground inline-flex size-9 items-center justify-center rounded-md transition-colors outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50",
297
+ triggerClassName
298
+ ),
299
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Palette, { className: "size-4", "aria-hidden": true })
300
+ }
301
+ ),
302
+ open && /* @__PURE__ */ jsxRuntime.jsxs(
303
+ "div",
304
+ {
305
+ role: "menu",
306
+ className: cn(
307
+ "bg-popover text-popover-foreground absolute right-0 z-50 mt-2 w-64 overflow-hidden rounded-md border shadow-md",
308
+ popoverClassName
309
+ ),
310
+ children: [
311
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "label-mono text-muted-foreground px-3 pt-3 pb-2", children: "mode" }),
312
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-3 gap-1 px-2 pb-2", children: MODES.map(({ id, label, Icon }) => {
313
+ const active = activeMode === id;
314
+ return /* @__PURE__ */ jsxRuntime.jsxs(
315
+ "button",
316
+ {
317
+ type: "button",
318
+ role: "menuitemradio",
319
+ "aria-checked": active,
320
+ onClick: () => setTheme(id),
321
+ className: cn(
322
+ "flex flex-col items-center gap-1 rounded-md border py-2 text-[11px] transition-colors",
323
+ active ? "border-primary text-foreground bg-accent" : "border-transparent text-muted-foreground hover:bg-accent hover:text-accent-foreground"
324
+ ),
325
+ children: [
326
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "size-4", "aria-hidden": true }),
327
+ label
328
+ ]
329
+ },
330
+ id
331
+ );
332
+ }) }),
333
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "label-mono text-muted-foreground border-t px-3 pt-3 pb-1", children: "theme" }),
334
+ /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "pb-1", children: themes.map((t) => {
335
+ const active = t.id === skin;
336
+ return /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsxs(
337
+ "button",
338
+ {
339
+ type: "button",
340
+ role: "menuitemradio",
341
+ "aria-checked": active,
342
+ onClick: () => setSkin(t.id),
343
+ className: cn(
344
+ "hover:bg-accent hover:text-accent-foreground flex w-full items-start gap-2 px-3 py-2 text-left transition-colors",
345
+ active && "text-foreground"
346
+ ),
347
+ children: [
348
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mt-0.5 size-4 shrink-0", children: active && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "text-primary size-4", "aria-hidden": true }) }),
349
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-0", children: [
350
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block text-sm font-medium", children: t.label }),
351
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground block text-xs leading-snug", children: t.description })
352
+ ] })
353
+ ]
354
+ }
355
+ ) }, t.id);
356
+ }) })
357
+ ]
358
+ }
359
+ )
360
+ ] });
361
+ }
253
362
 
254
363
  exports.DEFAULT_OC_THEME = DEFAULT_OC_THEME;
255
364
  exports.OC_SKIN_COOKIE = OC_SKIN_COOKIE;
256
365
  exports.OC_THEMES = OC_THEMES;
366
+ exports.OcAppearanceMenu = OcAppearanceMenu;
257
367
  exports.OcThemeBridge = OcThemeBridge;
258
368
  exports.OcThemePicker = OcThemePicker;
259
369
  exports.OcThemeProvider = OcThemeProvider;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/tokens/cn.ts","../src/tokens/themes.ts","../src/tokens/provider.tsx","../src/tokens/theme-bridge.tsx","../src/tokens/theme-picker.tsx"],"names":["twMerge","clsx","createContext","useState","useRef","useEffect","useCallback","useMemo","jsx","useContext","ONE_YEAR","useTheme","jsxs","Palette","Check"],"mappings":"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","file":"tokens.js","sourcesContent":["import type { ClassValue } from 'clsx';\n\nimport { clsx } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\n/**\n * Canonical class-name merge for the OrangeCheck family.\n * `clsx` for conditional composition, `tailwind-merge` to resolve\n * conflicting Tailwind utilities (last-wins by property).\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","/**\n * The OrangeCheck theme (skin) registry.\n *\n * A *skin* is the named-theme axis, orthogonal to the light/dark *mode* axis\n * (which stays owned by `next-themes` via the `.dark` class). A skin is applied\n * as `data-oc-theme=\"<id>\"` on `<html>` and persisted in the `oc_skin` cookie at\n * `Domain=.ochk.io` so a choice on one family site carries to every other.\n *\n * Adding a skin = add a `themes/<id>.css` token block in this package and a\n * row here. Every site picks it up on the next `@orangecheck/design` bump; no\n * per-site edits.\n */\nexport interface OcTheme {\n /** Stable id — also the `data-oc-theme` attribute value and the CSS file stem. */\n readonly id: string;\n /** Human label for the picker (lowercase, house voice). */\n readonly label: string;\n /** One-line description shown in the picker / Storybook. */\n readonly description: string;\n}\n\n// Every skin is rooted in the Bitcoin ethos — sharp, flat, cypherpunk — and\n// carries a meaning, not just a colour: the chain, the node, the network, sound\n// money. No SaaS softness.\nexport const OC_THEMES: readonly OcTheme[] = [\n {\n id: 'orangecheck',\n label: 'orangecheck',\n description: 'the chain — Bitcoin orange, sharp 0.25rem corners, Inter + JetBrains Mono',\n },\n {\n id: 'phosphor',\n label: 'phosphor',\n description: 'the node — CRT terminal green, hard 0.125rem, all-mono · sovereignty & verify',\n },\n {\n id: 'lightning',\n label: 'lightning',\n description: 'the network — electric violet (L2), hardest 0rem edges, sans · instant',\n },\n {\n id: 'gold',\n label: 'gold',\n description: 'sound money — digital gold, hard 0.125rem milled edge, mono ledger · 21M',\n },\n] as const;\n\nexport const DEFAULT_OC_THEME = 'orangecheck';\n\nexport type OcThemeId = (typeof OC_THEMES)[number]['id'] | (string & {});\n\nexport function isKnownTheme(id: string | null | undefined): id is OcThemeId {\n return !!id && OC_THEMES.some((t) => t.id === id);\n}\n\nexport function resolveTheme(id: string | null | undefined): string {\n return isKnownTheme(id) ? id : DEFAULT_OC_THEME;\n}\n\n/** Cookie name for the cross-subdomain skin choice. */\nexport const OC_SKIN_COOKIE = 'oc_skin';\n","'use client';\n\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from 'react';\n\nimport {\n DEFAULT_OC_THEME,\n OC_SKIN_COOKIE,\n OC_THEMES,\n resolveTheme,\n type OcTheme,\n} from './themes';\n\n/**\n * OcThemeProvider — the skin (named-theme) axis.\n *\n * Orthogonal to `next-themes`, which owns light/dark via the `.dark` class.\n * This provider owns the `data-oc-theme` attribute on `<html>` and persists the\n * choice in the `oc_skin` cookie at `Domain=.ochk.io`, so a skin chosen on one\n * `*.ochk.io` site is honored on every other. It re-reads the cookie on focus /\n * visibility change so a fresh choice picks up when the user returns to a tab\n * (same cross-site pickup trick `OcAccountMenu` uses for sign-in).\n *\n * Mount once, inside (or beside) the `next-themes` `<ThemeProvider>`:\n *\n * <ThemeProvider attribute=\"class\" defaultTheme=\"system\" enableSystem>\n * <OcThemeProvider>\n * …app…\n * </OcThemeProvider>\n * </ThemeProvider>\n *\n * Pair with `getOcThemeInitScript()` in `_document` to avoid a flash before\n * hydration.\n */\n\nconst ONE_YEAR = 60 * 60 * 24 * 365;\nconst ATTR = 'data-oc-theme';\n\nfunction readSkinCookie(): string | null {\n if (typeof document === 'undefined') return null;\n const prefix = `${OC_SKIN_COOKIE}=`;\n for (const part of document.cookie.split(';')) {\n const trimmed = part.trim();\n if (trimmed.startsWith(prefix)) {\n const v = decodeURIComponent(trimmed.slice(prefix.length));\n return v.length > 0 ? v : null;\n }\n }\n return null;\n}\n\nfunction writeSkinCookie(value: string): void {\n if (typeof document === 'undefined') return;\n const isLocal =\n location.hostname === 'localhost' || location.hostname === '127.0.0.1';\n const parts = [\n `${OC_SKIN_COOKIE}=${encodeURIComponent(value)}`,\n 'Path=/',\n `Max-Age=${ONE_YEAR}`,\n 'SameSite=Lax',\n ];\n if (!isLocal) {\n parts.push('Domain=.ochk.io');\n parts.push('Secure');\n }\n document.cookie = parts.join('; ');\n}\n\nfunction applyAttr(skin: string): void {\n if (typeof document === 'undefined') return;\n document.documentElement.setAttribute(ATTR, skin);\n}\n\ninterface OcSkinContextValue {\n /** Current skin id (always resolved to a known theme). */\n skin: string;\n /** Set the skin: validates, applies the attribute, persists the cookie. */\n setSkin: (id: string) => void;\n /** The full registry, for rendering a picker. */\n themes: readonly OcTheme[];\n}\n\nconst OcSkinContext = createContext<OcSkinContextValue | null>(null);\n\nexport interface OcThemeProviderProps {\n children: ReactNode;\n /** Skin to assume before the cookie is read (defaults to `orangecheck`). */\n defaultSkin?: string;\n}\n\nexport function OcThemeProvider({ children, defaultSkin = DEFAULT_OC_THEME }: OcThemeProviderProps) {\n const [skin, setSkinState] = useState<string>(() => resolveTheme(defaultSkin));\n const hydratedRef = useRef(false);\n\n // Hydrate from the cookie once on mount and align the attribute.\n useEffect(() => {\n if (hydratedRef.current) return;\n hydratedRef.current = true;\n const fromCookie = resolveTheme(readSkinCookie() ?? defaultSkin);\n applyAttr(fromCookie);\n setSkinState(fromCookie);\n }, [defaultSkin]);\n\n // Cross-site pickup: re-read the cookie when the tab regains focus.\n useEffect(() => {\n function sync() {\n const fromCookie = resolveTheme(readSkinCookie() ?? skin);\n if (fromCookie !== skin) {\n applyAttr(fromCookie);\n setSkinState(fromCookie);\n }\n }\n window.addEventListener('focus', sync);\n document.addEventListener('visibilitychange', sync);\n return () => {\n window.removeEventListener('focus', sync);\n document.removeEventListener('visibilitychange', sync);\n };\n }, [skin]);\n\n const setSkin = useCallback((id: string) => {\n const next = resolveTheme(id);\n applyAttr(next);\n writeSkinCookie(next);\n setSkinState(next);\n }, []);\n\n const value = useMemo<OcSkinContextValue>(\n () => ({ skin, setSkin, themes: OC_THEMES }),\n [skin, setSkin]\n );\n\n return <OcSkinContext.Provider value={value}>{children}</OcSkinContext.Provider>;\n}\n\n/** Read the current skin + setter. Throws if used outside `OcThemeProvider`. */\nexport function useOcSkin(): OcSkinContextValue {\n const ctx = useContext(OcSkinContext);\n if (!ctx) {\n throw new Error('useOcSkin must be used within <OcThemeProvider>');\n }\n return ctx;\n}\n\n/**\n * Blocking init script for `_document` (Pages Router) `<Head>`. Applies the\n * cookie's skin to `<html>` before first paint so there is no flash of the\n * default skin. Inject with:\n *\n * <script dangerouslySetInnerHTML={{ __html: getOcThemeInitScript() }} />\n */\nexport function getOcThemeInitScript(defaultSkin: string = DEFAULT_OC_THEME): string {\n // Self-contained IIFE — no imports available at parse time.\n return `(function(){try{var m=document.cookie.match(/(?:^|; )${OC_SKIN_COOKIE}=([^;]*)/);var s=m?decodeURIComponent(m[1]):'${defaultSkin}';if(!s){s='${defaultSkin}';}document.documentElement.setAttribute('${ATTR}',s);}catch(e){document.documentElement.setAttribute('${ATTR}','${defaultSkin}');}})();`;\n}\n","'use client';\n\nimport { useEffect, useRef } from 'react';\nimport { useTheme } from 'next-themes';\n\n/**\n * OcThemeBridge — cross-subdomain persistence for the MODE axis (light/dark).\n *\n * `next-themes` defaults to origin-scoped `localStorage`, so a dark/light choice\n * on one `*.ochk.io` site won't carry to another. This layers an\n * `oc_theme` cookie at `Domain=.ochk.io` on top so the choice is visible\n * family-wide. (The SKIN axis has its own `oc_skin` bridge inside\n * `OcThemeProvider`; this is the mode counterpart, previously duplicated in\n * every consumer repo.)\n *\n * Mount once inside the next-themes `<ThemeProvider>`:\n *\n * <ThemeProvider attribute=\"class\" defaultTheme=\"system\" enableSystem>\n * <OcThemeBridge />\n * <OcThemeProvider>…</OcThemeProvider>\n * </ThemeProvider>\n *\n * The cookie is intentionally non-HttpOnly (next-themes JS reads/writes it) and\n * carries no auth value — a leak only reveals a theme preference.\n */\nconst COOKIE = 'oc_theme';\nconst ALLOWED = new Set(['light', 'dark', 'system']);\nconst ONE_YEAR = 60 * 60 * 24 * 365;\n\nfunction readCookie(name: string): string | null {\n if (typeof document === 'undefined') return null;\n const prefix = `${name}=`;\n for (const part of document.cookie.split(';')) {\n const trimmed = part.trim();\n if (trimmed.startsWith(prefix)) {\n const v = decodeURIComponent(trimmed.slice(prefix.length));\n return v.length > 0 ? v : null;\n }\n }\n return null;\n}\n\nfunction writeCookie(value: string): void {\n if (typeof document === 'undefined') return;\n if (!ALLOWED.has(value)) return;\n const isLocal = location.hostname === 'localhost' || location.hostname === '127.0.0.1';\n const parts = [\n `${COOKIE}=${encodeURIComponent(value)}`,\n 'Path=/',\n `Max-Age=${ONE_YEAR}`,\n 'SameSite=Lax',\n ];\n if (!isLocal) {\n parts.push('Domain=.ochk.io');\n parts.push('Secure');\n }\n document.cookie = parts.join('; ');\n}\n\nexport function OcThemeBridge() {\n const { theme, setTheme } = useTheme();\n const hydratedRef = useRef(false);\n\n useEffect(() => {\n if (hydratedRef.current) return;\n hydratedRef.current = true;\n const cookieValue = readCookie(COOKIE);\n if (cookieValue && ALLOWED.has(cookieValue) && cookieValue !== theme) {\n setTheme(cookieValue);\n }\n }, [theme, setTheme]);\n\n useEffect(() => {\n if (!hydratedRef.current) return;\n if (!theme) return;\n writeCookie(theme);\n }, [theme]);\n\n return null;\n}\n","'use client';\n\nimport { Check, Palette } from 'lucide-react';\nimport { useEffect, useRef, useState } from 'react';\n\nimport { cn } from './cn';\nimport { useOcSkin } from './provider';\n\n/**\n * OcThemePicker — the skin selector. Sits beside `ThemeToggle` in the header.\n * Self-contained dropdown (no Radix): outside-click + Escape to close. Mirrors\n * the ghost-icon affordance of `ThemeToggle`. Gate its mount behind\n * `NEXT_PUBLIC_OC_THEME_PICKER` during staged rollout.\n */\nexport interface OcThemePickerProps {\n className?: string;\n triggerClassName?: string;\n popoverClassName?: string;\n}\n\nexport function OcThemePicker({ className, triggerClassName, popoverClassName }: OcThemePickerProps) {\n const { skin, setSkin, themes } = useOcSkin();\n const [open, setOpen] = useState(false);\n const rootRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n if (!open) return;\n function onDown(e: MouseEvent) {\n if (rootRef.current && !rootRef.current.contains(e.target as Node)) setOpen(false);\n }\n function onKey(e: KeyboardEvent) {\n if (e.key === 'Escape') setOpen(false);\n }\n document.addEventListener('mousedown', onDown);\n document.addEventListener('keydown', onKey);\n return () => {\n document.removeEventListener('mousedown', onDown);\n document.removeEventListener('keydown', onKey);\n };\n }, [open]);\n\n return (\n <div ref={rootRef} className={cn('relative', className)}>\n <button\n type=\"button\"\n aria-label=\"choose theme\"\n aria-haspopup=\"menu\"\n aria-expanded={open}\n title=\"choose theme\"\n onClick={() => setOpen((v) => !v)}\n className={cn(\n 'text-muted-foreground hover:text-foreground inline-flex size-9 items-center justify-center rounded-md transition-colors outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50',\n triggerClassName\n )}\n >\n <Palette className=\"size-4\" aria-hidden />\n </button>\n\n {open && (\n <div\n role=\"menu\"\n className={cn(\n 'bg-popover text-popover-foreground absolute right-0 z-50 mt-2 w-64 overflow-hidden rounded-md border shadow-md',\n popoverClassName\n )}\n >\n <div className=\"label-mono text-muted-foreground px-3 pt-3 pb-1\">theme</div>\n <ul className=\"pb-1\">\n {themes.map((t) => {\n const active = t.id === skin;\n return (\n <li key={t.id}>\n <button\n type=\"button\"\n role=\"menuitemradio\"\n aria-checked={active}\n onClick={() => {\n setSkin(t.id);\n setOpen(false);\n }}\n className={cn(\n 'hover:bg-accent hover:text-accent-foreground flex w-full items-start gap-2 px-3 py-2 text-left transition-colors',\n active && 'text-foreground'\n )}\n >\n <span className=\"mt-0.5 size-4 shrink-0\">\n {active && <Check className=\"size-4 text-primary\" aria-hidden />}\n </span>\n <span className=\"min-w-0\">\n <span className=\"block text-sm font-medium\">{t.label}</span>\n <span className=\"text-muted-foreground block text-xs leading-snug\">\n {t.description}\n </span>\n </span>\n </button>\n </li>\n );\n })}\n </ul>\n </div>\n )}\n </div>\n );\n}\n"]}
1
+ {"version":3,"sources":["../src/tokens/cn.ts","../src/tokens/themes.ts","../src/tokens/provider.tsx","../src/tokens/theme-bridge.tsx","../src/tokens/theme-picker.tsx","../src/tokens/appearance-menu.tsx"],"names":["twMerge","clsx","createContext","useState","useRef","useEffect","useCallback","useMemo","jsx","useContext","ONE_YEAR","useTheme","jsxs","Palette","Check","Sun","Moon","Monitor"],"mappings":"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","file":"tokens.js","sourcesContent":["import type { ClassValue } from 'clsx';\n\nimport { clsx } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\n/**\n * Canonical class-name merge for the OrangeCheck family.\n * `clsx` for conditional composition, `tailwind-merge` to resolve\n * conflicting Tailwind utilities (last-wins by property).\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","/**\n * The OrangeCheck theme (skin) registry.\n *\n * A *skin* is the named-theme axis, orthogonal to the light/dark *mode* axis\n * (which stays owned by `next-themes` via the `.dark` class). A skin is applied\n * as `data-oc-theme=\"<id>\"` on `<html>` and persisted in the `oc_skin` cookie at\n * `Domain=.ochk.io` so a choice on one family site carries to every other.\n *\n * Adding a skin = add a `themes/<id>.css` token block in this package and a\n * row here. Every site picks it up on the next `@orangecheck/design` bump; no\n * per-site edits.\n */\nexport interface OcTheme {\n /** Stable id — also the `data-oc-theme` attribute value and the CSS file stem. */\n readonly id: string;\n /** Human label for the picker (lowercase, house voice). */\n readonly label: string;\n /** One-line description shown in the picker / Storybook. */\n readonly description: string;\n}\n\n// Every skin is rooted in the Bitcoin ethos — sharp, flat, cypherpunk — and\n// carries a meaning, not just a colour: the chain, the node, the network, sound\n// money. No SaaS softness.\nexport const OC_THEMES: readonly OcTheme[] = [\n {\n id: 'orangecheck',\n label: 'orangecheck',\n description: 'the chain — Bitcoin orange, sharp 0.25rem corners, Inter + JetBrains Mono',\n },\n {\n id: 'phosphor',\n label: 'phosphor',\n description: 'the node — CRT terminal green, hard 0.125rem, all-mono · sovereignty & verify',\n },\n {\n id: 'lightning',\n label: 'lightning',\n description: 'the network — electric violet (L2), hardest 0rem edges, sans · instant',\n },\n {\n id: 'gold',\n label: 'gold',\n description: 'sound money — digital gold, hard 0.125rem milled edge, mono ledger · 21M',\n },\n] as const;\n\nexport const DEFAULT_OC_THEME = 'orangecheck';\n\nexport type OcThemeId = (typeof OC_THEMES)[number]['id'] | (string & {});\n\nexport function isKnownTheme(id: string | null | undefined): id is OcThemeId {\n return !!id && OC_THEMES.some((t) => t.id === id);\n}\n\nexport function resolveTheme(id: string | null | undefined): string {\n return isKnownTheme(id) ? id : DEFAULT_OC_THEME;\n}\n\n/** Cookie name for the cross-subdomain skin choice. */\nexport const OC_SKIN_COOKIE = 'oc_skin';\n","'use client';\n\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from 'react';\n\nimport {\n DEFAULT_OC_THEME,\n OC_SKIN_COOKIE,\n OC_THEMES,\n resolveTheme,\n type OcTheme,\n} from './themes';\n\n/**\n * OcThemeProvider — the skin (named-theme) axis.\n *\n * Orthogonal to `next-themes`, which owns light/dark via the `.dark` class.\n * This provider owns the `data-oc-theme` attribute on `<html>` and persists the\n * choice in the `oc_skin` cookie at `Domain=.ochk.io`, so a skin chosen on one\n * `*.ochk.io` site is honored on every other. It re-reads the cookie on focus /\n * visibility change so a fresh choice picks up when the user returns to a tab\n * (same cross-site pickup trick `OcAccountMenu` uses for sign-in).\n *\n * Mount once, inside (or beside) the `next-themes` `<ThemeProvider>`:\n *\n * <ThemeProvider attribute=\"class\" defaultTheme=\"system\" enableSystem>\n * <OcThemeProvider>\n * …app…\n * </OcThemeProvider>\n * </ThemeProvider>\n *\n * Pair with `getOcThemeInitScript()` in `_document` to avoid a flash before\n * hydration.\n */\n\nconst ONE_YEAR = 60 * 60 * 24 * 365;\nconst ATTR = 'data-oc-theme';\n\nfunction readSkinCookie(): string | null {\n if (typeof document === 'undefined') return null;\n const prefix = `${OC_SKIN_COOKIE}=`;\n for (const part of document.cookie.split(';')) {\n const trimmed = part.trim();\n if (trimmed.startsWith(prefix)) {\n const v = decodeURIComponent(trimmed.slice(prefix.length));\n return v.length > 0 ? v : null;\n }\n }\n return null;\n}\n\nfunction writeSkinCookie(value: string): void {\n if (typeof document === 'undefined') return;\n const isLocal =\n location.hostname === 'localhost' || location.hostname === '127.0.0.1';\n const parts = [\n `${OC_SKIN_COOKIE}=${encodeURIComponent(value)}`,\n 'Path=/',\n `Max-Age=${ONE_YEAR}`,\n 'SameSite=Lax',\n ];\n if (!isLocal) {\n parts.push('Domain=.ochk.io');\n parts.push('Secure');\n }\n document.cookie = parts.join('; ');\n}\n\nfunction applyAttr(skin: string): void {\n if (typeof document === 'undefined') return;\n document.documentElement.setAttribute(ATTR, skin);\n}\n\ninterface OcSkinContextValue {\n /** Current skin id (always resolved to a known theme). */\n skin: string;\n /** Set the skin: validates, applies the attribute, persists the cookie. */\n setSkin: (id: string) => void;\n /** The full registry, for rendering a picker. */\n themes: readonly OcTheme[];\n}\n\nconst OcSkinContext = createContext<OcSkinContextValue | null>(null);\n\nexport interface OcThemeProviderProps {\n children: ReactNode;\n /** Skin to assume before the cookie is read (defaults to `orangecheck`). */\n defaultSkin?: string;\n}\n\nexport function OcThemeProvider({ children, defaultSkin = DEFAULT_OC_THEME }: OcThemeProviderProps) {\n const [skin, setSkinState] = useState<string>(() => resolveTheme(defaultSkin));\n const hydratedRef = useRef(false);\n\n // Hydrate from the cookie once on mount and align the attribute.\n useEffect(() => {\n if (hydratedRef.current) return;\n hydratedRef.current = true;\n const fromCookie = resolveTheme(readSkinCookie() ?? defaultSkin);\n applyAttr(fromCookie);\n setSkinState(fromCookie);\n }, [defaultSkin]);\n\n // Cross-site pickup: re-read the cookie when the tab regains focus.\n useEffect(() => {\n function sync() {\n const fromCookie = resolveTheme(readSkinCookie() ?? skin);\n if (fromCookie !== skin) {\n applyAttr(fromCookie);\n setSkinState(fromCookie);\n }\n }\n window.addEventListener('focus', sync);\n document.addEventListener('visibilitychange', sync);\n return () => {\n window.removeEventListener('focus', sync);\n document.removeEventListener('visibilitychange', sync);\n };\n }, [skin]);\n\n const setSkin = useCallback((id: string) => {\n const next = resolveTheme(id);\n applyAttr(next);\n writeSkinCookie(next);\n setSkinState(next);\n }, []);\n\n const value = useMemo<OcSkinContextValue>(\n () => ({ skin, setSkin, themes: OC_THEMES }),\n [skin, setSkin]\n );\n\n return <OcSkinContext.Provider value={value}>{children}</OcSkinContext.Provider>;\n}\n\n/** Read the current skin + setter. Throws if used outside `OcThemeProvider`. */\nexport function useOcSkin(): OcSkinContextValue {\n const ctx = useContext(OcSkinContext);\n if (!ctx) {\n throw new Error('useOcSkin must be used within <OcThemeProvider>');\n }\n return ctx;\n}\n\n/**\n * Blocking init script for `_document` (Pages Router) `<Head>`. Applies the\n * cookie's skin to `<html>` before first paint so there is no flash of the\n * default skin. Inject with:\n *\n * <script dangerouslySetInnerHTML={{ __html: getOcThemeInitScript() }} />\n */\nexport function getOcThemeInitScript(defaultSkin: string = DEFAULT_OC_THEME): string {\n // Self-contained IIFE — no imports available at parse time.\n return `(function(){try{var m=document.cookie.match(/(?:^|; )${OC_SKIN_COOKIE}=([^;]*)/);var s=m?decodeURIComponent(m[1]):'${defaultSkin}';if(!s){s='${defaultSkin}';}document.documentElement.setAttribute('${ATTR}',s);}catch(e){document.documentElement.setAttribute('${ATTR}','${defaultSkin}');}})();`;\n}\n","'use client';\n\nimport { useEffect, useRef } from 'react';\nimport { useTheme } from 'next-themes';\n\n/**\n * OcThemeBridge — cross-subdomain persistence for the MODE axis (light/dark).\n *\n * `next-themes` defaults to origin-scoped `localStorage`, so a dark/light choice\n * on one `*.ochk.io` site won't carry to another. This layers an\n * `oc_theme` cookie at `Domain=.ochk.io` on top so the choice is visible\n * family-wide. (The SKIN axis has its own `oc_skin` bridge inside\n * `OcThemeProvider`; this is the mode counterpart, previously duplicated in\n * every consumer repo.)\n *\n * Mount once inside the next-themes `<ThemeProvider>`:\n *\n * <ThemeProvider attribute=\"class\" defaultTheme=\"system\" enableSystem>\n * <OcThemeBridge />\n * <OcThemeProvider>…</OcThemeProvider>\n * </ThemeProvider>\n *\n * The cookie is intentionally non-HttpOnly (next-themes JS reads/writes it) and\n * carries no auth value — a leak only reveals a theme preference.\n */\nconst COOKIE = 'oc_theme';\nconst ALLOWED = new Set(['light', 'dark', 'system']);\nconst ONE_YEAR = 60 * 60 * 24 * 365;\n\nfunction readCookie(name: string): string | null {\n if (typeof document === 'undefined') return null;\n const prefix = `${name}=`;\n for (const part of document.cookie.split(';')) {\n const trimmed = part.trim();\n if (trimmed.startsWith(prefix)) {\n const v = decodeURIComponent(trimmed.slice(prefix.length));\n return v.length > 0 ? v : null;\n }\n }\n return null;\n}\n\nfunction writeCookie(value: string): void {\n if (typeof document === 'undefined') return;\n if (!ALLOWED.has(value)) return;\n const isLocal = location.hostname === 'localhost' || location.hostname === '127.0.0.1';\n const parts = [\n `${COOKIE}=${encodeURIComponent(value)}`,\n 'Path=/',\n `Max-Age=${ONE_YEAR}`,\n 'SameSite=Lax',\n ];\n if (!isLocal) {\n parts.push('Domain=.ochk.io');\n parts.push('Secure');\n }\n document.cookie = parts.join('; ');\n}\n\nexport function OcThemeBridge() {\n const { theme, setTheme } = useTheme();\n const hydratedRef = useRef(false);\n\n useEffect(() => {\n if (hydratedRef.current) return;\n hydratedRef.current = true;\n const cookieValue = readCookie(COOKIE);\n if (cookieValue && ALLOWED.has(cookieValue) && cookieValue !== theme) {\n setTheme(cookieValue);\n }\n }, [theme, setTheme]);\n\n useEffect(() => {\n if (!hydratedRef.current) return;\n if (!theme) return;\n writeCookie(theme);\n }, [theme]);\n\n return null;\n}\n","'use client';\n\nimport { Check, Palette } from 'lucide-react';\nimport { useEffect, useRef, useState } from 'react';\n\nimport { cn } from './cn';\nimport { useOcSkin } from './provider';\n\n/**\n * OcThemePicker — the skin selector. Sits beside `ThemeToggle` in the header.\n * Self-contained dropdown (no Radix): outside-click + Escape to close. Mirrors\n * the ghost-icon affordance of `ThemeToggle`. Gate its mount behind\n * `NEXT_PUBLIC_OC_THEME_PICKER` during staged rollout.\n */\nexport interface OcThemePickerProps {\n className?: string;\n triggerClassName?: string;\n popoverClassName?: string;\n}\n\nexport function OcThemePicker({ className, triggerClassName, popoverClassName }: OcThemePickerProps) {\n const { skin, setSkin, themes } = useOcSkin();\n const [open, setOpen] = useState(false);\n const rootRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n if (!open) return;\n function onDown(e: MouseEvent) {\n if (rootRef.current && !rootRef.current.contains(e.target as Node)) setOpen(false);\n }\n function onKey(e: KeyboardEvent) {\n if (e.key === 'Escape') setOpen(false);\n }\n document.addEventListener('mousedown', onDown);\n document.addEventListener('keydown', onKey);\n return () => {\n document.removeEventListener('mousedown', onDown);\n document.removeEventListener('keydown', onKey);\n };\n }, [open]);\n\n return (\n <div ref={rootRef} className={cn('relative', className)}>\n <button\n type=\"button\"\n aria-label=\"choose theme\"\n aria-haspopup=\"menu\"\n aria-expanded={open}\n title=\"choose theme\"\n onClick={() => setOpen((v) => !v)}\n className={cn(\n 'text-muted-foreground hover:text-foreground inline-flex size-9 items-center justify-center rounded-md transition-colors outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50',\n triggerClassName\n )}\n >\n <Palette className=\"size-4\" aria-hidden />\n </button>\n\n {open && (\n <div\n role=\"menu\"\n className={cn(\n 'bg-popover text-popover-foreground absolute right-0 z-50 mt-2 w-64 overflow-hidden rounded-md border shadow-md',\n popoverClassName\n )}\n >\n <div className=\"label-mono text-muted-foreground px-3 pt-3 pb-1\">theme</div>\n <ul className=\"pb-1\">\n {themes.map((t) => {\n const active = t.id === skin;\n return (\n <li key={t.id}>\n <button\n type=\"button\"\n role=\"menuitemradio\"\n aria-checked={active}\n onClick={() => {\n setSkin(t.id);\n setOpen(false);\n }}\n className={cn(\n 'hover:bg-accent hover:text-accent-foreground flex w-full items-start gap-2 px-3 py-2 text-left transition-colors',\n active && 'text-foreground'\n )}\n >\n <span className=\"mt-0.5 size-4 shrink-0\">\n {active && <Check className=\"size-4 text-primary\" aria-hidden />}\n </span>\n <span className=\"min-w-0\">\n <span className=\"block text-sm font-medium\">{t.label}</span>\n <span className=\"text-muted-foreground block text-xs leading-snug\">\n {t.description}\n </span>\n </span>\n </button>\n </li>\n );\n })}\n </ul>\n </div>\n )}\n </div>\n );\n}\n","'use client';\n\nimport { Check, Monitor, Moon, Palette, Sun } from 'lucide-react';\nimport { useTheme } from 'next-themes';\nimport { useEffect, useRef, useState } from 'react';\n\nimport { cn } from './cn';\nimport { useOcSkin } from './provider';\n\n/**\n * OcAppearanceMenu — the single header control for ALL appearance settings.\n *\n * One icon button opens a dropdown with both axes:\n * • mode — light / dark / system (next-themes `.dark` class)\n * • theme — the registered skins (data-oc-theme, persisted .ochk.io)\n *\n * Replaces the separate `ThemeToggle` + `OcThemePicker` so every family site\n * carries exactly one appearance control. Self-contained dropdown (no Radix):\n * outside-click + Escape to close. Requires `<OcThemeProvider>` above it (skin\n * axis) and a next-themes `<ThemeProvider>` (mode axis) — both already mounted\n * on every family site.\n */\nexport interface OcAppearanceMenuProps {\n className?: string;\n triggerClassName?: string;\n popoverClassName?: string;\n}\n\nconst MODES = [\n { id: 'light', label: 'light', Icon: Sun },\n { id: 'dark', label: 'dark', Icon: Moon },\n { id: 'system', label: 'system', Icon: Monitor },\n] as const;\n\nexport function OcAppearanceMenu({\n className,\n triggerClassName,\n popoverClassName,\n}: OcAppearanceMenuProps) {\n const { skin, setSkin, themes } = useOcSkin();\n const { theme, setTheme } = useTheme();\n const [open, setOpen] = useState(false);\n const [mounted, setMounted] = useState(false);\n const rootRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => setMounted(true), []);\n\n useEffect(() => {\n if (!open) return;\n function onDown(e: MouseEvent) {\n if (rootRef.current && !rootRef.current.contains(e.target as Node)) setOpen(false);\n }\n function onKey(e: KeyboardEvent) {\n if (e.key === 'Escape') setOpen(false);\n }\n document.addEventListener('mousedown', onDown);\n document.addEventListener('keydown', onKey);\n return () => {\n document.removeEventListener('mousedown', onDown);\n document.removeEventListener('keydown', onKey);\n };\n }, [open]);\n\n // Avoid a hydration mismatch: only reflect the active mode after mount.\n const activeMode = mounted ? (theme ?? 'system') : null;\n\n return (\n <div ref={rootRef} className={cn('relative', className)}>\n <button\n type=\"button\"\n aria-label=\"appearance settings\"\n aria-haspopup=\"menu\"\n aria-expanded={open}\n title=\"appearance · mode + theme\"\n onClick={() => setOpen((v) => !v)}\n className={cn(\n 'text-muted-foreground hover:text-foreground inline-flex size-9 items-center justify-center rounded-md transition-colors outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50',\n triggerClassName\n )}\n >\n <Palette className=\"size-4\" aria-hidden />\n </button>\n\n {open && (\n <div\n role=\"menu\"\n className={cn(\n 'bg-popover text-popover-foreground absolute right-0 z-50 mt-2 w-64 overflow-hidden rounded-md border shadow-md',\n popoverClassName\n )}\n >\n {/* Mode — segmented light / dark / system */}\n <div className=\"label-mono text-muted-foreground px-3 pt-3 pb-2\">mode</div>\n <div className=\"grid grid-cols-3 gap-1 px-2 pb-2\">\n {MODES.map(({ id, label, Icon }) => {\n const active = activeMode === id;\n return (\n <button\n key={id}\n type=\"button\"\n role=\"menuitemradio\"\n aria-checked={active}\n onClick={() => setTheme(id)}\n className={cn(\n 'flex flex-col items-center gap-1 rounded-md border py-2 text-[11px] transition-colors',\n active\n ? 'border-primary text-foreground bg-accent'\n : 'border-transparent text-muted-foreground hover:bg-accent hover:text-accent-foreground'\n )}\n >\n <Icon className=\"size-4\" aria-hidden />\n {label}\n </button>\n );\n })}\n </div>\n\n {/* Theme — the registered skins */}\n <div className=\"label-mono text-muted-foreground border-t px-3 pt-3 pb-1\">\n theme\n </div>\n <ul className=\"pb-1\">\n {themes.map((t) => {\n const active = t.id === skin;\n return (\n <li key={t.id}>\n <button\n type=\"button\"\n role=\"menuitemradio\"\n aria-checked={active}\n onClick={() => setSkin(t.id)}\n className={cn(\n 'hover:bg-accent hover:text-accent-foreground flex w-full items-start gap-2 px-3 py-2 text-left transition-colors',\n active && 'text-foreground'\n )}\n >\n <span className=\"mt-0.5 size-4 shrink-0\">\n {active && (\n <Check className=\"text-primary size-4\" aria-hidden />\n )}\n </span>\n <span className=\"min-w-0\">\n <span className=\"block text-sm font-medium\">{t.label}</span>\n <span className=\"text-muted-foreground block text-xs leading-snug\">\n {t.description}\n </span>\n </span>\n </button>\n </li>\n );\n })}\n </ul>\n </div>\n )}\n </div>\n );\n}\n"]}
package/dist/tokens.mjs CHANGED
@@ -3,7 +3,7 @@ import { twMerge } from 'tailwind-merge';
3
3
  import { createContext, useState, useRef, useEffect, useCallback, useMemo, useContext } from 'react';
4
4
  import { jsx, jsxs } from 'react/jsx-runtime';
5
5
  import { useTheme } from 'next-themes';
6
- import { Palette, Check } from 'lucide-react';
6
+ import { Palette, Check, Sun, Moon, Monitor } from 'lucide-react';
7
7
 
8
8
  // src/tokens/cn.ts
9
9
  function cn(...inputs) {
@@ -248,7 +248,116 @@ function OcThemePicker({ className, triggerClassName, popoverClassName }) {
248
248
  )
249
249
  ] });
250
250
  }
251
+ var MODES = [
252
+ { id: "light", label: "light", Icon: Sun },
253
+ { id: "dark", label: "dark", Icon: Moon },
254
+ { id: "system", label: "system", Icon: Monitor }
255
+ ];
256
+ function OcAppearanceMenu({
257
+ className,
258
+ triggerClassName,
259
+ popoverClassName
260
+ }) {
261
+ const { skin, setSkin, themes } = useOcSkin();
262
+ const { theme, setTheme } = useTheme();
263
+ const [open, setOpen] = useState(false);
264
+ const [mounted, setMounted] = useState(false);
265
+ const rootRef = useRef(null);
266
+ useEffect(() => setMounted(true), []);
267
+ useEffect(() => {
268
+ if (!open) return;
269
+ function onDown(e) {
270
+ if (rootRef.current && !rootRef.current.contains(e.target)) setOpen(false);
271
+ }
272
+ function onKey(e) {
273
+ if (e.key === "Escape") setOpen(false);
274
+ }
275
+ document.addEventListener("mousedown", onDown);
276
+ document.addEventListener("keydown", onKey);
277
+ return () => {
278
+ document.removeEventListener("mousedown", onDown);
279
+ document.removeEventListener("keydown", onKey);
280
+ };
281
+ }, [open]);
282
+ const activeMode = mounted ? theme ?? "system" : null;
283
+ return /* @__PURE__ */ jsxs("div", { ref: rootRef, className: cn("relative", className), children: [
284
+ /* @__PURE__ */ jsx(
285
+ "button",
286
+ {
287
+ type: "button",
288
+ "aria-label": "appearance settings",
289
+ "aria-haspopup": "menu",
290
+ "aria-expanded": open,
291
+ title: "appearance \xB7 mode + theme",
292
+ onClick: () => setOpen((v) => !v),
293
+ className: cn(
294
+ "text-muted-foreground hover:text-foreground inline-flex size-9 items-center justify-center rounded-md transition-colors outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50",
295
+ triggerClassName
296
+ ),
297
+ children: /* @__PURE__ */ jsx(Palette, { className: "size-4", "aria-hidden": true })
298
+ }
299
+ ),
300
+ open && /* @__PURE__ */ jsxs(
301
+ "div",
302
+ {
303
+ role: "menu",
304
+ className: cn(
305
+ "bg-popover text-popover-foreground absolute right-0 z-50 mt-2 w-64 overflow-hidden rounded-md border shadow-md",
306
+ popoverClassName
307
+ ),
308
+ children: [
309
+ /* @__PURE__ */ jsx("div", { className: "label-mono text-muted-foreground px-3 pt-3 pb-2", children: "mode" }),
310
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-3 gap-1 px-2 pb-2", children: MODES.map(({ id, label, Icon }) => {
311
+ const active = activeMode === id;
312
+ return /* @__PURE__ */ jsxs(
313
+ "button",
314
+ {
315
+ type: "button",
316
+ role: "menuitemradio",
317
+ "aria-checked": active,
318
+ onClick: () => setTheme(id),
319
+ className: cn(
320
+ "flex flex-col items-center gap-1 rounded-md border py-2 text-[11px] transition-colors",
321
+ active ? "border-primary text-foreground bg-accent" : "border-transparent text-muted-foreground hover:bg-accent hover:text-accent-foreground"
322
+ ),
323
+ children: [
324
+ /* @__PURE__ */ jsx(Icon, { className: "size-4", "aria-hidden": true }),
325
+ label
326
+ ]
327
+ },
328
+ id
329
+ );
330
+ }) }),
331
+ /* @__PURE__ */ jsx("div", { className: "label-mono text-muted-foreground border-t px-3 pt-3 pb-1", children: "theme" }),
332
+ /* @__PURE__ */ jsx("ul", { className: "pb-1", children: themes.map((t) => {
333
+ const active = t.id === skin;
334
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
335
+ "button",
336
+ {
337
+ type: "button",
338
+ role: "menuitemradio",
339
+ "aria-checked": active,
340
+ onClick: () => setSkin(t.id),
341
+ className: cn(
342
+ "hover:bg-accent hover:text-accent-foreground flex w-full items-start gap-2 px-3 py-2 text-left transition-colors",
343
+ active && "text-foreground"
344
+ ),
345
+ children: [
346
+ /* @__PURE__ */ jsx("span", { className: "mt-0.5 size-4 shrink-0", children: active && /* @__PURE__ */ jsx(Check, { className: "text-primary size-4", "aria-hidden": true }) }),
347
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0", children: [
348
+ /* @__PURE__ */ jsx("span", { className: "block text-sm font-medium", children: t.label }),
349
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground block text-xs leading-snug", children: t.description })
350
+ ] })
351
+ ]
352
+ }
353
+ ) }, t.id);
354
+ }) })
355
+ ]
356
+ }
357
+ )
358
+ ] });
359
+ }
251
360
 
252
- export { DEFAULT_OC_THEME, OC_SKIN_COOKIE, OC_THEMES, OcThemeBridge, OcThemePicker, OcThemeProvider, cn, getOcThemeInitScript, isKnownTheme, resolveTheme, useOcSkin };
361
+ export { DEFAULT_OC_THEME, OC_SKIN_COOKIE, OC_THEMES, OcAppearanceMenu, OcThemeBridge, OcThemePicker, OcThemeProvider, cn, getOcThemeInitScript, isKnownTheme, resolveTheme, useOcSkin };
253
362
  //# sourceMappingURL=tokens.mjs.map
254
363
  //# sourceMappingURL=tokens.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/tokens/cn.ts","../src/tokens/themes.ts","../src/tokens/provider.tsx","../src/tokens/theme-bridge.tsx","../src/tokens/theme-picker.tsx"],"names":["ONE_YEAR","useRef","useEffect","useState","jsx"],"mappings":"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","file":"tokens.mjs","sourcesContent":["import type { ClassValue } from 'clsx';\n\nimport { clsx } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\n/**\n * Canonical class-name merge for the OrangeCheck family.\n * `clsx` for conditional composition, `tailwind-merge` to resolve\n * conflicting Tailwind utilities (last-wins by property).\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","/**\n * The OrangeCheck theme (skin) registry.\n *\n * A *skin* is the named-theme axis, orthogonal to the light/dark *mode* axis\n * (which stays owned by `next-themes` via the `.dark` class). A skin is applied\n * as `data-oc-theme=\"<id>\"` on `<html>` and persisted in the `oc_skin` cookie at\n * `Domain=.ochk.io` so a choice on one family site carries to every other.\n *\n * Adding a skin = add a `themes/<id>.css` token block in this package and a\n * row here. Every site picks it up on the next `@orangecheck/design` bump; no\n * per-site edits.\n */\nexport interface OcTheme {\n /** Stable id — also the `data-oc-theme` attribute value and the CSS file stem. */\n readonly id: string;\n /** Human label for the picker (lowercase, house voice). */\n readonly label: string;\n /** One-line description shown in the picker / Storybook. */\n readonly description: string;\n}\n\n// Every skin is rooted in the Bitcoin ethos — sharp, flat, cypherpunk — and\n// carries a meaning, not just a colour: the chain, the node, the network, sound\n// money. No SaaS softness.\nexport const OC_THEMES: readonly OcTheme[] = [\n {\n id: 'orangecheck',\n label: 'orangecheck',\n description: 'the chain — Bitcoin orange, sharp 0.25rem corners, Inter + JetBrains Mono',\n },\n {\n id: 'phosphor',\n label: 'phosphor',\n description: 'the node — CRT terminal green, hard 0.125rem, all-mono · sovereignty & verify',\n },\n {\n id: 'lightning',\n label: 'lightning',\n description: 'the network — electric violet (L2), hardest 0rem edges, sans · instant',\n },\n {\n id: 'gold',\n label: 'gold',\n description: 'sound money — digital gold, hard 0.125rem milled edge, mono ledger · 21M',\n },\n] as const;\n\nexport const DEFAULT_OC_THEME = 'orangecheck';\n\nexport type OcThemeId = (typeof OC_THEMES)[number]['id'] | (string & {});\n\nexport function isKnownTheme(id: string | null | undefined): id is OcThemeId {\n return !!id && OC_THEMES.some((t) => t.id === id);\n}\n\nexport function resolveTheme(id: string | null | undefined): string {\n return isKnownTheme(id) ? id : DEFAULT_OC_THEME;\n}\n\n/** Cookie name for the cross-subdomain skin choice. */\nexport const OC_SKIN_COOKIE = 'oc_skin';\n","'use client';\n\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from 'react';\n\nimport {\n DEFAULT_OC_THEME,\n OC_SKIN_COOKIE,\n OC_THEMES,\n resolveTheme,\n type OcTheme,\n} from './themes';\n\n/**\n * OcThemeProvider — the skin (named-theme) axis.\n *\n * Orthogonal to `next-themes`, which owns light/dark via the `.dark` class.\n * This provider owns the `data-oc-theme` attribute on `<html>` and persists the\n * choice in the `oc_skin` cookie at `Domain=.ochk.io`, so a skin chosen on one\n * `*.ochk.io` site is honored on every other. It re-reads the cookie on focus /\n * visibility change so a fresh choice picks up when the user returns to a tab\n * (same cross-site pickup trick `OcAccountMenu` uses for sign-in).\n *\n * Mount once, inside (or beside) the `next-themes` `<ThemeProvider>`:\n *\n * <ThemeProvider attribute=\"class\" defaultTheme=\"system\" enableSystem>\n * <OcThemeProvider>\n * …app…\n * </OcThemeProvider>\n * </ThemeProvider>\n *\n * Pair with `getOcThemeInitScript()` in `_document` to avoid a flash before\n * hydration.\n */\n\nconst ONE_YEAR = 60 * 60 * 24 * 365;\nconst ATTR = 'data-oc-theme';\n\nfunction readSkinCookie(): string | null {\n if (typeof document === 'undefined') return null;\n const prefix = `${OC_SKIN_COOKIE}=`;\n for (const part of document.cookie.split(';')) {\n const trimmed = part.trim();\n if (trimmed.startsWith(prefix)) {\n const v = decodeURIComponent(trimmed.slice(prefix.length));\n return v.length > 0 ? v : null;\n }\n }\n return null;\n}\n\nfunction writeSkinCookie(value: string): void {\n if (typeof document === 'undefined') return;\n const isLocal =\n location.hostname === 'localhost' || location.hostname === '127.0.0.1';\n const parts = [\n `${OC_SKIN_COOKIE}=${encodeURIComponent(value)}`,\n 'Path=/',\n `Max-Age=${ONE_YEAR}`,\n 'SameSite=Lax',\n ];\n if (!isLocal) {\n parts.push('Domain=.ochk.io');\n parts.push('Secure');\n }\n document.cookie = parts.join('; ');\n}\n\nfunction applyAttr(skin: string): void {\n if (typeof document === 'undefined') return;\n document.documentElement.setAttribute(ATTR, skin);\n}\n\ninterface OcSkinContextValue {\n /** Current skin id (always resolved to a known theme). */\n skin: string;\n /** Set the skin: validates, applies the attribute, persists the cookie. */\n setSkin: (id: string) => void;\n /** The full registry, for rendering a picker. */\n themes: readonly OcTheme[];\n}\n\nconst OcSkinContext = createContext<OcSkinContextValue | null>(null);\n\nexport interface OcThemeProviderProps {\n children: ReactNode;\n /** Skin to assume before the cookie is read (defaults to `orangecheck`). */\n defaultSkin?: string;\n}\n\nexport function OcThemeProvider({ children, defaultSkin = DEFAULT_OC_THEME }: OcThemeProviderProps) {\n const [skin, setSkinState] = useState<string>(() => resolveTheme(defaultSkin));\n const hydratedRef = useRef(false);\n\n // Hydrate from the cookie once on mount and align the attribute.\n useEffect(() => {\n if (hydratedRef.current) return;\n hydratedRef.current = true;\n const fromCookie = resolveTheme(readSkinCookie() ?? defaultSkin);\n applyAttr(fromCookie);\n setSkinState(fromCookie);\n }, [defaultSkin]);\n\n // Cross-site pickup: re-read the cookie when the tab regains focus.\n useEffect(() => {\n function sync() {\n const fromCookie = resolveTheme(readSkinCookie() ?? skin);\n if (fromCookie !== skin) {\n applyAttr(fromCookie);\n setSkinState(fromCookie);\n }\n }\n window.addEventListener('focus', sync);\n document.addEventListener('visibilitychange', sync);\n return () => {\n window.removeEventListener('focus', sync);\n document.removeEventListener('visibilitychange', sync);\n };\n }, [skin]);\n\n const setSkin = useCallback((id: string) => {\n const next = resolveTheme(id);\n applyAttr(next);\n writeSkinCookie(next);\n setSkinState(next);\n }, []);\n\n const value = useMemo<OcSkinContextValue>(\n () => ({ skin, setSkin, themes: OC_THEMES }),\n [skin, setSkin]\n );\n\n return <OcSkinContext.Provider value={value}>{children}</OcSkinContext.Provider>;\n}\n\n/** Read the current skin + setter. Throws if used outside `OcThemeProvider`. */\nexport function useOcSkin(): OcSkinContextValue {\n const ctx = useContext(OcSkinContext);\n if (!ctx) {\n throw new Error('useOcSkin must be used within <OcThemeProvider>');\n }\n return ctx;\n}\n\n/**\n * Blocking init script for `_document` (Pages Router) `<Head>`. Applies the\n * cookie's skin to `<html>` before first paint so there is no flash of the\n * default skin. Inject with:\n *\n * <script dangerouslySetInnerHTML={{ __html: getOcThemeInitScript() }} />\n */\nexport function getOcThemeInitScript(defaultSkin: string = DEFAULT_OC_THEME): string {\n // Self-contained IIFE — no imports available at parse time.\n return `(function(){try{var m=document.cookie.match(/(?:^|; )${OC_SKIN_COOKIE}=([^;]*)/);var s=m?decodeURIComponent(m[1]):'${defaultSkin}';if(!s){s='${defaultSkin}';}document.documentElement.setAttribute('${ATTR}',s);}catch(e){document.documentElement.setAttribute('${ATTR}','${defaultSkin}');}})();`;\n}\n","'use client';\n\nimport { useEffect, useRef } from 'react';\nimport { useTheme } from 'next-themes';\n\n/**\n * OcThemeBridge — cross-subdomain persistence for the MODE axis (light/dark).\n *\n * `next-themes` defaults to origin-scoped `localStorage`, so a dark/light choice\n * on one `*.ochk.io` site won't carry to another. This layers an\n * `oc_theme` cookie at `Domain=.ochk.io` on top so the choice is visible\n * family-wide. (The SKIN axis has its own `oc_skin` bridge inside\n * `OcThemeProvider`; this is the mode counterpart, previously duplicated in\n * every consumer repo.)\n *\n * Mount once inside the next-themes `<ThemeProvider>`:\n *\n * <ThemeProvider attribute=\"class\" defaultTheme=\"system\" enableSystem>\n * <OcThemeBridge />\n * <OcThemeProvider>…</OcThemeProvider>\n * </ThemeProvider>\n *\n * The cookie is intentionally non-HttpOnly (next-themes JS reads/writes it) and\n * carries no auth value — a leak only reveals a theme preference.\n */\nconst COOKIE = 'oc_theme';\nconst ALLOWED = new Set(['light', 'dark', 'system']);\nconst ONE_YEAR = 60 * 60 * 24 * 365;\n\nfunction readCookie(name: string): string | null {\n if (typeof document === 'undefined') return null;\n const prefix = `${name}=`;\n for (const part of document.cookie.split(';')) {\n const trimmed = part.trim();\n if (trimmed.startsWith(prefix)) {\n const v = decodeURIComponent(trimmed.slice(prefix.length));\n return v.length > 0 ? v : null;\n }\n }\n return null;\n}\n\nfunction writeCookie(value: string): void {\n if (typeof document === 'undefined') return;\n if (!ALLOWED.has(value)) return;\n const isLocal = location.hostname === 'localhost' || location.hostname === '127.0.0.1';\n const parts = [\n `${COOKIE}=${encodeURIComponent(value)}`,\n 'Path=/',\n `Max-Age=${ONE_YEAR}`,\n 'SameSite=Lax',\n ];\n if (!isLocal) {\n parts.push('Domain=.ochk.io');\n parts.push('Secure');\n }\n document.cookie = parts.join('; ');\n}\n\nexport function OcThemeBridge() {\n const { theme, setTheme } = useTheme();\n const hydratedRef = useRef(false);\n\n useEffect(() => {\n if (hydratedRef.current) return;\n hydratedRef.current = true;\n const cookieValue = readCookie(COOKIE);\n if (cookieValue && ALLOWED.has(cookieValue) && cookieValue !== theme) {\n setTheme(cookieValue);\n }\n }, [theme, setTheme]);\n\n useEffect(() => {\n if (!hydratedRef.current) return;\n if (!theme) return;\n writeCookie(theme);\n }, [theme]);\n\n return null;\n}\n","'use client';\n\nimport { Check, Palette } from 'lucide-react';\nimport { useEffect, useRef, useState } from 'react';\n\nimport { cn } from './cn';\nimport { useOcSkin } from './provider';\n\n/**\n * OcThemePicker — the skin selector. Sits beside `ThemeToggle` in the header.\n * Self-contained dropdown (no Radix): outside-click + Escape to close. Mirrors\n * the ghost-icon affordance of `ThemeToggle`. Gate its mount behind\n * `NEXT_PUBLIC_OC_THEME_PICKER` during staged rollout.\n */\nexport interface OcThemePickerProps {\n className?: string;\n triggerClassName?: string;\n popoverClassName?: string;\n}\n\nexport function OcThemePicker({ className, triggerClassName, popoverClassName }: OcThemePickerProps) {\n const { skin, setSkin, themes } = useOcSkin();\n const [open, setOpen] = useState(false);\n const rootRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n if (!open) return;\n function onDown(e: MouseEvent) {\n if (rootRef.current && !rootRef.current.contains(e.target as Node)) setOpen(false);\n }\n function onKey(e: KeyboardEvent) {\n if (e.key === 'Escape') setOpen(false);\n }\n document.addEventListener('mousedown', onDown);\n document.addEventListener('keydown', onKey);\n return () => {\n document.removeEventListener('mousedown', onDown);\n document.removeEventListener('keydown', onKey);\n };\n }, [open]);\n\n return (\n <div ref={rootRef} className={cn('relative', className)}>\n <button\n type=\"button\"\n aria-label=\"choose theme\"\n aria-haspopup=\"menu\"\n aria-expanded={open}\n title=\"choose theme\"\n onClick={() => setOpen((v) => !v)}\n className={cn(\n 'text-muted-foreground hover:text-foreground inline-flex size-9 items-center justify-center rounded-md transition-colors outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50',\n triggerClassName\n )}\n >\n <Palette className=\"size-4\" aria-hidden />\n </button>\n\n {open && (\n <div\n role=\"menu\"\n className={cn(\n 'bg-popover text-popover-foreground absolute right-0 z-50 mt-2 w-64 overflow-hidden rounded-md border shadow-md',\n popoverClassName\n )}\n >\n <div className=\"label-mono text-muted-foreground px-3 pt-3 pb-1\">theme</div>\n <ul className=\"pb-1\">\n {themes.map((t) => {\n const active = t.id === skin;\n return (\n <li key={t.id}>\n <button\n type=\"button\"\n role=\"menuitemradio\"\n aria-checked={active}\n onClick={() => {\n setSkin(t.id);\n setOpen(false);\n }}\n className={cn(\n 'hover:bg-accent hover:text-accent-foreground flex w-full items-start gap-2 px-3 py-2 text-left transition-colors',\n active && 'text-foreground'\n )}\n >\n <span className=\"mt-0.5 size-4 shrink-0\">\n {active && <Check className=\"size-4 text-primary\" aria-hidden />}\n </span>\n <span className=\"min-w-0\">\n <span className=\"block text-sm font-medium\">{t.label}</span>\n <span className=\"text-muted-foreground block text-xs leading-snug\">\n {t.description}\n </span>\n </span>\n </button>\n </li>\n );\n })}\n </ul>\n </div>\n )}\n </div>\n );\n}\n"]}
1
+ {"version":3,"sources":["../src/tokens/cn.ts","../src/tokens/themes.ts","../src/tokens/provider.tsx","../src/tokens/theme-bridge.tsx","../src/tokens/theme-picker.tsx","../src/tokens/appearance-menu.tsx"],"names":["ONE_YEAR","useRef","useEffect","useState","jsx","useTheme","jsxs","Palette","Check"],"mappings":";;;;;;;;AAUO,SAAS,MAAM,MAAA,EAA8B;AAChD,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC/B;;;ACYO,IAAM,SAAA,GAAgC;AAAA,EACzC;AAAA,IACI,EAAA,EAAI,aAAA;AAAA,IACJ,KAAA,EAAO,aAAA;AAAA,IACP,WAAA,EAAa;AAAA,GACjB;AAAA,EACA;AAAA,IACI,EAAA,EAAI,UAAA;AAAA,IACJ,KAAA,EAAO,UAAA;AAAA,IACP,WAAA,EAAa;AAAA,GACjB;AAAA,EACA;AAAA,IACI,EAAA,EAAI,WAAA;AAAA,IACJ,KAAA,EAAO,WAAA;AAAA,IACP,WAAA,EAAa;AAAA,GACjB;AAAA,EACA;AAAA,IACI,EAAA,EAAI,MAAA;AAAA,IACJ,KAAA,EAAO,MAAA;AAAA,IACP,WAAA,EAAa;AAAA;AAErB;AAEO,IAAM,gBAAA,GAAmB;AAIzB,SAAS,aAAa,EAAA,EAAgD;AACzE,EAAA,OAAO,CAAC,CAAC,EAAA,IAAM,SAAA,CAAU,KAAK,CAAC,CAAA,KAAM,CAAA,CAAE,EAAA,KAAO,EAAE,CAAA;AACpD;AAEO,SAAS,aAAa,EAAA,EAAuC;AAChE,EAAA,OAAO,YAAA,CAAa,EAAE,CAAA,GAAI,EAAA,GAAK,gBAAA;AACnC;AAGO,IAAM,cAAA,GAAiB;ACjB9B,IAAM,QAAA,GAAW,EAAA,GAAK,EAAA,GAAK,EAAA,GAAK,GAAA;AAChC,IAAM,IAAA,GAAO,eAAA;AAEb,SAAS,cAAA,GAAgC;AACrC,EAAA,IAAI,OAAO,QAAA,KAAa,WAAA,EAAa,OAAO,IAAA;AAC5C,EAAA,MAAM,MAAA,GAAS,GAAG,cAAc,CAAA,CAAA,CAAA;AAChC,EAAA,KAAA,MAAW,IAAA,IAAQ,QAAA,CAAS,MAAA,CAAO,KAAA,CAAM,GAAG,CAAA,EAAG;AAC3C,IAAA,MAAM,OAAA,GAAU,KAAK,IAAA,EAAK;AAC1B,IAAA,IAAI,OAAA,CAAQ,UAAA,CAAW,MAAM,CAAA,EAAG;AAC5B,MAAA,MAAM,IAAI,kBAAA,CAAmB,OAAA,CAAQ,KAAA,CAAM,MAAA,CAAO,MAAM,CAAC,CAAA;AACzD,MAAA,OAAO,CAAA,CAAE,MAAA,GAAS,CAAA,GAAI,CAAA,GAAI,IAAA;AAAA,IAC9B;AAAA,EACJ;AACA,EAAA,OAAO,IAAA;AACX;AAEA,SAAS,gBAAgB,KAAA,EAAqB;AAC1C,EAAA,IAAI,OAAO,aAAa,WAAA,EAAa;AACrC,EAAA,MAAM,OAAA,GACF,QAAA,CAAS,QAAA,KAAa,WAAA,IAAe,SAAS,QAAA,KAAa,WAAA;AAC/D,EAAA,MAAM,KAAA,GAAQ;AAAA,IACV,CAAA,EAAG,cAAc,CAAA,CAAA,EAAI,kBAAA,CAAmB,KAAK,CAAC,CAAA,CAAA;AAAA,IAC9C,QAAA;AAAA,IACA,WAAW,QAAQ,CAAA,CAAA;AAAA,IACnB;AAAA,GACJ;AACA,EAAA,IAAI,CAAC,OAAA,EAAS;AACV,IAAA,KAAA,CAAM,KAAK,iBAAiB,CAAA;AAC5B,IAAA,KAAA,CAAM,KAAK,QAAQ,CAAA;AAAA,EACvB;AACA,EAAA,QAAA,CAAS,MAAA,GAAS,KAAA,CAAM,IAAA,CAAK,IAAI,CAAA;AACrC;AAEA,SAAS,UAAU,IAAA,EAAoB;AACnC,EAAA,IAAI,OAAO,aAAa,WAAA,EAAa;AACrC,EAAA,QAAA,CAAS,eAAA,CAAgB,YAAA,CAAa,IAAA,EAAM,IAAI,CAAA;AACpD;AAWA,IAAM,aAAA,GAAgB,cAAyC,IAAI,CAAA;AAQ5D,SAAS,eAAA,CAAgB,EAAE,QAAA,EAAU,WAAA,GAAc,kBAAiB,EAAyB;AAChG,EAAA,MAAM,CAAC,MAAM,YAAY,CAAA,GAAI,SAAiB,MAAM,YAAA,CAAa,WAAW,CAAC,CAAA;AAC7E,EAAA,MAAM,WAAA,GAAc,OAAO,KAAK,CAAA;AAGhC,EAAA,SAAA,CAAU,MAAM;AACZ,IAAA,IAAI,YAAY,OAAA,EAAS;AACzB,IAAA,WAAA,CAAY,OAAA,GAAU,IAAA;AACtB,IAAA,MAAM,UAAA,GAAa,YAAA,CAAa,cAAA,EAAe,IAAK,WAAW,CAAA;AAC/D,IAAA,SAAA,CAAU,UAAU,CAAA;AACpB,IAAA,YAAA,CAAa,UAAU,CAAA;AAAA,EAC3B,CAAA,EAAG,CAAC,WAAW,CAAC,CAAA;AAGhB,EAAA,SAAA,CAAU,MAAM;AACZ,IAAA,SAAS,IAAA,GAAO;AACZ,MAAA,MAAM,UAAA,GAAa,YAAA,CAAa,cAAA,EAAe,IAAK,IAAI,CAAA;AACxD,MAAA,IAAI,eAAe,IAAA,EAAM;AACrB,QAAA,SAAA,CAAU,UAAU,CAAA;AACpB,QAAA,YAAA,CAAa,UAAU,CAAA;AAAA,MAC3B;AAAA,IACJ;AACA,IAAA,MAAA,CAAO,gBAAA,CAAiB,SAAS,IAAI,CAAA;AACrC,IAAA,QAAA,CAAS,gBAAA,CAAiB,oBAAoB,IAAI,CAAA;AAClD,IAAA,OAAO,MAAM;AACT,MAAA,MAAA,CAAO,mBAAA,CAAoB,SAAS,IAAI,CAAA;AACxC,MAAA,QAAA,CAAS,mBAAA,CAAoB,oBAAoB,IAAI,CAAA;AAAA,IACzD,CAAA;AAAA,EACJ,CAAA,EAAG,CAAC,IAAI,CAAC,CAAA;AAET,EAAA,MAAM,OAAA,GAAU,WAAA,CAAY,CAAC,EAAA,KAAe;AACxC,IAAA,MAAM,IAAA,GAAO,aAAa,EAAE,CAAA;AAC5B,IAAA,SAAA,CAAU,IAAI,CAAA;AACd,IAAA,eAAA,CAAgB,IAAI,CAAA;AACpB,IAAA,YAAA,CAAa,IAAI,CAAA;AAAA,EACrB,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,KAAA,GAAQ,OAAA;AAAA,IACV,OAAO,EAAE,IAAA,EAAM,OAAA,EAAS,QAAQ,SAAA,EAAU,CAAA;AAAA,IAC1C,CAAC,MAAM,OAAO;AAAA,GAClB;AAEA,EAAA,uBAAO,GAAA,CAAC,aAAA,CAAc,QAAA,EAAd,EAAuB,OAAe,QAAA,EAAS,CAAA;AAC3D;AAGO,SAAS,SAAA,GAAgC;AAC5C,EAAA,MAAM,GAAA,GAAM,WAAW,aAAa,CAAA;AACpC,EAAA,IAAI,CAAC,GAAA,EAAK;AACN,IAAA,MAAM,IAAI,MAAM,iDAAiD,CAAA;AAAA,EACrE;AACA,EAAA,OAAO,GAAA;AACX;AASO,SAAS,oBAAA,CAAqB,cAAsB,gBAAA,EAA0B;AAEjF,EAAA,OAAO,CAAA,qDAAA,EAAwD,cAAc,CAAA,6CAAA,EAAgD,WAAW,CAAA,YAAA,EAAe,WAAW,CAAA,0CAAA,EAA6C,IAAI,CAAA,sDAAA,EAAyD,IAAI,CAAA,GAAA,EAAM,WAAW,CAAA,SAAA,CAAA;AACrS;ACzIA,IAAM,MAAA,GAAS,UAAA;AACf,IAAM,0BAAU,IAAI,GAAA,CAAI,CAAC,OAAA,EAAS,MAAA,EAAQ,QAAQ,CAAC,CAAA;AACnD,IAAMA,SAAAA,GAAW,EAAA,GAAK,EAAA,GAAK,EAAA,GAAK,GAAA;AAEhC,SAAS,WAAW,IAAA,EAA6B;AAC7C,EAAA,IAAI,OAAO,QAAA,KAAa,WAAA,EAAa,OAAO,IAAA;AAC5C,EAAA,MAAM,MAAA,GAAS,GAAG,IAAI,CAAA,CAAA,CAAA;AACtB,EAAA,KAAA,MAAW,IAAA,IAAQ,QAAA,CAAS,MAAA,CAAO,KAAA,CAAM,GAAG,CAAA,EAAG;AAC3C,IAAA,MAAM,OAAA,GAAU,KAAK,IAAA,EAAK;AAC1B,IAAA,IAAI,OAAA,CAAQ,UAAA,CAAW,MAAM,CAAA,EAAG;AAC5B,MAAA,MAAM,IAAI,kBAAA,CAAmB,OAAA,CAAQ,KAAA,CAAM,MAAA,CAAO,MAAM,CAAC,CAAA;AACzD,MAAA,OAAO,CAAA,CAAE,MAAA,GAAS,CAAA,GAAI,CAAA,GAAI,IAAA;AAAA,IAC9B;AAAA,EACJ;AACA,EAAA,OAAO,IAAA;AACX;AAEA,SAAS,YAAY,KAAA,EAAqB;AACtC,EAAA,IAAI,OAAO,aAAa,WAAA,EAAa;AACrC,EAAA,IAAI,CAAC,OAAA,CAAQ,GAAA,CAAI,KAAK,CAAA,EAAG;AACzB,EAAA,MAAM,OAAA,GAAU,QAAA,CAAS,QAAA,KAAa,WAAA,IAAe,SAAS,QAAA,KAAa,WAAA;AAC3E,EAAA,MAAM,KAAA,GAAQ;AAAA,IACV,CAAA,EAAG,MAAM,CAAA,CAAA,EAAI,kBAAA,CAAmB,KAAK,CAAC,CAAA,CAAA;AAAA,IACtC,QAAA;AAAA,IACA,WAAWA,SAAQ,CAAA,CAAA;AAAA,IACnB;AAAA,GACJ;AACA,EAAA,IAAI,CAAC,OAAA,EAAS;AACV,IAAA,KAAA,CAAM,KAAK,iBAAiB,CAAA;AAC5B,IAAA,KAAA,CAAM,KAAK,QAAQ,CAAA;AAAA,EACvB;AACA,EAAA,QAAA,CAAS,MAAA,GAAS,KAAA,CAAM,IAAA,CAAK,IAAI,CAAA;AACrC;AAEO,SAAS,aAAA,GAAgB;AAC5B,EAAA,MAAM,EAAE,KAAA,EAAO,QAAA,EAAS,GAAI,QAAA,EAAS;AACrC,EAAA,MAAM,WAAA,GAAcC,OAAO,KAAK,CAAA;AAEhC,EAAAC,UAAU,MAAM;AACZ,IAAA,IAAI,YAAY,OAAA,EAAS;AACzB,IAAA,WAAA,CAAY,OAAA,GAAU,IAAA;AACtB,IAAA,MAAM,WAAA,GAAc,WAAW,MAAM,CAAA;AACrC,IAAA,IAAI,eAAe,OAAA,CAAQ,GAAA,CAAI,WAAW,CAAA,IAAK,gBAAgB,KAAA,EAAO;AAClE,MAAA,QAAA,CAAS,WAAW,CAAA;AAAA,IACxB;AAAA,EACJ,CAAA,EAAG,CAAC,KAAA,EAAO,QAAQ,CAAC,CAAA;AAEpB,EAAAA,UAAU,MAAM;AACZ,IAAA,IAAI,CAAC,YAAY,OAAA,EAAS;AAC1B,IAAA,IAAI,CAAC,KAAA,EAAO;AACZ,IAAA,WAAA,CAAY,KAAK,CAAA;AAAA,EACrB,CAAA,EAAG,CAAC,KAAK,CAAC,CAAA;AAEV,EAAA,OAAO,IAAA;AACX;AC3DO,SAAS,aAAA,CAAc,EAAE,SAAA,EAAW,gBAAA,EAAkB,kBAAiB,EAAuB;AACjG,EAAA,MAAM,EAAE,IAAA,EAAM,OAAA,EAAS,MAAA,KAAW,SAAA,EAAU;AAC5C,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAIC,SAAS,KAAK,CAAA;AACtC,EAAA,MAAM,OAAA,GAAUF,OAAuB,IAAI,CAAA;AAE3C,EAAAC,UAAU,MAAM;AACZ,IAAA,IAAI,CAAC,IAAA,EAAM;AACX,IAAA,SAAS,OAAO,CAAA,EAAe;AAC3B,MAAA,IAAI,OAAA,CAAQ,OAAA,IAAW,CAAC,OAAA,CAAQ,OAAA,CAAQ,SAAS,CAAA,CAAE,MAAc,CAAA,EAAG,OAAA,CAAQ,KAAK,CAAA;AAAA,IACrF;AACA,IAAA,SAAS,MAAM,CAAA,EAAkB;AAC7B,MAAA,IAAI,CAAA,CAAE,GAAA,KAAQ,QAAA,EAAU,OAAA,CAAQ,KAAK,CAAA;AAAA,IACzC;AACA,IAAA,QAAA,CAAS,gBAAA,CAAiB,aAAa,MAAM,CAAA;AAC7C,IAAA,QAAA,CAAS,gBAAA,CAAiB,WAAW,KAAK,CAAA;AAC1C,IAAA,OAAO,MAAM;AACT,MAAA,QAAA,CAAS,mBAAA,CAAoB,aAAa,MAAM,CAAA;AAChD,MAAA,QAAA,CAAS,mBAAA,CAAoB,WAAW,KAAK,CAAA;AAAA,IACjD,CAAA;AAAA,EACJ,CAAA,EAAG,CAAC,IAAI,CAAC,CAAA;AAET,EAAA,uBACI,IAAA,CAAC,SAAI,GAAA,EAAK,OAAA,EAAS,WAAW,EAAA,CAAG,UAAA,EAAY,SAAS,CAAA,EAClD,QAAA,EAAA;AAAA,oBAAAE,GAAAA;AAAA,MAAC,QAAA;AAAA,MAAA;AAAA,QACG,IAAA,EAAK,QAAA;AAAA,QACL,YAAA,EAAW,cAAA;AAAA,QACX,eAAA,EAAc,MAAA;AAAA,QACd,eAAA,EAAe,IAAA;AAAA,QACf,KAAA,EAAM,cAAA;AAAA,QACN,SAAS,MAAM,OAAA,CAAQ,CAAC,CAAA,KAAM,CAAC,CAAC,CAAA;AAAA,QAChC,SAAA,EAAW,EAAA;AAAA,UACP,0LAAA;AAAA,UACA;AAAA,SACJ;AAAA,QAEA,0BAAAA,GAAAA,CAAC,OAAA,EAAA,EAAQ,SAAA,EAAU,QAAA,EAAS,eAAW,IAAA,EAAC;AAAA;AAAA,KAC5C;AAAA,IAEC,IAAA,oBACG,IAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACG,IAAA,EAAK,MAAA;AAAA,QACL,SAAA,EAAW,EAAA;AAAA,UACP,gHAAA;AAAA,UACA;AAAA,SACJ;AAAA,QAEA,QAAA,EAAA;AAAA,0BAAAA,GAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,iDAAA,EAAkD,QAAA,EAAA,OAAA,EAAK,CAAA;AAAA,0BACtEA,IAAC,IAAA,EAAA,EAAG,SAAA,EAAU,QACT,QAAA,EAAA,MAAA,CAAO,GAAA,CAAI,CAAC,CAAA,KAAM;AACf,YAAA,MAAM,MAAA,GAAS,EAAE,EAAA,KAAO,IAAA;AACxB,YAAA,uBACIA,IAAC,IAAA,EAAA,EACG,QAAA,kBAAA,IAAA;AAAA,cAAC,QAAA;AAAA,cAAA;AAAA,gBACG,IAAA,EAAK,QAAA;AAAA,gBACL,IAAA,EAAK,eAAA;AAAA,gBACL,cAAA,EAAc,MAAA;AAAA,gBACd,SAAS,MAAM;AACX,kBAAA,OAAA,CAAQ,EAAE,EAAE,CAAA;AACZ,kBAAA,OAAA,CAAQ,KAAK,CAAA;AAAA,gBACjB,CAAA;AAAA,gBACA,SAAA,EAAW,EAAA;AAAA,kBACP,kHAAA;AAAA,kBACA,MAAA,IAAU;AAAA,iBACd;AAAA,gBAEA,QAAA,EAAA;AAAA,kCAAAA,GAAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,wBAAA,EACX,QAAA,EAAA,MAAA,oBAAUA,GAAAA,CAAC,KAAA,EAAA,EAAM,SAAA,EAAU,qBAAA,EAAsB,aAAA,EAAW,IAAA,EAAC,CAAA,EAClE,CAAA;AAAA,kCACA,IAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,SAAA,EACZ,QAAA,EAAA;AAAA,oCAAAA,GAAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,2BAAA,EAA6B,YAAE,KAAA,EAAM,CAAA;AAAA,oCACrDA,GAAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,kDAAA,EACX,YAAE,WAAA,EACP;AAAA,mBAAA,EACJ;AAAA;AAAA;AAAA,aACJ,EAAA,EAvBK,EAAE,EAwBX,CAAA;AAAA,UAER,CAAC,CAAA,EACL;AAAA;AAAA;AAAA;AACJ,GAAA,EAER,CAAA;AAER;AC3EA,IAAM,KAAA,GAAQ;AAAA,EACV,EAAE,EAAA,EAAI,OAAA,EAAS,KAAA,EAAO,OAAA,EAAS,MAAM,GAAA,EAAI;AAAA,EACzC,EAAE,EAAA,EAAI,MAAA,EAAQ,KAAA,EAAO,MAAA,EAAQ,MAAM,IAAA,EAAK;AAAA,EACxC,EAAE,EAAA,EAAI,QAAA,EAAU,KAAA,EAAO,QAAA,EAAU,MAAM,OAAA;AAC3C,CAAA;AAEO,SAAS,gBAAA,CAAiB;AAAA,EAC7B,SAAA;AAAA,EACA,gBAAA;AAAA,EACA;AACJ,CAAA,EAA0B;AACtB,EAAA,MAAM,EAAE,IAAA,EAAM,OAAA,EAAS,MAAA,KAAW,SAAA,EAAU;AAC5C,EAAA,MAAM,EAAE,KAAA,EAAO,QAAA,EAAS,GAAIC,QAAAA,EAAS;AACrC,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAIF,SAAS,KAAK,CAAA;AACtC,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAIA,SAAS,KAAK,CAAA;AAC5C,EAAA,MAAM,OAAA,GAAUF,OAAuB,IAAI,CAAA;AAE3C,EAAAC,UAAU,MAAM,UAAA,CAAW,IAAI,CAAA,EAAG,EAAE,CAAA;AAEpC,EAAAA,UAAU,MAAM;AACZ,IAAA,IAAI,CAAC,IAAA,EAAM;AACX,IAAA,SAAS,OAAO,CAAA,EAAe;AAC3B,MAAA,IAAI,OAAA,CAAQ,OAAA,IAAW,CAAC,OAAA,CAAQ,OAAA,CAAQ,SAAS,CAAA,CAAE,MAAc,CAAA,EAAG,OAAA,CAAQ,KAAK,CAAA;AAAA,IACrF;AACA,IAAA,SAAS,MAAM,CAAA,EAAkB;AAC7B,MAAA,IAAI,CAAA,CAAE,GAAA,KAAQ,QAAA,EAAU,OAAA,CAAQ,KAAK,CAAA;AAAA,IACzC;AACA,IAAA,QAAA,CAAS,gBAAA,CAAiB,aAAa,MAAM,CAAA;AAC7C,IAAA,QAAA,CAAS,gBAAA,CAAiB,WAAW,KAAK,CAAA;AAC1C,IAAA,OAAO,MAAM;AACT,MAAA,QAAA,CAAS,mBAAA,CAAoB,aAAa,MAAM,CAAA;AAChD,MAAA,QAAA,CAAS,mBAAA,CAAoB,WAAW,KAAK,CAAA;AAAA,IACjD,CAAA;AAAA,EACJ,CAAA,EAAG,CAAC,IAAI,CAAC,CAAA;AAGT,EAAA,MAAM,UAAA,GAAa,OAAA,GAAW,KAAA,IAAS,QAAA,GAAY,IAAA;AAEnD,EAAA,uBACII,KAAC,KAAA,EAAA,EAAI,GAAA,EAAK,SAAS,SAAA,EAAW,EAAA,CAAG,UAAA,EAAY,SAAS,CAAA,EAClD,QAAA,EAAA;AAAA,oBAAAF,GAAAA;AAAA,MAAC,QAAA;AAAA,MAAA;AAAA,QACG,IAAA,EAAK,QAAA;AAAA,QACL,YAAA,EAAW,qBAAA;AAAA,QACX,eAAA,EAAc,MAAA;AAAA,QACd,eAAA,EAAe,IAAA;AAAA,QACf,KAAA,EAAM,8BAAA;AAAA,QACN,SAAS,MAAM,OAAA,CAAQ,CAAC,CAAA,KAAM,CAAC,CAAC,CAAA;AAAA,QAChC,SAAA,EAAW,EAAA;AAAA,UACP,0LAAA;AAAA,UACA;AAAA,SACJ;AAAA,QAEA,0BAAAA,GAAAA,CAACG,OAAAA,EAAA,EAAQ,SAAA,EAAU,QAAA,EAAS,eAAW,IAAA,EAAC;AAAA;AAAA,KAC5C;AAAA,IAEC,wBACGD,IAAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACG,IAAA,EAAK,MAAA;AAAA,QACL,SAAA,EAAW,EAAA;AAAA,UACP,gHAAA;AAAA,UACA;AAAA,SACJ;AAAA,QAGA,QAAA,EAAA;AAAA,0BAAAF,GAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,iDAAA,EAAkD,QAAA,EAAA,MAAA,EAAI,CAAA;AAAA,0BACrEA,GAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,kCAAA,EACV,QAAA,EAAA,KAAA,CAAM,GAAA,CAAI,CAAC,EAAE,EAAA,EAAI,KAAA,EAAO,IAAA,EAAK,KAAM;AAChC,YAAA,MAAM,SAAS,UAAA,KAAe,EAAA;AAC9B,YAAA,uBACIE,IAAAA;AAAA,cAAC,QAAA;AAAA,cAAA;AAAA,gBAEG,IAAA,EAAK,QAAA;AAAA,gBACL,IAAA,EAAK,eAAA;AAAA,gBACL,cAAA,EAAc,MAAA;AAAA,gBACd,OAAA,EAAS,MAAM,QAAA,CAAS,EAAE,CAAA;AAAA,gBAC1B,SAAA,EAAW,EAAA;AAAA,kBACP,uFAAA;AAAA,kBACA,SACM,0CAAA,GACA;AAAA,iBACV;AAAA,gBAEA,QAAA,EAAA;AAAA,kCAAAF,GAAAA,CAAC,IAAA,EAAA,EAAK,SAAA,EAAU,QAAA,EAAS,eAAW,IAAA,EAAC,CAAA;AAAA,kBACpC;AAAA;AAAA,eAAA;AAAA,cAbI;AAAA,aAcT;AAAA,UAER,CAAC,CAAA,EACL,CAAA;AAAA,0BAGAA,GAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,4DAA2D,QAAA,EAAA,OAAA,EAE1E,CAAA;AAAA,0BACAA,IAAC,IAAA,EAAA,EAAG,SAAA,EAAU,QACT,QAAA,EAAA,MAAA,CAAO,GAAA,CAAI,CAAC,CAAA,KAAM;AACf,YAAA,MAAM,MAAA,GAAS,EAAE,EAAA,KAAO,IAAA;AACxB,YAAA,uBACIA,GAAAA,CAAC,IAAA,EAAA,EACG,QAAA,kBAAAE,IAAAA;AAAA,cAAC,QAAA;AAAA,cAAA;AAAA,gBACG,IAAA,EAAK,QAAA;AAAA,gBACL,IAAA,EAAK,eAAA;AAAA,gBACL,cAAA,EAAc,MAAA;AAAA,gBACd,OAAA,EAAS,MAAM,OAAA,CAAQ,CAAA,CAAE,EAAE,CAAA;AAAA,gBAC3B,SAAA,EAAW,EAAA;AAAA,kBACP,kHAAA;AAAA,kBACA,MAAA,IAAU;AAAA,iBACd;AAAA,gBAEA,QAAA,EAAA;AAAA,kCAAAF,GAAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,wBAAA,EACX,QAAA,EAAA,MAAA,oBACGA,GAAAA,CAACI,KAAAA,EAAA,EAAM,SAAA,EAAU,qBAAA,EAAsB,aAAA,EAAW,MAAC,CAAA,EAE3D,CAAA;AAAA,kCACAF,IAAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,SAAA,EACZ,QAAA,EAAA;AAAA,oCAAAF,GAAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,2BAAA,EAA6B,YAAE,KAAA,EAAM,CAAA;AAAA,oCACrDA,GAAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,kDAAA,EACX,YAAE,WAAA,EACP;AAAA,mBAAA,EACJ;AAAA;AAAA;AAAA,aACJ,EAAA,EAtBK,EAAE,EAuBX,CAAA;AAAA,UAER,CAAC,CAAA,EACL;AAAA;AAAA;AAAA;AACJ,GAAA,EAER,CAAA;AAER","file":"tokens.mjs","sourcesContent":["import type { ClassValue } from 'clsx';\n\nimport { clsx } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\n/**\n * Canonical class-name merge for the OrangeCheck family.\n * `clsx` for conditional composition, `tailwind-merge` to resolve\n * conflicting Tailwind utilities (last-wins by property).\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","/**\n * The OrangeCheck theme (skin) registry.\n *\n * A *skin* is the named-theme axis, orthogonal to the light/dark *mode* axis\n * (which stays owned by `next-themes` via the `.dark` class). A skin is applied\n * as `data-oc-theme=\"<id>\"` on `<html>` and persisted in the `oc_skin` cookie at\n * `Domain=.ochk.io` so a choice on one family site carries to every other.\n *\n * Adding a skin = add a `themes/<id>.css` token block in this package and a\n * row here. Every site picks it up on the next `@orangecheck/design` bump; no\n * per-site edits.\n */\nexport interface OcTheme {\n /** Stable id — also the `data-oc-theme` attribute value and the CSS file stem. */\n readonly id: string;\n /** Human label for the picker (lowercase, house voice). */\n readonly label: string;\n /** One-line description shown in the picker / Storybook. */\n readonly description: string;\n}\n\n// Every skin is rooted in the Bitcoin ethos — sharp, flat, cypherpunk — and\n// carries a meaning, not just a colour: the chain, the node, the network, sound\n// money. No SaaS softness.\nexport const OC_THEMES: readonly OcTheme[] = [\n {\n id: 'orangecheck',\n label: 'orangecheck',\n description: 'the chain — Bitcoin orange, sharp 0.25rem corners, Inter + JetBrains Mono',\n },\n {\n id: 'phosphor',\n label: 'phosphor',\n description: 'the node — CRT terminal green, hard 0.125rem, all-mono · sovereignty & verify',\n },\n {\n id: 'lightning',\n label: 'lightning',\n description: 'the network — electric violet (L2), hardest 0rem edges, sans · instant',\n },\n {\n id: 'gold',\n label: 'gold',\n description: 'sound money — digital gold, hard 0.125rem milled edge, mono ledger · 21M',\n },\n] as const;\n\nexport const DEFAULT_OC_THEME = 'orangecheck';\n\nexport type OcThemeId = (typeof OC_THEMES)[number]['id'] | (string & {});\n\nexport function isKnownTheme(id: string | null | undefined): id is OcThemeId {\n return !!id && OC_THEMES.some((t) => t.id === id);\n}\n\nexport function resolveTheme(id: string | null | undefined): string {\n return isKnownTheme(id) ? id : DEFAULT_OC_THEME;\n}\n\n/** Cookie name for the cross-subdomain skin choice. */\nexport const OC_SKIN_COOKIE = 'oc_skin';\n","'use client';\n\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from 'react';\n\nimport {\n DEFAULT_OC_THEME,\n OC_SKIN_COOKIE,\n OC_THEMES,\n resolveTheme,\n type OcTheme,\n} from './themes';\n\n/**\n * OcThemeProvider — the skin (named-theme) axis.\n *\n * Orthogonal to `next-themes`, which owns light/dark via the `.dark` class.\n * This provider owns the `data-oc-theme` attribute on `<html>` and persists the\n * choice in the `oc_skin` cookie at `Domain=.ochk.io`, so a skin chosen on one\n * `*.ochk.io` site is honored on every other. It re-reads the cookie on focus /\n * visibility change so a fresh choice picks up when the user returns to a tab\n * (same cross-site pickup trick `OcAccountMenu` uses for sign-in).\n *\n * Mount once, inside (or beside) the `next-themes` `<ThemeProvider>`:\n *\n * <ThemeProvider attribute=\"class\" defaultTheme=\"system\" enableSystem>\n * <OcThemeProvider>\n * …app…\n * </OcThemeProvider>\n * </ThemeProvider>\n *\n * Pair with `getOcThemeInitScript()` in `_document` to avoid a flash before\n * hydration.\n */\n\nconst ONE_YEAR = 60 * 60 * 24 * 365;\nconst ATTR = 'data-oc-theme';\n\nfunction readSkinCookie(): string | null {\n if (typeof document === 'undefined') return null;\n const prefix = `${OC_SKIN_COOKIE}=`;\n for (const part of document.cookie.split(';')) {\n const trimmed = part.trim();\n if (trimmed.startsWith(prefix)) {\n const v = decodeURIComponent(trimmed.slice(prefix.length));\n return v.length > 0 ? v : null;\n }\n }\n return null;\n}\n\nfunction writeSkinCookie(value: string): void {\n if (typeof document === 'undefined') return;\n const isLocal =\n location.hostname === 'localhost' || location.hostname === '127.0.0.1';\n const parts = [\n `${OC_SKIN_COOKIE}=${encodeURIComponent(value)}`,\n 'Path=/',\n `Max-Age=${ONE_YEAR}`,\n 'SameSite=Lax',\n ];\n if (!isLocal) {\n parts.push('Domain=.ochk.io');\n parts.push('Secure');\n }\n document.cookie = parts.join('; ');\n}\n\nfunction applyAttr(skin: string): void {\n if (typeof document === 'undefined') return;\n document.documentElement.setAttribute(ATTR, skin);\n}\n\ninterface OcSkinContextValue {\n /** Current skin id (always resolved to a known theme). */\n skin: string;\n /** Set the skin: validates, applies the attribute, persists the cookie. */\n setSkin: (id: string) => void;\n /** The full registry, for rendering a picker. */\n themes: readonly OcTheme[];\n}\n\nconst OcSkinContext = createContext<OcSkinContextValue | null>(null);\n\nexport interface OcThemeProviderProps {\n children: ReactNode;\n /** Skin to assume before the cookie is read (defaults to `orangecheck`). */\n defaultSkin?: string;\n}\n\nexport function OcThemeProvider({ children, defaultSkin = DEFAULT_OC_THEME }: OcThemeProviderProps) {\n const [skin, setSkinState] = useState<string>(() => resolveTheme(defaultSkin));\n const hydratedRef = useRef(false);\n\n // Hydrate from the cookie once on mount and align the attribute.\n useEffect(() => {\n if (hydratedRef.current) return;\n hydratedRef.current = true;\n const fromCookie = resolveTheme(readSkinCookie() ?? defaultSkin);\n applyAttr(fromCookie);\n setSkinState(fromCookie);\n }, [defaultSkin]);\n\n // Cross-site pickup: re-read the cookie when the tab regains focus.\n useEffect(() => {\n function sync() {\n const fromCookie = resolveTheme(readSkinCookie() ?? skin);\n if (fromCookie !== skin) {\n applyAttr(fromCookie);\n setSkinState(fromCookie);\n }\n }\n window.addEventListener('focus', sync);\n document.addEventListener('visibilitychange', sync);\n return () => {\n window.removeEventListener('focus', sync);\n document.removeEventListener('visibilitychange', sync);\n };\n }, [skin]);\n\n const setSkin = useCallback((id: string) => {\n const next = resolveTheme(id);\n applyAttr(next);\n writeSkinCookie(next);\n setSkinState(next);\n }, []);\n\n const value = useMemo<OcSkinContextValue>(\n () => ({ skin, setSkin, themes: OC_THEMES }),\n [skin, setSkin]\n );\n\n return <OcSkinContext.Provider value={value}>{children}</OcSkinContext.Provider>;\n}\n\n/** Read the current skin + setter. Throws if used outside `OcThemeProvider`. */\nexport function useOcSkin(): OcSkinContextValue {\n const ctx = useContext(OcSkinContext);\n if (!ctx) {\n throw new Error('useOcSkin must be used within <OcThemeProvider>');\n }\n return ctx;\n}\n\n/**\n * Blocking init script for `_document` (Pages Router) `<Head>`. Applies the\n * cookie's skin to `<html>` before first paint so there is no flash of the\n * default skin. Inject with:\n *\n * <script dangerouslySetInnerHTML={{ __html: getOcThemeInitScript() }} />\n */\nexport function getOcThemeInitScript(defaultSkin: string = DEFAULT_OC_THEME): string {\n // Self-contained IIFE — no imports available at parse time.\n return `(function(){try{var m=document.cookie.match(/(?:^|; )${OC_SKIN_COOKIE}=([^;]*)/);var s=m?decodeURIComponent(m[1]):'${defaultSkin}';if(!s){s='${defaultSkin}';}document.documentElement.setAttribute('${ATTR}',s);}catch(e){document.documentElement.setAttribute('${ATTR}','${defaultSkin}');}})();`;\n}\n","'use client';\n\nimport { useEffect, useRef } from 'react';\nimport { useTheme } from 'next-themes';\n\n/**\n * OcThemeBridge — cross-subdomain persistence for the MODE axis (light/dark).\n *\n * `next-themes` defaults to origin-scoped `localStorage`, so a dark/light choice\n * on one `*.ochk.io` site won't carry to another. This layers an\n * `oc_theme` cookie at `Domain=.ochk.io` on top so the choice is visible\n * family-wide. (The SKIN axis has its own `oc_skin` bridge inside\n * `OcThemeProvider`; this is the mode counterpart, previously duplicated in\n * every consumer repo.)\n *\n * Mount once inside the next-themes `<ThemeProvider>`:\n *\n * <ThemeProvider attribute=\"class\" defaultTheme=\"system\" enableSystem>\n * <OcThemeBridge />\n * <OcThemeProvider>…</OcThemeProvider>\n * </ThemeProvider>\n *\n * The cookie is intentionally non-HttpOnly (next-themes JS reads/writes it) and\n * carries no auth value — a leak only reveals a theme preference.\n */\nconst COOKIE = 'oc_theme';\nconst ALLOWED = new Set(['light', 'dark', 'system']);\nconst ONE_YEAR = 60 * 60 * 24 * 365;\n\nfunction readCookie(name: string): string | null {\n if (typeof document === 'undefined') return null;\n const prefix = `${name}=`;\n for (const part of document.cookie.split(';')) {\n const trimmed = part.trim();\n if (trimmed.startsWith(prefix)) {\n const v = decodeURIComponent(trimmed.slice(prefix.length));\n return v.length > 0 ? v : null;\n }\n }\n return null;\n}\n\nfunction writeCookie(value: string): void {\n if (typeof document === 'undefined') return;\n if (!ALLOWED.has(value)) return;\n const isLocal = location.hostname === 'localhost' || location.hostname === '127.0.0.1';\n const parts = [\n `${COOKIE}=${encodeURIComponent(value)}`,\n 'Path=/',\n `Max-Age=${ONE_YEAR}`,\n 'SameSite=Lax',\n ];\n if (!isLocal) {\n parts.push('Domain=.ochk.io');\n parts.push('Secure');\n }\n document.cookie = parts.join('; ');\n}\n\nexport function OcThemeBridge() {\n const { theme, setTheme } = useTheme();\n const hydratedRef = useRef(false);\n\n useEffect(() => {\n if (hydratedRef.current) return;\n hydratedRef.current = true;\n const cookieValue = readCookie(COOKIE);\n if (cookieValue && ALLOWED.has(cookieValue) && cookieValue !== theme) {\n setTheme(cookieValue);\n }\n }, [theme, setTheme]);\n\n useEffect(() => {\n if (!hydratedRef.current) return;\n if (!theme) return;\n writeCookie(theme);\n }, [theme]);\n\n return null;\n}\n","'use client';\n\nimport { Check, Palette } from 'lucide-react';\nimport { useEffect, useRef, useState } from 'react';\n\nimport { cn } from './cn';\nimport { useOcSkin } from './provider';\n\n/**\n * OcThemePicker — the skin selector. Sits beside `ThemeToggle` in the header.\n * Self-contained dropdown (no Radix): outside-click + Escape to close. Mirrors\n * the ghost-icon affordance of `ThemeToggle`. Gate its mount behind\n * `NEXT_PUBLIC_OC_THEME_PICKER` during staged rollout.\n */\nexport interface OcThemePickerProps {\n className?: string;\n triggerClassName?: string;\n popoverClassName?: string;\n}\n\nexport function OcThemePicker({ className, triggerClassName, popoverClassName }: OcThemePickerProps) {\n const { skin, setSkin, themes } = useOcSkin();\n const [open, setOpen] = useState(false);\n const rootRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n if (!open) return;\n function onDown(e: MouseEvent) {\n if (rootRef.current && !rootRef.current.contains(e.target as Node)) setOpen(false);\n }\n function onKey(e: KeyboardEvent) {\n if (e.key === 'Escape') setOpen(false);\n }\n document.addEventListener('mousedown', onDown);\n document.addEventListener('keydown', onKey);\n return () => {\n document.removeEventListener('mousedown', onDown);\n document.removeEventListener('keydown', onKey);\n };\n }, [open]);\n\n return (\n <div ref={rootRef} className={cn('relative', className)}>\n <button\n type=\"button\"\n aria-label=\"choose theme\"\n aria-haspopup=\"menu\"\n aria-expanded={open}\n title=\"choose theme\"\n onClick={() => setOpen((v) => !v)}\n className={cn(\n 'text-muted-foreground hover:text-foreground inline-flex size-9 items-center justify-center rounded-md transition-colors outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50',\n triggerClassName\n )}\n >\n <Palette className=\"size-4\" aria-hidden />\n </button>\n\n {open && (\n <div\n role=\"menu\"\n className={cn(\n 'bg-popover text-popover-foreground absolute right-0 z-50 mt-2 w-64 overflow-hidden rounded-md border shadow-md',\n popoverClassName\n )}\n >\n <div className=\"label-mono text-muted-foreground px-3 pt-3 pb-1\">theme</div>\n <ul className=\"pb-1\">\n {themes.map((t) => {\n const active = t.id === skin;\n return (\n <li key={t.id}>\n <button\n type=\"button\"\n role=\"menuitemradio\"\n aria-checked={active}\n onClick={() => {\n setSkin(t.id);\n setOpen(false);\n }}\n className={cn(\n 'hover:bg-accent hover:text-accent-foreground flex w-full items-start gap-2 px-3 py-2 text-left transition-colors',\n active && 'text-foreground'\n )}\n >\n <span className=\"mt-0.5 size-4 shrink-0\">\n {active && <Check className=\"size-4 text-primary\" aria-hidden />}\n </span>\n <span className=\"min-w-0\">\n <span className=\"block text-sm font-medium\">{t.label}</span>\n <span className=\"text-muted-foreground block text-xs leading-snug\">\n {t.description}\n </span>\n </span>\n </button>\n </li>\n );\n })}\n </ul>\n </div>\n )}\n </div>\n );\n}\n","'use client';\n\nimport { Check, Monitor, Moon, Palette, Sun } from 'lucide-react';\nimport { useTheme } from 'next-themes';\nimport { useEffect, useRef, useState } from 'react';\n\nimport { cn } from './cn';\nimport { useOcSkin } from './provider';\n\n/**\n * OcAppearanceMenu — the single header control for ALL appearance settings.\n *\n * One icon button opens a dropdown with both axes:\n * • mode — light / dark / system (next-themes `.dark` class)\n * • theme — the registered skins (data-oc-theme, persisted .ochk.io)\n *\n * Replaces the separate `ThemeToggle` + `OcThemePicker` so every family site\n * carries exactly one appearance control. Self-contained dropdown (no Radix):\n * outside-click + Escape to close. Requires `<OcThemeProvider>` above it (skin\n * axis) and a next-themes `<ThemeProvider>` (mode axis) — both already mounted\n * on every family site.\n */\nexport interface OcAppearanceMenuProps {\n className?: string;\n triggerClassName?: string;\n popoverClassName?: string;\n}\n\nconst MODES = [\n { id: 'light', label: 'light', Icon: Sun },\n { id: 'dark', label: 'dark', Icon: Moon },\n { id: 'system', label: 'system', Icon: Monitor },\n] as const;\n\nexport function OcAppearanceMenu({\n className,\n triggerClassName,\n popoverClassName,\n}: OcAppearanceMenuProps) {\n const { skin, setSkin, themes } = useOcSkin();\n const { theme, setTheme } = useTheme();\n const [open, setOpen] = useState(false);\n const [mounted, setMounted] = useState(false);\n const rootRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => setMounted(true), []);\n\n useEffect(() => {\n if (!open) return;\n function onDown(e: MouseEvent) {\n if (rootRef.current && !rootRef.current.contains(e.target as Node)) setOpen(false);\n }\n function onKey(e: KeyboardEvent) {\n if (e.key === 'Escape') setOpen(false);\n }\n document.addEventListener('mousedown', onDown);\n document.addEventListener('keydown', onKey);\n return () => {\n document.removeEventListener('mousedown', onDown);\n document.removeEventListener('keydown', onKey);\n };\n }, [open]);\n\n // Avoid a hydration mismatch: only reflect the active mode after mount.\n const activeMode = mounted ? (theme ?? 'system') : null;\n\n return (\n <div ref={rootRef} className={cn('relative', className)}>\n <button\n type=\"button\"\n aria-label=\"appearance settings\"\n aria-haspopup=\"menu\"\n aria-expanded={open}\n title=\"appearance · mode + theme\"\n onClick={() => setOpen((v) => !v)}\n className={cn(\n 'text-muted-foreground hover:text-foreground inline-flex size-9 items-center justify-center rounded-md transition-colors outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50',\n triggerClassName\n )}\n >\n <Palette className=\"size-4\" aria-hidden />\n </button>\n\n {open && (\n <div\n role=\"menu\"\n className={cn(\n 'bg-popover text-popover-foreground absolute right-0 z-50 mt-2 w-64 overflow-hidden rounded-md border shadow-md',\n popoverClassName\n )}\n >\n {/* Mode — segmented light / dark / system */}\n <div className=\"label-mono text-muted-foreground px-3 pt-3 pb-2\">mode</div>\n <div className=\"grid grid-cols-3 gap-1 px-2 pb-2\">\n {MODES.map(({ id, label, Icon }) => {\n const active = activeMode === id;\n return (\n <button\n key={id}\n type=\"button\"\n role=\"menuitemradio\"\n aria-checked={active}\n onClick={() => setTheme(id)}\n className={cn(\n 'flex flex-col items-center gap-1 rounded-md border py-2 text-[11px] transition-colors',\n active\n ? 'border-primary text-foreground bg-accent'\n : 'border-transparent text-muted-foreground hover:bg-accent hover:text-accent-foreground'\n )}\n >\n <Icon className=\"size-4\" aria-hidden />\n {label}\n </button>\n );\n })}\n </div>\n\n {/* Theme — the registered skins */}\n <div className=\"label-mono text-muted-foreground border-t px-3 pt-3 pb-1\">\n theme\n </div>\n <ul className=\"pb-1\">\n {themes.map((t) => {\n const active = t.id === skin;\n return (\n <li key={t.id}>\n <button\n type=\"button\"\n role=\"menuitemradio\"\n aria-checked={active}\n onClick={() => setSkin(t.id)}\n className={cn(\n 'hover:bg-accent hover:text-accent-foreground flex w-full items-start gap-2 px-3 py-2 text-left transition-colors',\n active && 'text-foreground'\n )}\n >\n <span className=\"mt-0.5 size-4 shrink-0\">\n {active && (\n <Check className=\"text-primary size-4\" aria-hidden />\n )}\n </span>\n <span className=\"min-w-0\">\n <span className=\"block text-sm font-medium\">{t.label}</span>\n <span className=\"text-muted-foreground block text-xs leading-snug\">\n {t.description}\n </span>\n </span>\n </button>\n </li>\n );\n })}\n </ul>\n </div>\n )}\n </div>\n );\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orangecheck/design",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "OrangeCheck design system — multi-theme tokens, primitives, and family composites for the .ochk.io sub-sites. Tailwind 4 + Next.js Pages Router. Not for third-party integration.",
5
5
  "keywords": [
6
6
  "orangecheck",