@fiestaboard/ui 1.6.5 → 2.0.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.
@@ -0,0 +1,24 @@
1
+ import { type BoardPreviewEntry, type PreviewShapeLabels } from "../../lib/board-previews";
2
+ export interface BoardShowcaseLabels extends PreviewShapeLabels {
3
+ /** Accessible name for the shape tab list. */
4
+ boardShape: string;
5
+ /** Accessible name for the board-colour group. */
6
+ boardColor: string;
7
+ blackBoard: string;
8
+ whiteBoard: string;
9
+ }
10
+ export declare const DEFAULT_SHOWCASE_LABELS: BoardShowcaseLabels;
11
+ export interface BoardShowcaseProps {
12
+ /** Literal boards to show; the first is the default tab. */
13
+ previews: BoardPreviewEntry[];
14
+ /** Accessible description of the board, e.g. "Weather on a split-flap board". */
15
+ previewLabel?: string;
16
+ size?: "sm" | "md" | "lg";
17
+ /** Controlled board colour. Leave unset to let the showcase own it. */
18
+ boardType?: "black" | "white";
19
+ defaultBoardType?: "black" | "white";
20
+ onBoardTypeChange?: (boardType: "black" | "white") => void;
21
+ labels?: Partial<BoardShowcaseLabels>;
22
+ className?: string;
23
+ }
24
+ export declare function BoardShowcase({ previews, previewLabel, size, boardType, defaultBoardType, onBoardTypeChange, labels, className, }: BoardShowcaseProps): import("react").JSX.Element | null;
@@ -0,0 +1,73 @@
1
+ "use client";
2
+ import { cn as e } from "../../lib/utils.js";
3
+ import { Tabs as t, TabsContent as n, TabsList as r, TabsTrigger as i } from "../ui/tabs.js";
4
+ import { StaticBoardDisplay as a } from "../board/static-board-display.js";
5
+ import { DEFAULT_SHAPE_LABELS as o, previewLabels as s, previewMessage as c } from "../../lib/board-previews.js";
6
+ import { useState as l } from "react";
7
+ import { jsx as u, jsxs as d } from "react/jsx-runtime";
8
+ //#region src/components/plugin/board-showcase.tsx
9
+ var f = {
10
+ ...o,
11
+ boardShape: "Board shape",
12
+ boardColor: "Board color",
13
+ blackBoard: "Black Board",
14
+ whiteBoard: "White Board"
15
+ }, p = "rounded-full border px-4 py-1.5 text-xs font-medium transition-colors data-[active]:border-brand-emphasis data-[active]:bg-brand-emphasis data-[active]:text-brand-foreground data-[active]:font-semibold hover:border-brand hover:text-brand data-[active]:hover:text-brand-foreground";
16
+ function m({ previews: o, previewLabel: m, size: h = "md", boardType: g, defaultBoardType: _ = "black", onBoardTypeChange: v, labels: y, className: b }) {
17
+ let x = {
18
+ ...f,
19
+ ...y
20
+ }, [S, C] = l(_), w = g ?? S;
21
+ function T(e) {
22
+ g === void 0 && C(e), v?.(e);
23
+ }
24
+ if (o.length === 0) return null;
25
+ let E = s(o, x);
26
+ return /* @__PURE__ */ d("div", {
27
+ "data-slot": "board-showcase",
28
+ className: e("flex flex-col items-center gap-4", b),
29
+ children: [/* @__PURE__ */ d(t, {
30
+ defaultValue: 0,
31
+ className: "flex w-full flex-col items-center gap-4",
32
+ children: [o.length > 1 && /* @__PURE__ */ u(r, {
33
+ "aria-label": x.boardShape,
34
+ className: "h-auto gap-2 rounded-full bg-transparent p-0",
35
+ children: E.map((e, t) => /* @__PURE__ */ u(i, {
36
+ value: t,
37
+ className: p,
38
+ children: e
39
+ }, e))
40
+ }), o.map((e, t) => /* @__PURE__ */ u(n, {
41
+ value: t,
42
+ className: "mt-0 flex w-full justify-center overflow-x-auto rounded-xl bg-muted/40 px-4 py-6 shadow-card",
43
+ children: /* @__PURE__ */ u(a, {
44
+ message: c(e),
45
+ size: h,
46
+ boardType: w,
47
+ deviceType: e.device_type ?? "flagship",
48
+ notesWide: e.notes_wide ?? 1,
49
+ notesTall: e.notes_tall ?? 1,
50
+ previewLabel: m
51
+ })
52
+ }, E[t]))]
53
+ }), /* @__PURE__ */ u("div", {
54
+ role: "group",
55
+ "aria-label": x.boardColor,
56
+ className: "flex",
57
+ children: ["black", "white"].map((t) => {
58
+ let n = w === t;
59
+ return /* @__PURE__ */ u("button", {
60
+ type: "button",
61
+ "aria-pressed": n,
62
+ onClick: () => T(t),
63
+ className: e("border px-4 py-1.5 text-xs font-medium transition-colors", "first:rounded-l-full last:rounded-r-full [&:not(:last-child)]:border-r-0", "outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50", n ? "border-brand-emphasis bg-brand-emphasis font-semibold text-brand-foreground" : "text-muted-foreground hover:border-brand hover:text-brand"),
64
+ children: t === "black" ? x.blackBoard : x.whiteBoard
65
+ }, t);
66
+ })
67
+ })]
68
+ });
69
+ }
70
+ //#endregion
71
+ export { m as BoardShowcase, f as DEFAULT_SHOWCASE_LABELS };
72
+
73
+ //# sourceMappingURL=board-showcase.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"board-showcase.js","names":[],"sources":["../../../src/components/plugin/board-showcase.tsx"],"mappings":";;;;;;;;AAiCA,IAAa,IAA+C;CAC1D,GAAG;CACH,YAAY;CACZ,YAAY;CACZ,YAAY;CACZ,YAAY;AACd,GAoBM,IACJ;AAIF,SAAgB,EAAc,EAC5B,aACA,iBACA,UAAO,MACP,cACA,sBAAmB,SACnB,sBACA,WACA,gBACqB;CACrB,IAAM,IAAI;EAAE,GAAG;EAAyB,GAAG;CAAO,GAC5C,CAAC,GAAuB,KAA4B,EAA4B,CAAgB,GAChG,IAAkB,KAAa;CAErC,SAAS,EAAgB,GAAyB;EAEhD,AADI,MAAc,KAAA,KAAW,EAAyB,CAAI,GAC1D,IAAoB,CAAI;CAC1B;CAEA,IAAI,EAAS,WAAW,GAAG,OAAO;CAElC,IAAM,IAAY,EAAc,GAAU,CAAC;CAE3C,OACE,kBAAC,OAAD;EAAK,aAAU;EAAiB,WAAW,EAAG,oCAAoC,CAAS;EAA3F,UAAA,CACE,kBAAC,GAAD;GAAM,cAAc;GAAG,WAAU;GAAjC,UAAA,CACG,EAAS,SAAS,KACjB,kBAAC,GAAD;IAAU,cAAY,EAAE;IAAY,WAAU;IAC3C,UAAA,EAAU,KAAK,GAAO,MACrB,kBAAC,GAAD;KAAyB,OAAO;KAAO,WAAW;KAC/C,UAAA;IACU,GAFK,CAEL,CACd;GACO,CAAA,GAGX,EAAS,KAAK,GAAS,MACtB,kBAAC,GAAD;IAEE,OAAO;IACP,WAAU;IAEV,UAAA,kBAAC,GAAD;KACE,SAAS,EAAe,CAAO;KACzB;KACN,WAAW;KACX,YAAY,EAAQ,eAAe;KACnC,WAAW,EAAQ,cAAc;KACjC,WAAW,EAAQ,cAAc;KACnB;IACf,CAAA;GACU,GAbN,EAAU,EAaJ,CACd,CACG;EAEN,CAAA,GAAA,kBAAC,OAAD;GAAK,MAAK;GAAQ,cAAY,EAAE;GAAY,WAAU;GAClD,UAAA,CAAC,SAAS,OAAO,CAAC,CAAW,KAAK,MAAU;IAC5C,IAAM,IAAS,MAAoB;IACnC,OACE,kBAAC,UAAD;KAEE,MAAK;KACL,gBAAc;KACd,eAAe,EAAgB,CAAK;KACpC,WAAW,EACT,4DACA,4EACA,oEACA,IACI,gFACA,2DACN;KAEC,UAAA,MAAU,UAAU,EAAE,aAAa,EAAE;IAChC,GAdD,CAcC;GAEZ,CAAC;EACE,CAAA,CACF;;AAET"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Directory card for a plugin: name, category, description, author, and a
3
+ * split-flap teaser strip showing what the plugin actually puts on a board.
4
+ *
5
+ * Presentational — routing and localization stay with the consumer. The
6
+ * primary link is supplied through `renderLink` because the two consumers use
7
+ * different routers (Docusaurus `Link`, the app's `smart-link`); the card
8
+ * stretches it over the whole surface so the `action` slot can hold a button
9
+ * without nesting one interactive element inside another.
10
+ */
11
+ import type { CSSProperties, ReactNode } from "react";
12
+ export interface PluginCardProps {
13
+ name: string;
14
+ description?: ReactNode;
15
+ /**
16
+ * Pre-formatted attribution, e.g. `"by FiestaBoard Team"` — the wording is a
17
+ * localized string, so the consumer builds it.
18
+ */
19
+ authorLabel?: ReactNode;
20
+ /** Manifest category id, e.g. `"weather"`. Omit to hide the badge. */
21
+ category?: string;
22
+ /** Localized category name. */
23
+ categoryLabel?: string;
24
+ /** Literal board line, at most 15 tiles. Omit to hide the teaser footer. */
25
+ teaser?: string;
26
+ boardType?: "black" | "white";
27
+ /**
28
+ * Renders the card's primary link. Receives the class that stretches it over
29
+ * the card, and the card title as children.
30
+ */
31
+ renderLink: (props: {
32
+ className: string;
33
+ children: ReactNode;
34
+ }) => ReactNode;
35
+ /** Trailing action, e.g. an Install button. Rendered above the stretched link. */
36
+ action?: ReactNode;
37
+ className?: string;
38
+ style?: CSSProperties;
39
+ }
40
+ export declare function PluginCard({ name, description, authorLabel, category, categoryLabel, teaser, boardType, renderLink, action, className, style, }: PluginCardProps): import("react").JSX.Element;
@@ -0,0 +1,65 @@
1
+ "use client";
2
+ import { cn as e } from "../../lib/utils.js";
3
+ import { Heading as t } from "../ui/heading.js";
4
+ import { Text as n } from "../ui/text.js";
5
+ import { PluginCategoryBadge as r } from "./plugin-category-badge.js";
6
+ import { ScaledBoardTeaser as i } from "./scaled-board-teaser.js";
7
+ import { jsx as a, jsxs as o } from "react/jsx-runtime";
8
+ //#region src/components/plugin/plugin-card.tsx
9
+ var s = "after:absolute after:inset-0 after:rounded-xl after:content-[''] outline-none focus-visible:after:ring-[3px] focus-visible:after:ring-ring/50 text-foreground no-underline hover:no-underline hover:text-foreground";
10
+ function c({ name: c, description: l, authorLabel: u, category: d, categoryLabel: f, teaser: p, boardType: m = "black", renderLink: h, action: g, className: _, style: v }) {
11
+ return /* @__PURE__ */ o("div", {
12
+ "data-slot": "plugin-card",
13
+ style: v,
14
+ className: e("group relative flex h-full flex-col overflow-hidden rounded-xl border bg-card text-card-foreground", "shadow-card transition-[transform,box-shadow] duration-200 hover:-translate-y-1 hover:shadow-lg", "motion-reduce:transition-none motion-reduce:hover:translate-y-0", _),
15
+ children: [/* @__PURE__ */ o("div", {
16
+ className: "flex flex-1 flex-col gap-2 p-4",
17
+ children: [
18
+ /* @__PURE__ */ o("div", {
19
+ className: "flex flex-wrap items-center gap-2",
20
+ children: [/* @__PURE__ */ a(t, {
21
+ level: 3,
22
+ className: "min-w-0 text-base",
23
+ children: h({
24
+ className: s,
25
+ children: c
26
+ })
27
+ }), d && /* @__PURE__ */ a(r, {
28
+ category: d,
29
+ label: f,
30
+ className: "ml-auto"
31
+ })]
32
+ }),
33
+ l && /* @__PURE__ */ a(n, {
34
+ tone: "muted",
35
+ size: "sm",
36
+ className: "line-clamp-3 flex-1 leading-relaxed",
37
+ children: l
38
+ }),
39
+ /* @__PURE__ */ o("div", {
40
+ className: "mt-auto flex items-center justify-between gap-2 pt-1",
41
+ children: [u ? /* @__PURE__ */ a(n, {
42
+ as: "span",
43
+ tone: "muted",
44
+ size: "xs",
45
+ className: "truncate",
46
+ children: u
47
+ }) : /* @__PURE__ */ a("span", {}), g && /* @__PURE__ */ a("div", {
48
+ className: "relative z-10 shrink-0",
49
+ children: g
50
+ })]
51
+ })
52
+ ]
53
+ }), p && /* @__PURE__ */ a("div", {
54
+ className: "border-t bg-muted/30 px-3 py-4",
55
+ children: /* @__PURE__ */ a(i, {
56
+ teaser: p,
57
+ boardType: m
58
+ })
59
+ })]
60
+ });
61
+ }
62
+ //#endregion
63
+ export { c as PluginCard };
64
+
65
+ //# sourceMappingURL=plugin-card.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-card.js","names":[],"sources":["../../../src/components/plugin/plugin-card.tsx"],"mappings":";;;;;;;;AAsBA,IAAM,IACJ;AA8BF,SAAgB,EAAW,EACzB,SACA,gBACA,gBACA,aACA,kBACA,WACA,eAAY,SACZ,eACA,WACA,cACA,YACkB;CAClB,OACE,kBAAC,OAAD;EACE,aAAU;EACH;EACP,WAAW,EACT,sGACA,mGACA,mEACA,CACF;EARF,UAAA,CAUE,kBAAC,OAAD;GAAK,WAAU;GAAf,UAAA;IACE,kBAAC,OAAD;KAAK,WAAU;KAAf,UAAA,CACE,kBAAC,GAAD;MAAS,OAAO;MAAG,WAAU;MAC1B,UAAA,EAAW;OAAE,WAAW;OAAsB,UAAU;MAAK,CAAC;KACxD,CAAA,GACR,KAAY,kBAAC,GAAD;MAA+B;MAAU,OAAO;MAAe,WAAU;KAAW,CAAA,CAC9F;;IAEJ,KACC,kBAAC,GAAD;KAAM,MAAK;KAAQ,MAAK;KAAK,WAAU;KACpC,UAAA;IACG,CAAA;IAGR,kBAAC,OAAD;KAAK,WAAU;KAAf,UAAA,CACG,IACC,kBAAC,GAAD;MAAM,IAAG;MAAO,MAAK;MAAQ,MAAK;MAAK,WAAU;MAC9C,UAAA;KACG,CAAA,IAEN,kBAAC,QAAD,CAAO,CAAA,GAER,KAAU,kBAAC,OAAD;MAAK,WAAU;MAA0B,UAAA;KAAY,CAAA,CAC7D;;GACF;EAEJ,CAAA,GAAA,KACC,kBAAC,OAAD;GAAK,WAAU;GACb,UAAA,kBAAC,GAAD;IAA2B;IAAmB;GAAY,CAAA;EACvD,CAAA,CAEJ;;AAET"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Category badge for a plugin. One hue per category, defined once here so a
3
+ * plugin reads the same colour in the FiestaBoard marketplace and in the docs
4
+ * plugin directory. Unknown categories fall back to the neutral badge.
5
+ */
6
+ /** The categories FiestaBoard plugins may declare (manifest `category`). */
7
+ export declare const PLUGIN_CATEGORIES: readonly ["art", "data", "entertainment", "home", "transit", "utility", "weather"];
8
+ export type PluginCategory = (typeof PLUGIN_CATEGORIES)[number];
9
+ export interface PluginCategoryBadgeProps {
10
+ /** Manifest category id, e.g. `"weather"`. */
11
+ category: string;
12
+ /** Localized display name. Defaults to the raw category id. */
13
+ label?: string;
14
+ className?: string;
15
+ }
16
+ export declare function PluginCategoryBadge({ category, label, className }: PluginCategoryBadgeProps): import("react").JSX.Element;
@@ -0,0 +1,37 @@
1
+ import { cn as e } from "../../lib/utils.js";
2
+ import { Badge as t } from "../ui/badge.js";
3
+ import { jsx as n } from "react/jsx-runtime";
4
+ //#region src/components/plugin/plugin-category-badge.tsx
5
+ var r = [
6
+ "art",
7
+ "data",
8
+ "entertainment",
9
+ "home",
10
+ "transit",
11
+ "utility",
12
+ "weather"
13
+ ], i = {
14
+ art: "bg-category-art/15 border-category-art/30 text-category-art-foreground",
15
+ data: "bg-category-data/15 border-category-data/30 text-category-data-foreground",
16
+ entertainment: "bg-category-entertainment/15 border-category-entertainment/30 text-category-entertainment-foreground",
17
+ home: "bg-category-home/15 border-category-home/30 text-category-home-foreground",
18
+ transit: "bg-category-transit/15 border-category-transit/30 text-category-transit-foreground",
19
+ utility: "bg-category-utility/15 border-category-utility/30 text-category-utility-foreground",
20
+ weather: "bg-category-weather/15 border-category-weather/30 text-category-weather-foreground"
21
+ };
22
+ function a(e) {
23
+ return e in i;
24
+ }
25
+ function o({ category: r, label: o, className: s }) {
26
+ let c = a(r) ? i[r] : "";
27
+ return /* @__PURE__ */ n(t, {
28
+ variant: a(r) ? "outline" : "secondary",
29
+ "data-category": r,
30
+ className: e("text-[0.68rem] font-semibold uppercase tracking-wide px-2 py-0.5", c, s),
31
+ children: o ?? r
32
+ });
33
+ }
34
+ //#endregion
35
+ export { r as PLUGIN_CATEGORIES, o as PluginCategoryBadge };
36
+
37
+ //# sourceMappingURL=plugin-category-badge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-category-badge.js","names":[],"sources":["../../../src/components/plugin/plugin-category-badge.tsx"],"mappings":";;;;AAUA,IAAa,IAAoB;CAAC;CAAO;CAAQ;CAAiB;CAAQ;CAAW;CAAW;AAAS,GAQnG,IAAmD;CACvD,KAAK;CACL,MAAM;CACN,eAAe;CACf,MAAM;CACN,SAAS;CACT,SAAS;CACT,SAAS;AACX;AAEA,SAAS,EAAgB,GAA8C;CACrE,OAAO,KAAY;AACrB;AAUA,SAAgB,EAAoB,EAAE,aAAU,UAAO,gBAAuC;CAC5F,IAAM,IAAO,EAAgB,CAAQ,IAAI,EAAiB,KAAY;CACtE,OACE,kBAAC,GAAD;EACE,SAAS,EAAgB,CAAQ,IAAI,YAAY;EACjD,iBAAe;EACf,WAAW,EAAG,oEAAoE,GAAM,CAAS;EAEhG,UAAA,KAAS;CACL,CAAA;AAEX"}
@@ -0,0 +1,11 @@
1
+ export interface ScaledBoardTeaserProps {
2
+ /** Literal board line, e.g. `"{66}AQI 45 CLEAR"`. */
3
+ teaser: string;
4
+ boardType?: "black" | "white";
5
+ /** Strip width in tiles. */
6
+ tiles?: number;
7
+ minScale?: number;
8
+ maxScale?: number;
9
+ className?: string;
10
+ }
11
+ export declare function ScaledBoardTeaser({ teaser, boardType, tiles, minScale, maxScale, className, }: ScaledBoardTeaserProps): import("react").JSX.Element;
@@ -0,0 +1,40 @@
1
+ "use client";
2
+ import { cn as e } from "../../lib/utils.js";
3
+ import { BoardTeaser as t } from "../board/board-teaser.js";
4
+ import { useEffect as n, useRef as r, useState as i } from "react";
5
+ import { jsx as a } from "react/jsx-runtime";
6
+ //#region src/components/plugin/scaled-board-teaser.tsx
7
+ var o = 18;
8
+ function s({ teaser: s, boardType: c = "black", tiles: l = 15, minScale: u = .85, maxScale: d = 1.5, className: f }) {
9
+ let p = r(null), [m, h] = i(1);
10
+ return n(() => {
11
+ let e = p.current, t = e?.firstElementChild;
12
+ if (!e || !t) return;
13
+ let n = () => {
14
+ t.offsetWidth > 0 && h(Math.min(d, Math.max(u, e.clientWidth / t.offsetWidth)));
15
+ };
16
+ n();
17
+ let r = new ResizeObserver(n);
18
+ return r.observe(e), () => r.disconnect();
19
+ }, [u, d]), /* @__PURE__ */ a("div", {
20
+ ref: p,
21
+ "data-slot": "scaled-board-teaser",
22
+ className: e("flex justify-center overflow-hidden", f),
23
+ style: { height: `${o * m}px` },
24
+ children: /* @__PURE__ */ a("div", {
25
+ style: {
26
+ transform: `scale(${m})`,
27
+ transformOrigin: "top center"
28
+ },
29
+ children: /* @__PURE__ */ a(t, {
30
+ teaser: s,
31
+ tiles: l,
32
+ boardType: c
33
+ })
34
+ })
35
+ });
36
+ }
37
+ //#endregion
38
+ export { s as ScaledBoardTeaser };
39
+
40
+ //# sourceMappingURL=scaled-board-teaser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaled-board-teaser.js","names":[],"sources":["../../../src/components/plugin/scaled-board-teaser.tsx"],"mappings":";;;;;;AAgBA,IAAM,IAAe;AAarB,SAAgB,EAAkB,EAChC,WACA,eAAY,SACZ,WAAQ,IACR,cAAW,KACX,cAAW,KACX,gBACyB;CACzB,IAAM,IAAe,EAAuB,IAAI,GAC1C,CAAC,GAAO,KAAY,EAAS,CAAC;CAkBpC,OAhBA,QAAgB;EACd,IAAM,IAAY,EAAa,SACzB,IAAQ,GAAW;EACzB,IAAI,CAAC,KAAa,CAAC,GAAO;EAC1B,IAAM,UAAgB;GAEpB,AAAI,EAAM,cAAc,KACtB,EAAS,KAAK,IAAI,GAAU,KAAK,IAAI,GAAU,EAAU,cAAc,EAAM,WAAW,CAAC,CAAC;EAE9F;EACA,EAAQ;EACR,IAAM,IAAW,IAAI,eAAe,CAAO;EAE3C,OADA,EAAS,QAAQ,CAAS,SACb,EAAS,WAAW;CACnC,GAAG,CAAC,GAAU,CAAQ,CAAC,GAGrB,kBAAC,OAAD;EACE,KAAK;EACL,aAAU;EACV,WAAW,EAAG,uCAAuC,CAAS;EAC9D,OAAO,EAAE,QAAQ,GAAG,IAAe,EAAM,IAAI;EAE7C,UAAA,kBAAC,OAAD;GAAK,OAAO;IAAE,WAAW,SAAS,EAAM;IAAI,iBAAiB;GAAa;GACxE,UAAA,kBAAC,GAAD;IAAqB;IAAe;IAAkB;GAAY,CAAA;EAC/D,CAAA;CACF,CAAA;AAET"}
package/dist/index.d.ts CHANGED
@@ -64,3 +64,8 @@ export * from "./components/board/static-board-display";
64
64
  export * from "./lib/board-characters";
65
65
  export * from "./lib/board-colors";
66
66
  export * from "./lib/board-dimensions";
67
+ export * from "./lib/board-previews";
68
+ export * from "./components/plugin/board-showcase";
69
+ export * from "./components/plugin/plugin-card";
70
+ export * from "./components/plugin/plugin-category-badge";
71
+ export * from "./components/plugin/scaled-board-teaser";
package/dist/index.js CHANGED
@@ -56,4 +56,9 @@ import { BoardDisplay as un } from "./components/board/board-display.js";
56
56
  import { BoardTeaser as dn } from "./components/board/board-teaser.js";
57
57
  import { ScaledBoardDisplay as fn } from "./components/board/scaled-board-display.js";
58
58
  import { StaticBoardDisplay as pn } from "./components/board/static-board-display.js";
59
- export { Bt as ALL_COLOR_CODES, Vt as AVAILABLE_COLORS, t as Accordion, n as AccordionContent, r as AccordionItem, i as AccordionTrigger, a as Alert, o as AlertDescription, u as AlertDialog, d as AlertDialogAction, f as AlertDialogCancel, p as AlertDialogContent, m as AlertDialogDescription, h as AlertDialogFooter, g as AlertDialogHeader, _ as AlertDialogOverlay, v as AlertDialogPortal, y as AlertDialogTitle, b as AlertDialogTrigger, s as AlertTitle, x as Aurora, Jt as BOARD_CHARS, Ht as BOARD_COLORS, S as Badge, un as BoardDisplay, pt as BoardIcon, mt as BoardSelector, dn as BoardTeaser, w as Box, c as Button, Ut as COLOR_CODE_MAP, Wt as COLOR_DISPLAY, T as Card, E as CardAction, D as CardContent, O as CardDescription, k as CardFooter, A as CardHeader, j as CardTitle, M as Checkbox, N as Code, P as Collapsible, F as CollapsibleContent, I as CollapsibleTrigger, nn as DEVICE_DIMENSIONS, be as DecryptedText, L as Dialog, R as DialogClose, z as DialogContent, B as DialogDescription, V as DialogFooter, H as DialogHeader, U as DialogOverlay, W as DialogPortal, G as DialogTitle, K as DialogTrigger, q as DropdownMenu, J as DropdownMenuCheckboxItem, Y as DropdownMenuContent, X as DropdownMenuGroup, Z as DropdownMenuItem, Q as DropdownMenuLabel, $ as DropdownMenuPortal, ee as DropdownMenuRadioGroup, te as DropdownMenuRadioItem, ne as DropdownMenuSeparator, re as DropdownMenuShortcut, ie as DropdownMenuSub, ae as DropdownMenuSubContent, oe as DropdownMenuSubTrigger, se as DropdownMenuTrigger, Yt as EXTRA_CHARS, ce as EmptyState, ht as FIESTA_ICON_DATA_URI, gt as FIESTA_ICON_PALETTE, _t as FIESTA_ICON_SVG, xe as FadeContent, vt as FiestaIcon, yt as FiestaLogo, le as Flex, de as Grid, jt as HIDE_FESTIVE_COOKIE, pe as Heading, he as Input, ge as Label, bt as LanguageSelector, _e as List, ve as ListItem, rn as MAX_NOTES_PER_AXIS, xt as MainContent, an as NOTE_COLS, on as NOTE_ROWS, Mt as PRIDE_SEASON, St as PageHeader, Ct as PageIconGradientDefs, wt as PageLayout, Tt as PageToolbar, Nt as SEASONS, fn as ScaledBoardDisplay, Se as ScrollArea, Ce as ScrollBar, we as Select, Te as SelectContent, Ee as SelectGroup, De as SelectItem, Oe as SelectLabel, ke as SelectScrollDownButton, Ae as SelectScrollUpButton, je as SelectSeparator, Me as SelectTrigger, Ne as SelectValue, Pe as Sheet, Fe as SheetClose, Ie as SheetContent, Le as SheetDescription, Re as SheetFooter, ze as SheetHeader, Be as SheetOverlay, Ve as SheetPortal, He as SheetTitle, Ue as SheetTrigger, Ot as Sidebar, Et as SidebarAurora, Dt as SidebarAuroraHorizontal, We as Skeleton, kt as SkipToContent, Ge as Slider, Ke as Stack, pn as StaticBoardDisplay, Je as Switch, Ye as Table, Xe as TableBody, Ze as TableCell, Qe as TableHead, $e as TableHeader, et as TableRow, tt as Tabs, nt as TabsContent, rt as TabsList, it as TabsTrigger, at as Text, st as TextLink, ct as Textarea, At as ThemeToggle, lt as Tooltip, ut as TooltipContent, dt as TooltipProvider, ft as TooltipTrigger, C as badgeVariants, l as buttonVariants, e as cn, Pt as fireSeasonBurst, ue as flexVariants, Ft as getActiveSeason, Gt as getBoardColor, Xt as getCharFromToken, Zt as getCharIndex, fe as gridVariants, me as headingVariants, Qt as isColorTile, sn as isNoteArray, Kt as isValidBoardColor, ye as listVariants, $t as messageToGrid, cn as noteArrayDimensions, en as parseLine, It as readCookieString, qt as resolveColorCode, ln as resolveDimensions, Lt as shouldShowPride, qe as stackVariants, ot as textVariants, tn as tokensEqual, Rt as useActiveSeason, zt as usePrideActive };
59
+ import { DEFAULT_SHAPE_LABELS as mn, previewLabel as hn, previewLabels as gn, previewMessage as _n } from "./lib/board-previews.js";
60
+ import { BoardShowcase as vn, DEFAULT_SHOWCASE_LABELS as yn } from "./components/plugin/board-showcase.js";
61
+ import { PLUGIN_CATEGORIES as bn, PluginCategoryBadge as xn } from "./components/plugin/plugin-category-badge.js";
62
+ import { ScaledBoardTeaser as Sn } from "./components/plugin/scaled-board-teaser.js";
63
+ import { PluginCard as Cn } from "./components/plugin/plugin-card.js";
64
+ export { Bt as ALL_COLOR_CODES, Vt as AVAILABLE_COLORS, t as Accordion, n as AccordionContent, r as AccordionItem, i as AccordionTrigger, a as Alert, o as AlertDescription, u as AlertDialog, d as AlertDialogAction, f as AlertDialogCancel, p as AlertDialogContent, m as AlertDialogDescription, h as AlertDialogFooter, g as AlertDialogHeader, _ as AlertDialogOverlay, v as AlertDialogPortal, y as AlertDialogTitle, b as AlertDialogTrigger, s as AlertTitle, x as Aurora, Jt as BOARD_CHARS, Ht as BOARD_COLORS, S as Badge, un as BoardDisplay, pt as BoardIcon, mt as BoardSelector, vn as BoardShowcase, dn as BoardTeaser, w as Box, c as Button, Ut as COLOR_CODE_MAP, Wt as COLOR_DISPLAY, T as Card, E as CardAction, D as CardContent, O as CardDescription, k as CardFooter, A as CardHeader, j as CardTitle, M as Checkbox, N as Code, P as Collapsible, F as CollapsibleContent, I as CollapsibleTrigger, mn as DEFAULT_SHAPE_LABELS, yn as DEFAULT_SHOWCASE_LABELS, nn as DEVICE_DIMENSIONS, be as DecryptedText, L as Dialog, R as DialogClose, z as DialogContent, B as DialogDescription, V as DialogFooter, H as DialogHeader, U as DialogOverlay, W as DialogPortal, G as DialogTitle, K as DialogTrigger, q as DropdownMenu, J as DropdownMenuCheckboxItem, Y as DropdownMenuContent, X as DropdownMenuGroup, Z as DropdownMenuItem, Q as DropdownMenuLabel, $ as DropdownMenuPortal, ee as DropdownMenuRadioGroup, te as DropdownMenuRadioItem, ne as DropdownMenuSeparator, re as DropdownMenuShortcut, ie as DropdownMenuSub, ae as DropdownMenuSubContent, oe as DropdownMenuSubTrigger, se as DropdownMenuTrigger, Yt as EXTRA_CHARS, ce as EmptyState, ht as FIESTA_ICON_DATA_URI, gt as FIESTA_ICON_PALETTE, _t as FIESTA_ICON_SVG, xe as FadeContent, vt as FiestaIcon, yt as FiestaLogo, le as Flex, de as Grid, jt as HIDE_FESTIVE_COOKIE, pe as Heading, he as Input, ge as Label, bt as LanguageSelector, _e as List, ve as ListItem, rn as MAX_NOTES_PER_AXIS, xt as MainContent, an as NOTE_COLS, on as NOTE_ROWS, bn as PLUGIN_CATEGORIES, Mt as PRIDE_SEASON, St as PageHeader, Ct as PageIconGradientDefs, wt as PageLayout, Tt as PageToolbar, Cn as PluginCard, xn as PluginCategoryBadge, Nt as SEASONS, fn as ScaledBoardDisplay, Sn as ScaledBoardTeaser, Se as ScrollArea, Ce as ScrollBar, we as Select, Te as SelectContent, Ee as SelectGroup, De as SelectItem, Oe as SelectLabel, ke as SelectScrollDownButton, Ae as SelectScrollUpButton, je as SelectSeparator, Me as SelectTrigger, Ne as SelectValue, Pe as Sheet, Fe as SheetClose, Ie as SheetContent, Le as SheetDescription, Re as SheetFooter, ze as SheetHeader, Be as SheetOverlay, Ve as SheetPortal, He as SheetTitle, Ue as SheetTrigger, Ot as Sidebar, Et as SidebarAurora, Dt as SidebarAuroraHorizontal, We as Skeleton, kt as SkipToContent, Ge as Slider, Ke as Stack, pn as StaticBoardDisplay, Je as Switch, Ye as Table, Xe as TableBody, Ze as TableCell, Qe as TableHead, $e as TableHeader, et as TableRow, tt as Tabs, nt as TabsContent, rt as TabsList, it as TabsTrigger, at as Text, st as TextLink, ct as Textarea, At as ThemeToggle, lt as Tooltip, ut as TooltipContent, dt as TooltipProvider, ft as TooltipTrigger, C as badgeVariants, l as buttonVariants, e as cn, Pt as fireSeasonBurst, ue as flexVariants, Ft as getActiveSeason, Gt as getBoardColor, Xt as getCharFromToken, Zt as getCharIndex, fe as gridVariants, me as headingVariants, Qt as isColorTile, sn as isNoteArray, Kt as isValidBoardColor, ye as listVariants, $t as messageToGrid, cn as noteArrayDimensions, en as parseLine, hn as previewLabel, gn as previewLabels, _n as previewMessage, It as readCookieString, qt as resolveColorCode, ln as resolveDimensions, Lt as shouldShowPride, qe as stackVariants, ot as textVariants, tn as tokensEqual, Rt as useActiveSeason, zt as usePrideActive };
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Board previews — the `teaser` / `previews` plugin-manifest contract, shared
3
+ * by every surface that advertises a plugin (the FiestaBoard marketplace, the
4
+ * docs plugin directory).
5
+ *
6
+ * A preview holds *literal* board rows, never `{{variables}}`: widths count
7
+ * tiles, so a color marker like `{66}` is one tile and an end tag `{/green}` is
8
+ * none. See FiestaBoard's docs/development/PLUGIN_DEVELOPMENT.md.
9
+ */
10
+ import type { DeviceType } from "./board-dimensions";
11
+ /** One literal board, at one shape. */
12
+ export interface BoardPreviewEntry {
13
+ /** Human tab label; when absent, derive one with `previewLabel()`. */
14
+ label?: string;
15
+ device_type?: DeviceType;
16
+ /** Notes wide (note_array only). */
17
+ notes_wide?: number;
18
+ /** Notes tall (note_array only). */
19
+ notes_tall?: number;
20
+ rows: string[];
21
+ }
22
+ /** Localized shape names. `noteArray` may contain `{w}` and `{h}` placeholders. */
23
+ export interface PreviewShapeLabels {
24
+ flagship: string;
25
+ note: string;
26
+ noteArray: string;
27
+ }
28
+ export declare const DEFAULT_SHAPE_LABELS: PreviewShapeLabels;
29
+ /** Tab label for a preview: its declared label, or one derived from the shape. */
30
+ export declare function previewLabel(preview: BoardPreviewEntry, labels?: PreviewShapeLabels): string;
31
+ /**
32
+ * Tab labels for a whole preview list, numbering repeats so every tab has a
33
+ * distinct accessible name — authors may declare several previews of the same
34
+ * shape ("Flagship" twice).
35
+ */
36
+ export declare function previewLabels(previews: BoardPreviewEntry[], labels?: PreviewShapeLabels): string[];
37
+ /** The newline-joined message `StaticBoardDisplay` renders. */
38
+ export declare function previewMessage(preview: BoardPreviewEntry): string;
@@ -0,0 +1,23 @@
1
+ //#region src/lib/board-previews.ts
2
+ var e = {
3
+ flagship: "Flagship",
4
+ note: "Note",
5
+ noteArray: "Note Array {w}×{h}"
6
+ };
7
+ function t(t, n = e) {
8
+ return t.label ? t.label : t.device_type === "note_array" ? n.noteArray.replace("{w}", String(t.notes_wide ?? 1)).replace("{h}", String(t.notes_tall ?? 1)) : t.device_type === "note" ? n.note : n.flagship;
9
+ }
10
+ function n(e, n) {
11
+ let r = /* @__PURE__ */ new Map();
12
+ return e.map((e) => {
13
+ let i = t(e, n), a = r.get(i) ?? 0;
14
+ return r.set(i, a + 1), a > 0 ? `${i} ${a + 1}` : i;
15
+ });
16
+ }
17
+ function r(e) {
18
+ return e.rows.join("\n");
19
+ }
20
+ //#endregion
21
+ export { e as DEFAULT_SHAPE_LABELS, t as previewLabel, n as previewLabels, r as previewMessage };
22
+
23
+ //# sourceMappingURL=board-previews.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"board-previews.js","names":[],"sources":["../../src/lib/board-previews.ts"],"mappings":";AA+BA,IAAa,IAA2C;CACtD,UAAU;CACV,MAAM;CACN,WAAW;AACb;AAGA,SAAgB,EAAa,GAA4B,IAA6B,GAA8B;CAOlH,OANI,EAAQ,QAAc,EAAQ,QAC9B,EAAQ,gBAAgB,eACnB,EAAO,UACX,QAAQ,OAAO,OAAO,EAAQ,cAAc,CAAC,CAAC,CAAC,CAC/C,QAAQ,OAAO,OAAO,EAAQ,cAAc,CAAC,CAAC,IAE5C,EAAQ,gBAAgB,SAAS,EAAO,OAAO,EAAO;AAC/D;AAOA,SAAgB,EAAc,GAA+B,GAAuC;CAClG,IAAM,oBAAO,IAAI,IAAoB;CACrC,OAAO,EAAS,KAAK,MAAY;EAC/B,IAAM,IAAO,EAAa,GAAS,CAAM,GACnC,IAAS,EAAK,IAAI,CAAI,KAAK;EAEjC,OADA,EAAK,IAAI,GAAM,IAAS,CAAC,GAClB,IAAS,IAAI,GAAG,EAAK,GAAG,IAAS,MAAM;CAChD,CAAC;AACH;AAGA,SAAgB,EAAe,GAAoC;CACjE,OAAO,EAAQ,KAAK,KAAK,IAAI;AAC/B"}
package/dist/theme.css CHANGED
@@ -36,15 +36,16 @@
36
36
  --color-sidebar-border: var(--sidebar-border);
37
37
  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
38
38
  --color-sidebar-accent: var(--sidebar-accent);
39
- --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
40
- --color-sidebar-primary: var(--sidebar-primary);
41
39
  --color-sidebar-foreground: var(--sidebar-foreground);
42
40
  --color-sidebar: var(--sidebar);
43
- --color-chart-5: var(--chart-5);
44
- --color-chart-4: var(--chart-4);
45
- --color-chart-3: var(--chart-3);
46
- --color-chart-2: var(--chart-2);
47
- --color-chart-1: var(--chart-1);
41
+ /* NOTE (#173): --chart-1..5 and --sidebar-primary(-foreground) used to be
42
+ exported here. They were stock shadcn leftovers, consumed by nothing in
43
+ this package, and carried 137-157 degree hue shifts between light and
44
+ dark — so a chart adopting them would silently re-encode its series on a
45
+ theme toggle. Deleted rather than redesigned: a design system should not
46
+ ship a palette it has not designed. If a data-viz component ever lands,
47
+ add a hue-STABLE five-series ramp (lightness varies per theme, hue does
48
+ not) plus a story that proves both themes. */
48
49
  --color-ring: var(--ring);
49
50
  --color-input: var(--input);
50
51
  --color-border: var(--border);
@@ -86,6 +87,23 @@
86
87
  --color-tag-formula: var(--tag-formula);
87
88
  --color-tag-formula-foreground: var(--tag-formula-foreground);
88
89
 
90
+ /* Plugin category accents (PluginCategoryBadge) — one hue per category so a
91
+ plugin reads the same on the marketplace and in the docs directory. */
92
+ --color-category-weather: var(--category-weather);
93
+ --color-category-weather-foreground: var(--category-weather-foreground);
94
+ --color-category-transit: var(--category-transit);
95
+ --color-category-transit-foreground: var(--category-transit-foreground);
96
+ --color-category-data: var(--category-data);
97
+ --color-category-data-foreground: var(--category-data-foreground);
98
+ --color-category-entertainment: var(--category-entertainment);
99
+ --color-category-entertainment-foreground: var(--category-entertainment-foreground);
100
+ --color-category-art: var(--category-art);
101
+ --color-category-art-foreground: var(--category-art-foreground);
102
+ --color-category-utility: var(--category-utility);
103
+ --color-category-utility-foreground: var(--category-utility-foreground);
104
+ --color-category-home: var(--category-home);
105
+ --color-category-home-foreground: var(--category-home-foreground);
106
+
89
107
  /* Overlay */
90
108
  --color-overlay: var(--overlay);
91
109
 
@@ -147,6 +165,12 @@
147
165
  owned by the stylesheet, not a build-time JS wrapper. */
148
166
  --font-geist-sans: "Geist Variable", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
149
167
  --font-geist-mono: "Geist Mono Variable", ui-monospace, SFMono-Regular, "SF Mono", Monaco, Menlo, monospace;
168
+ /* Tell the UA which form-control palette to paint (#160). Without this,
169
+ every browser-drawn widget — accent-color'd checkboxes, file/date/number
170
+ chrome, scrollbars, autofill — renders in LIGHT chrome, which on the dark
171
+ default theme makes an unchecked Checkbox read as a filled/checked box.
172
+ Declared next to the palette it belongs to, once per theme. */
173
+ color-scheme: light;
150
174
  --radius: 0.625rem;
151
175
  --background: oklch(0.985 0 0);
152
176
  --foreground: oklch(0.145 0 0);
@@ -154,6 +178,17 @@
154
178
  --card-foreground: oklch(0.145 0 0);
155
179
  --popover: oklch(1 0 0);
156
180
  --popover-foreground: oklch(0.145 0 0);
181
+ /* ============================================================
182
+ * INVARIANT — "one token means 'this control is on'" (#156/#159).
183
+ * --primary is the single pigment that says a control is the primary
184
+ * action / is selected / is focused. --ring and --sidebar-ring are
185
+ * deliberately set to the SAME value in both themes so a focused
186
+ * control and an engaged control read as one idea. They are the only
187
+ * intentional value aliases in this file; do not "de-duplicate" them
188
+ * into separate hues, and do not let --primary drift to a neutral —
189
+ * that is what made the dark theme brandless (see .dark below).
190
+ * --primary carries the FiestaBoard warm orange in BOTH themes.
191
+ * ============================================================ */
157
192
  --primary: oklch(0.52 0.16 60);
158
193
  --primary-foreground: oklch(0.985 0 0);
159
194
  --secondary: oklch(0.96 0 0);
@@ -165,26 +200,48 @@
165
200
  --accent-foreground: oklch(0.205 0 0);
166
201
  --destructive: oklch(0.43 0.245 27.325);
167
202
  --destructive-foreground: oklch(0.985 0 0);
203
+ /* ============================================================
204
+ * --border vs --input: TWO DIFFERENT INTENTS, NOT ONE VALUE (#161).
205
+ *
206
+ * --border is DECORATION: table rules, card edges, dividers, list
207
+ * separators. It separates content that is already legible on its
208
+ * own, so WCAG 2.2 SC 1.4.11 does not apply and it is deliberately
209
+ * allowed to stay whisper-subtle (~1.3:1). Raising it to 3:1 would
210
+ * turn every card and table into a wireframe.
211
+ *
212
+ * --input is the CONTROL BOUNDARY: Input, Textarea, Select trigger and
213
+ * Checkbox are `bg-transparent`, so this 1px edge is the ONLY thing
214
+ * that says "a control lives here". That is exactly the "visual
215
+ * information required to identify a user interface component" of
216
+ * SC 1.4.11, so it MUST clear 3:1 against BOTH --background and
217
+ * --card (a field can sit on either surface).
218
+ *
219
+ * Derived from --foreground as an alpha fraction so the boundary
220
+ * tracks the text color in every theme and season instead of being a
221
+ * hand-tuned grey per palette. Percentages are the smallest that clear
222
+ * 3:1 on both surfaces with headroom (light 48% -> 3.45/3.48,
223
+ * dark 38% -> 3.37/3.44); they differ because contrast ramps faster
224
+ * for light-on-dark than dark-on-light.
225
+ *
226
+ * DO NOT collapse these two tokens into one value in either direction.
227
+ * ============================================================ */
168
228
  --border: oklch(0.905 0 0);
169
- --input: oklch(0.885 0 0);
170
- --ring: oklch(0.52 0.16 60);
171
- --chart-1: oklch(0.646 0.222 41.116);
172
- --chart-2: oklch(0.6 0.118 184.704);
173
- --chart-3: oklch(0.398 0.07 227.392);
174
- --chart-4: oklch(0.65 0.189 84.429);
175
- --chart-5: oklch(0.65 0.188 70.08);
229
+ --input: color-mix(in oklch, var(--foreground) 48%, transparent);
230
+ --ring: oklch(0.52 0.16 60); /* = --primary, see the invariant above */
176
231
  --sidebar: oklch(0.975 0 0);
177
232
  --sidebar-foreground: oklch(0.145 0 0);
178
- --sidebar-primary: oklch(0.52 0.16 60);
179
- --sidebar-primary-foreground: oklch(0.985 0 0);
180
233
  --sidebar-accent: oklch(0.92 0 0);
181
234
  --sidebar-accent-foreground: oklch(0.205 0 0);
182
235
  --sidebar-border: oklch(0.915 0 0);
183
- --sidebar-ring: oklch(0.52 0.16 60);
236
+ --sidebar-ring: oklch(0.52 0.16 60); /* = --primary, see the invariant above */
184
237
  --info: oklch(0.45 0.15 250);
185
238
  --info-foreground: oklch(0.985 0 0);
186
239
  --success: oklch(0.4 0.16 150);
187
240
  --success-foreground: oklch(0.985 0 0);
241
+ /* --warning must never collide with --brand (#157). In light they share a
242
+ near hue (70 vs 75) but are pulled apart by lightness — 1.61:1 between
243
+ them — so caution still reads as caution next to a brand accent. The
244
+ dark pair is separated on hue instead; see .dark. */
188
245
  --warning: oklch(0.45 0.16 70);
189
246
  --warning-foreground: oklch(0.985 0 0);
190
247
  --tag-variable: oklch(0.45 0.2 280);
@@ -193,6 +250,20 @@
193
250
  --tag-success-foreground: oklch(0.35 0.1 160);
194
251
  --tag-formula: oklch(0.45 0.15 80);
195
252
  --tag-formula-foreground: oklch(0.35 0.1 80);
253
+ --category-weather: oklch(0.55 0.14 245);
254
+ --category-weather-foreground: oklch(0.42 0.11 245);
255
+ --category-transit: oklch(0.55 0.15 145);
256
+ --category-transit-foreground: oklch(0.42 0.12 145);
257
+ --category-data: oklch(0.55 0.16 305);
258
+ --category-data-foreground: oklch(0.42 0.13 305);
259
+ --category-entertainment: oklch(0.55 0.14 75);
260
+ --category-entertainment-foreground: oklch(0.42 0.11 75);
261
+ --category-art: oklch(0.55 0.16 30);
262
+ --category-art-foreground: oklch(0.42 0.13 30);
263
+ --category-utility: oklch(0.55 0.03 255);
264
+ --category-utility-foreground: oklch(0.42 0.03 255);
265
+ --category-home: oklch(0.55 0.12 180);
266
+ --category-home-foreground: oklch(0.42 0.1 180);
196
267
  --overlay: oklch(0 0 0 / 80%);
197
268
  /* Brand accent - darkened for WCAG 2.2 AA (≥4.5:1 on light bg) */
198
269
  --brand: oklch(0.56 0.15 75);
@@ -348,14 +419,25 @@
348
419
  }
349
420
 
350
421
  .dark {
422
+ color-scheme: dark; /* see the note in :root (#160) */
351
423
  --background: oklch(0.13 0 0);
352
424
  --foreground: oklch(0.985 0 0);
353
425
  --card: oklch(0.16 0 0);
354
426
  --card-foreground: oklch(0.985 0 0);
355
427
  --popover: oklch(0.16 0 0);
356
428
  --popover-foreground: oklch(0.985 0 0);
357
- --primary: oklch(0.922 0 0);
358
- --primary-foreground: oklch(0.205 0 0);
429
+ /* Brand-bearing primary (#156). This used to be oklch(0.922 0 0) — stock
430
+ shadcn's achromatic dark primary, never re-designed when the FiestaBoard
431
+ palette landed. Dark is the DEFAULT theme, so every default Button,
432
+ Badge, Switch track, Slider indicator and focus ring shipped with zero
433
+ brand hue. Same warm orange as :root, lifted in lightness for a dark
434
+ surface (light 0.52 -> dark 0.72) and held on the same hue family.
435
+ Upholds the "one token means 'this control is on'" invariant declared in
436
+ :root: --ring and --sidebar-ring below repeat this exact value. */
437
+ --primary: oklch(0.72 0.15 62);
438
+ /* Near-black on-accent foreground, matching every other dark accent
439
+ foreground in this block (destructive/info/success/warning all 0.15). */
440
+ --primary-foreground: oklch(0.15 0 0);
359
441
  --secondary: oklch(0.215 0 0);
360
442
  --secondary-foreground: oklch(0.985 0 0);
361
443
  --muted: oklch(0.2 0 0);
@@ -364,27 +446,30 @@
364
446
  --accent-foreground: oklch(0.985 0 0);
365
447
  --destructive: oklch(0.8 0.191 22.216);
366
448
  --destructive-foreground: oklch(0.15 0 0);
449
+ /* --border stays decorative-subtle; --input is the 3:1 control boundary.
450
+ Full rationale in the :root block — do not merge the two. */
367
451
  --border: oklch(1 0 0 / 10%);
368
- --input: oklch(1 0 0 / 14%);
369
- --ring: oklch(0.55 0 0);
370
- --chart-1: oklch(0.55 0.243 264.376);
371
- --chart-2: oklch(0.696 0.17 162.48);
372
- --chart-3: oklch(0.769 0.188 70.08);
373
- --chart-4: oklch(0.627 0.265 303.9);
374
- --chart-5: oklch(0.645 0.246 16.439);
452
+ --input: color-mix(in oklch, var(--foreground) 38%, transparent);
453
+ --ring: oklch(0.72 0.15 62); /* = --primary, see the invariant in :root */
375
454
  --sidebar: oklch(0.145 0 0);
376
455
  --sidebar-foreground: oklch(0.985 0 0);
377
- --sidebar-primary: oklch(0.488 0.243 264.376);
378
- --sidebar-primary-foreground: oklch(0.985 0 0);
379
456
  --sidebar-accent: oklch(0.28 0 0);
380
457
  --sidebar-accent-foreground: oklch(0.985 0 0);
381
458
  --sidebar-border: oklch(1 0 0 / 8%);
382
- --sidebar-ring: oklch(0.55 0 0);
459
+ --sidebar-ring: oklch(0.72 0.15 62); /* = --primary, see the invariant in :root */
383
460
  --info: oklch(0.75 0.18 250);
384
461
  --info-foreground: oklch(0.15 0 0);
385
462
  --success: oklch(0.68 0.18 145);
386
463
  --success-foreground: oklch(0.15 0 0);
387
- --warning: oklch(0.78 0.14 75);
464
+ /* Amber caution, deliberately OFF the brand hue (#157). This was
465
+ oklch(0.78 0.14 75) — byte-identical to --brand below, so "this is
466
+ FiestaBoard" and "be careful" rendered as the same pigment and neither
467
+ could be learned. Moved +20deg toward yellow and +0.08 lighter, which
468
+ puts 1.34:1 of luminance and a clear hue break between the two. The
469
+ 22deg drift from the light --warning (hue 70) is intentional and small:
470
+ light must separate warning from brand on LIGHTNESS (it has to stay dark
471
+ enough to be readable text on white), dark separates them on HUE. */
472
+ --warning: oklch(0.86 0.16 95);
388
473
  --warning-foreground: oklch(0.15 0 0);
389
474
  --tag-variable: oklch(0.65 0.15 280);
390
475
  --tag-variable-foreground: oklch(0.85 0.08 280);
@@ -392,12 +477,36 @@
392
477
  --tag-success-foreground: oklch(0.85 0.08 160);
393
478
  --tag-formula: oklch(0.65 0.15 80);
394
479
  --tag-formula-foreground: oklch(0.85 0.08 80);
480
+ --category-weather: oklch(0.68 0.13 245);
481
+ --category-weather-foreground: oklch(0.85 0.08 245);
482
+ --category-transit: oklch(0.68 0.14 145);
483
+ --category-transit-foreground: oklch(0.85 0.09 145);
484
+ --category-data: oklch(0.68 0.14 305);
485
+ --category-data-foreground: oklch(0.85 0.09 305);
486
+ --category-entertainment: oklch(0.68 0.13 75);
487
+ --category-entertainment-foreground: oklch(0.85 0.08 75);
488
+ --category-art: oklch(0.68 0.14 30);
489
+ --category-art-foreground: oklch(0.85 0.09 30);
490
+ --category-utility: oklch(0.68 0.03 255);
491
+ --category-utility-foreground: oklch(0.85 0.02 255);
492
+ --category-home: oklch(0.68 0.11 180);
493
+ --category-home-foreground: oklch(0.85 0.07 180);
395
494
  --overlay: oklch(0 0 0 / 80%);
396
495
  --brand: oklch(0.78 0.14 75);
397
496
  --brand-emphasis: oklch(0.55 0.16 60);
398
497
  --brand-foreground: oklch(1 0 0);
399
- /* Navigation active state — indigo palette (lighter for dark mode) */
400
- --nav-active: oklch(0.59 0.19 265); /* indigo-500 */
498
+ /* Navigation active state — indigo palette.
499
+ NOT "lighter for dark mode" (#162): --nav-active is the solid FALLBACK
500
+ fill for the active nav pill, used whenever the frosted-glass treatment
501
+ can't run — no backdrop-filter support, prefers-reduced-transparency, and
502
+ prefers-contrast: more. At the old indigo-500 (0.59) white nav labels sat
503
+ at 4.26:1, i.e. BELOW AA for the 14px/600 labels Sidebar renders, which
504
+ made the explicit high-contrast path less accessible than the default
505
+ one. Darkened to clear AAA (7.47:1) instead. Kept as light as AAA allows
506
+ rather than the 0.42 the audit suggested (8.91:1), because darkening the
507
+ pill trades text contrast against the pill's own separation from the
508
+ surface behind it — see the fallback rules near .nav-active. */
509
+ --nav-active: oklch(0.46 0.19 265);
401
510
  --nav-active-foreground: oklch(1 0 0); /* white */
402
511
  --nav-active-hover: oklch(0.59 0.19 265 / 20%); /* indigo tint hover */
403
512
  /* Fiesta gradient palette — lighter for visibility on dark card backgrounds */
@@ -1027,6 +1136,15 @@ html[data-board-switch="backward"]::view-transition-new(board-page) {
1027
1136
  background: var(--nav-active);
1028
1137
  color: var(--nav-active-foreground);
1029
1138
  }
1139
+ /* The dark --nav-active fill is deliberately dark enough for AAA white labels
1140
+ (#162), which necessarily brings it closer in luminance to the near-black
1141
+ surfaces it sits on. A 1px light inset keeps the pill's OWN boundary
1142
+ identifiable (SC 1.4.11 — "state" of a component) without reintroducing the
1143
+ transparency these fallbacks exist to avoid. Light mode needs no help: its
1144
+ pill already sits 7.6:1 off --sidebar. */
1145
+ .dark .nav-active {
1146
+ box-shadow: inset 0 0 0 1px oklch(1 0 0 / 0.22);
1147
+ }
1030
1148
  .nav-active-hover:hover {
1031
1149
  background: var(--nav-active-hover);
1032
1150
  }
@@ -1071,7 +1189,10 @@ html[data-board-switch="backward"]::view-transition-new(board-page) {
1071
1189
  .sidebar-gradient .nav-active,
1072
1190
  .sidebar-gradient-horizontal .nav-active {
1073
1191
  background: var(--nav-active);
1074
- box-shadow: none;
1192
+ /* Keep the hairline (was `none`): the solid fill is only ~1.2-1.7:1 off
1193
+ the gradient stops behind it in either theme, so the ring is what makes
1194
+ the pill an identifiable shape (#162). */
1195
+ box-shadow: inset 0 0 0 1px oklch(1 0 0 / 0.22);
1075
1196
  backdrop-filter: none;
1076
1197
  -webkit-backdrop-filter: none;
1077
1198
  }
@@ -1087,7 +1208,8 @@ html[data-board-switch="backward"]::view-transition-new(board-page) {
1087
1208
  .sidebar-gradient .nav-active,
1088
1209
  .sidebar-gradient-horizontal .nav-active {
1089
1210
  background: var(--nav-active);
1090
- box-shadow: none;
1211
+ /* Same reasoning as the @supports fallback above (#162). */
1212
+ box-shadow: inset 0 0 0 1px oklch(1 0 0 / 0.22);
1091
1213
  backdrop-filter: none;
1092
1214
  -webkit-backdrop-filter: none;
1093
1215
  }
@@ -1099,6 +1221,10 @@ html[data-board-switch="backward"]::view-transition-new(board-page) {
1099
1221
  }
1100
1222
  }
1101
1223
 
1224
+ /* This branch is where the old fallback was most wrong: users who explicitly
1225
+ asked for MORE contrast got 4.26:1 in dark, below the 4.5:1 the default
1226
+ frosted path achieved. --nav-active now targets AAA (7.47:1) so this path
1227
+ is deliberately the most legible one, not the least (#162). */
1102
1228
  @media (prefers-contrast: more) {
1103
1229
  .sidebar-gradient .nav-active,
1104
1230
  .sidebar-gradient-horizontal .nav-active {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fiestaboard/ui",
3
- "version": "1.6.5",
3
+ "version": "2.0.0",
4
4
  "description": "FiestaUI — the FiestaBoard design system. React components on Base UI with Tailwind v4 design tokens.",
5
5
  "license": "MIT",
6
6
  "repository": {