@charcuterie/ui 4.0.0 → 4.1.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.
Files changed (45) hide show
  1. package/dist/ActionTiles/ActionTiles.d.ts +66 -8
  2. package/dist/ActionTiles/ActionTiles.d.ts.map +1 -1
  3. package/dist/ActionTiles/ActionTiles.js +88 -11
  4. package/dist/ActionTiles/ActionTiles.js.map +1 -1
  5. package/dist/PortraitTiles/PortraitAvatar.d.ts +21 -0
  6. package/dist/PortraitTiles/PortraitAvatar.d.ts.map +1 -0
  7. package/dist/PortraitTiles/PortraitAvatar.js +29 -0
  8. package/dist/PortraitTiles/PortraitAvatar.js.map +1 -0
  9. package/dist/PortraitTiles/PortraitTiles.d.ts +162 -0
  10. package/dist/PortraitTiles/PortraitTiles.d.ts.map +1 -0
  11. package/dist/PortraitTiles/PortraitTiles.js +263 -0
  12. package/dist/PortraitTiles/PortraitTiles.js.map +1 -0
  13. package/dist/PortraitTiles/portraitColour.d.ts +49 -0
  14. package/dist/PortraitTiles/portraitColour.d.ts.map +1 -0
  15. package/dist/PortraitTiles/portraitColour.js +55 -0
  16. package/dist/PortraitTiles/portraitColour.js.map +1 -0
  17. package/dist/categoricalStyles.d.ts +27 -0
  18. package/dist/categoricalStyles.d.ts.map +1 -1
  19. package/dist/categoricalStyles.js +49 -0
  20. package/dist/categoricalStyles.js.map +1 -1
  21. package/dist/index.d.ts +4 -2
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +2 -1
  24. package/dist/index.js.map +1 -1
  25. package/dist/tileStyles.d.ts +15 -0
  26. package/dist/tileStyles.d.ts.map +1 -1
  27. package/dist/tileStyles.js +26 -1
  28. package/dist/tileStyles.js.map +1 -1
  29. package/package.json +2 -2
  30. package/src/ActionTiles/ActionTiles.mdx +64 -6
  31. package/src/ActionTiles/ActionTiles.stories.tsx +107 -2
  32. package/src/ActionTiles/ActionTiles.test.tsx +205 -5
  33. package/src/ActionTiles/ActionTiles.tsx +274 -110
  34. package/src/PortraitTiles/PortraitAvatar.tsx +49 -0
  35. package/src/PortraitTiles/PortraitTiles.mdx +153 -0
  36. package/src/PortraitTiles/PortraitTiles.stories.tsx +403 -0
  37. package/src/PortraitTiles/PortraitTiles.test.tsx +399 -0
  38. package/src/PortraitTiles/PortraitTiles.tsx +577 -0
  39. package/src/PortraitTiles/portraitColour.test.ts +39 -0
  40. package/src/PortraitTiles/portraitColour.ts +67 -0
  41. package/src/RadioGroup/RadioGroup.stories.tsx +39 -0
  42. package/src/categoricalStyles.ts +57 -0
  43. package/src/index.ts +9 -0
  44. package/src/sourceRules.test.ts +15 -1
  45. package/src/tileStyles.ts +30 -1
@@ -1,6 +1,20 @@
1
- import type { ControlSize } from "@charcuterie/tokens";
1
+ import type { CategoricalIndex, ControlSize } from "@charcuterie/tokens";
2
2
  import type { ReactNode } from "react";
3
3
  export type ActionTileItem = {
4
+ /**
5
+ * Which of the ten hues this tile wears, overriding the one its
6
+ * position would have given it.
7
+ *
8
+ * Reach for it when a tile's colour means something outside the
9
+ * set — a destination that is red everywhere else in the app, a
10
+ * pair that must not drift apart when a third tile is inserted
11
+ * between them. An overridden tile keeps its hue and the rest go
12
+ * on walking the palette in order, so naming one does not force
13
+ * naming all of them.
14
+ *
15
+ * Ignored when the set is `accent="none"`.
16
+ */
17
+ categorical?: CategoricalIndex;
4
18
  /**
5
19
  * A line of help under the name. Drawn one step down the type ramp
6
20
  * in `content-muted`, **inside** the tile — so `getByRole` computes
@@ -20,10 +34,15 @@ export type ActionTileItem = {
20
34
  */
21
35
  href?: string;
22
36
  /**
23
- * Leading content, above the name. A `Badge`, an app's own icon, a
24
- * count.
37
+ * Leading content, drawn **beside** the name rather than above it.
38
+ * A glyph, a `Badge`, a count.
25
39
  *
26
- * `aria-hidden` by construction: the name it sits above is inside
40
+ * Takes the tile's hue, so an app passes a `stroke="currentColor"`
41
+ * path and states no colour of its own. Size is the app's: around
42
+ * 22px at `md` and 30px at `lg` reads the way the fleet's
43
+ * hand-rolled tool cards did.
44
+ *
45
+ * `aria-hidden` by construction: the name it sits beside is inside
27
46
  * the same tile, so an announced icon is the label read twice.
28
47
  */
29
48
  icon?: ReactNode;
@@ -40,7 +59,22 @@ export type ActionTileItem = {
40
59
  */
41
60
  value: string;
42
61
  };
62
+ /**
63
+ * Where a tile's colour comes from.
64
+ *
65
+ * - `auto` — the set walks the ten-hue categorical palette in
66
+ * order, so eight tiles get eight colours and no call site does
67
+ * any work. An item may still name its own with `categorical`.
68
+ * - `none` — no bar, no hue, and the neutral `border-strong`
69
+ * hover every other box uses.
70
+ */
71
+ export type ActionTilesAccent = "auto" | "none";
43
72
  export type ActionTilesProps = {
73
+ /**
74
+ * Whether the set colours itself. `auto` by default — see
75
+ * {@link ActionTilesAccent}.
76
+ */
77
+ accent?: ActionTilesAccent;
44
78
  className?: string;
45
79
  items: readonly ActionTileItem[];
46
80
  /** The set's accessible name. Required. */
@@ -59,9 +93,9 @@ export type ActionTilesProps = {
59
93
  size?: ControlSize;
60
94
  };
61
95
  /**
62
- * A set of ACTIONS drawn as tiles — a bordered card carrying a name
63
- * and a line of help, in a grid that gains columns with its
64
- * container.
96
+ * A set of ACTIONS drawn as tiles — a bordered card carrying a
97
+ * colour bar, an icon beside its name, and a line of help, in a
98
+ * grid that gains columns with its container.
65
99
  *
66
100
  * This is `RadioGroup`'s `itemShape="tile"` with the radio taken
67
101
  * away. The
@@ -90,6 +124,30 @@ export type ActionTilesProps = {
90
124
  * why QueuePilot's "Queue type" chooser is here and its Tonight
91
125
  * activity filter is not.
92
126
  *
127
+ * ### The colour is the point, and it is not decoration
128
+ *
129
+ * The first version of this component was neutral, and the owner's
130
+ * verdict on it was *"very boring, not colorful"*. Three apps had
131
+ * already answered the question themselves — mux-magic, Gallery
132
+ * Downloader and points-market all colour their tiles by hand — so
133
+ * the paint is now the library's and every one of them can stop.
134
+ *
135
+ * A tile wears a **bar down its leading edge**, drawn by `Card`'s
136
+ * accent-edge pseudo-element so a tile and a card on the same page
137
+ * are the same bar rather than two that nearly match. Its icon takes
138
+ * the same hue, and the box hovers in it too. The hue comes from the
139
+ * ten-wide categorical palette, taken **in order**, which is why a
140
+ * set of eight needs no colour props at all. See
141
+ * [the tile paint record](../../../../docs/decisions/2026-09-02-an-action-tile-is-coloured-and-the-icon-sits-beside-the-name.md)
142
+ * for the four other paints that were drawn and rejected.
143
+ *
144
+ * `categorical` and not `intent`, for the reason
145
+ * [`cardAccentEdge.ts`](../Card/cardAccentEdge.ts) gives: an intent
146
+ * is a *claim* — `danger` says what happens if you press the thing —
147
+ * and "Builder" beside "Jobs" is making no claim about either. Ten
148
+ * numbered hues say only "these are different from each other",
149
+ * which is exactly what a tile set means.
150
+ *
93
151
  * ### Why this is not a `Button` with a `className`
94
152
  *
95
153
  * A `Button` is sized by `h-(--control-height-md)` and carries **no
@@ -111,5 +169,5 @@ export type ActionTilesProps = {
111
169
  * true, so imposing that model would only cost a keyboard user the
112
170
  * ability to Tab to the second tile.
113
171
  */
114
- export declare const ActionTiles: ({ className, items, label, minTileInlineSize, newTabLabel, onChoose, size, }: ActionTilesProps) => ReactNode;
172
+ export declare const ActionTiles: ({ accent, className, items, label, minTileInlineSize, newTabLabel, onChoose, size, }: ActionTilesProps) => ReactNode;
115
173
  //# sourceMappingURL=ActionTiles.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ActionTiles.d.ts","sourceRoot":"","sources":["../../src/ActionTiles/ActionTiles.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,KAAK,EAAiB,SAAS,EAAE,MAAM,OAAO,CAAA;AAiBrD,MAAM,MAAM,cAAc,GAAG;IAC3B;;;;;OAKG;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB;;;;;;;;;OASG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,uEAAuE;IACvE,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,KAAK,EAAE,SAAS,CAAA;IAChB;;;;;;OAMG;IACH,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,SAAS,cAAc,EAAE,CAAA;IAChC,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAA;IACb;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAClC,8CAA8C;IAC9C,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,IAAI,CAAC,EAAE,WAAW,CAAA;CACnB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AACH,eAAO,MAAM,WAAW,GAAI,8EAQzB,gBAAgB,KAAG,SAwJrB,CAAA"}
1
+ {"version":3,"file":"ActionTiles.d.ts","sourceRoot":"","sources":["../../src/ActionTiles/ActionTiles.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,WAAW,EACZ,MAAM,qBAAqB,CAAA;AAK5B,OAAO,KAAK,EAAiB,SAAS,EAAE,MAAM,OAAO,CAAA;AAwBrD,MAAM,MAAM,cAAc,GAAG;IAC3B;;;;;;;;;;;;OAYG;IACH,WAAW,CAAC,EAAE,gBAAgB,CAAA;IAC9B;;;;;OAKG;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB;;;;;;;;;OASG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;;;;;;;;;OAWG;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,uEAAuE;IACvE,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,KAAK,EAAE,SAAS,CAAA;IAChB;;;;;;OAMG;IACH,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,MAAM,CAAA;AAE/C,MAAM,MAAM,gBAAgB,GAAG;IAC7B;;;OAGG;IACH,MAAM,CAAC,EAAE,iBAAiB,CAAA;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,SAAS,cAAc,EAAE,CAAA;IAChC,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAA;IACb;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAClC,8CAA8C;IAC9C,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,IAAI,CAAC,EAAE,WAAW,CAAA;CACnB,CAAA;AA4CD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4EG;AACH,eAAO,MAAM,WAAW,GAAI,sFASzB,gBAAgB,KAAG,SAwMrB,CAAA"}
@@ -1,14 +1,50 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { CATEGORICAL_INDEX_COUNT, CATEGORICAL_INDEXES, } from "@charcuterie/tokens";
3
+ import { getAccentEdgeClassName } from "../Card/cardAccentEdge.js";
4
+ import { CATEGORICAL_CONTENT_CLASS, CATEGORICAL_HOVER_BORDER_CLASS, CATEGORICAL_HOVER_CLASS, } from "../categoricalStyles.js";
2
5
  import { FOCUS_RING_CLASS } from "../intentStyles.js";
3
6
  import { useRouterLink } from "../RouterLink/RouterLinkProvider.js";
4
7
  import { getIsRoutedHref } from "../RouterLink/routerLink.js";
5
- import { TILE_BOX_CLASS, TILE_COLUMNS_CLASS, TILE_GAP_CLASS, TILE_HINT_TEXT_CLASS, TILE_MIN_INLINE_SIZE_PROPERTY, TILE_PADDING_CLASS, TILE_TEXT_SIZE_CLASS, } from "../tileStyles.js";
8
+ import { TILE_ACCENT_EDGE_PADDING_CLASS, TILE_BOX_CLASS, TILE_COLUMNS_CLASS, TILE_GAP_CLASS, TILE_HINT_TEXT_CLASS, TILE_MIN_INLINE_SIZE_PROPERTY, TILE_PADDING_CLASS, TILE_TEXT_SIZE_CLASS, } from "../tileStyles.js";
6
9
  import { toClassName } from "../toClassName.js";
7
10
  import { VisuallyHidden } from "../VisuallyHidden/VisuallyHidden.js";
8
11
  /**
9
- * A set of ACTIONS drawn as tiles — a bordered card carrying a name
10
- * and a line of help, in a grid that gains columns with its
11
- * container.
12
+ * The gap between the icon and the name it sits beside.
13
+ *
14
+ * Local to this component rather than in `tileStyles.ts`, because a
15
+ * radio tile's inner layout is a control beside a label and has no
16
+ * head row to space. The shared file owns the BOX; this is what goes
17
+ * in it.
18
+ */
19
+ const HEAD_GAP_CLASS = {
20
+ sm: "gap-2",
21
+ md: "gap-2.5",
22
+ lg: "gap-3",
23
+ };
24
+ /** The gap between the head row and the hint under it. */
25
+ const STACK_GAP_CLASS = {
26
+ sm: "gap-1",
27
+ md: "gap-1",
28
+ lg: "gap-2",
29
+ };
30
+ /**
31
+ * The hue a tile wears when it did not name one: its position in
32
+ * the set, wrapping at ten.
33
+ *
34
+ * Position rather than `getCategoricalIndex(value)`. That helper
35
+ * hashes a key so a row keeps its colour forever across a list
36
+ * nobody controls the order of — right for Docket's labels, wrong
37
+ * here, because a hash of two tile values can collide and hand a
38
+ * two-tile set the same colour twice. A tile set is short, ordered,
39
+ * and written out in the source; the eye reads it as a row, so the
40
+ * palette is walked as one.
41
+ */
42
+ const getTileCategorical = (item, position) => item.categorical ??
43
+ CATEGORICAL_INDEXES[position % CATEGORICAL_INDEX_COUNT];
44
+ /**
45
+ * A set of ACTIONS drawn as tiles — a bordered card carrying a
46
+ * colour bar, an icon beside its name, and a line of help, in a
47
+ * grid that gains columns with its container.
12
48
  *
13
49
  * This is `RadioGroup`'s `itemShape="tile"` with the radio taken
14
50
  * away. The
@@ -37,6 +73,30 @@ import { VisuallyHidden } from "../VisuallyHidden/VisuallyHidden.js";
37
73
  * why QueuePilot's "Queue type" chooser is here and its Tonight
38
74
  * activity filter is not.
39
75
  *
76
+ * ### The colour is the point, and it is not decoration
77
+ *
78
+ * The first version of this component was neutral, and the owner's
79
+ * verdict on it was *"very boring, not colorful"*. Three apps had
80
+ * already answered the question themselves — mux-magic, Gallery
81
+ * Downloader and points-market all colour their tiles by hand — so
82
+ * the paint is now the library's and every one of them can stop.
83
+ *
84
+ * A tile wears a **bar down its leading edge**, drawn by `Card`'s
85
+ * accent-edge pseudo-element so a tile and a card on the same page
86
+ * are the same bar rather than two that nearly match. Its icon takes
87
+ * the same hue, and the box hovers in it too. The hue comes from the
88
+ * ten-wide categorical palette, taken **in order**, which is why a
89
+ * set of eight needs no colour props at all. See
90
+ * [the tile paint record](../../../../docs/decisions/2026-09-02-an-action-tile-is-coloured-and-the-icon-sits-beside-the-name.md)
91
+ * for the four other paints that were drawn and rejected.
92
+ *
93
+ * `categorical` and not `intent`, for the reason
94
+ * [`cardAccentEdge.ts`](../Card/cardAccentEdge.ts) gives: an intent
95
+ * is a *claim* — `danger` says what happens if you press the thing —
96
+ * and "Builder" beside "Jobs" is making no claim about either. Ten
97
+ * numbered hues say only "these are different from each other",
98
+ * which is exactly what a tile set means.
99
+ *
40
100
  * ### Why this is not a `Button` with a `className`
41
101
  *
42
102
  * A `Button` is sized by `h-(--control-height-md)` and carries **no
@@ -58,11 +118,15 @@ import { VisuallyHidden } from "../VisuallyHidden/VisuallyHidden.js";
58
118
  * true, so imposing that model would only cost a keyboard user the
59
119
  * ability to Tab to the second tile.
60
120
  */
61
- export const ActionTiles = ({ className, items, label, minTileInlineSize = 200, newTabLabel = "(opens in a new tab)", onChoose, size = "md", }) => {
121
+ export const ActionTiles = ({ accent = "auto", className, items, label, minTileInlineSize = 200, newTabLabel = "(opens in a new tab)", onChoose, size = "md", }) => {
62
122
  const RouterLink = useRouterLink();
63
123
  return (_jsx("div", { "aria-label": label, className: toClassName("grid", TILE_COLUMNS_CLASS, TILE_GAP_CLASS[size], className), role: "group", style: {
64
124
  [TILE_MIN_INLINE_SIZE_PROPERTY]: `${minTileInlineSize}px`,
65
- }, children: items.map(({ hint, href, icon, isDisabled = false, isExternal = false, label: tileLabel, value, }) => {
125
+ }, children: items.map((item, position) => {
126
+ const { hint, href, icon, isDisabled = false, isExternal = false, label: tileLabel, value, } = item;
127
+ const categorical = accent === "none"
128
+ ? null
129
+ : getTileCategorical(item, position);
66
130
  const tileClassName = toClassName(
67
131
  // `flex`, not `inline-flex`: a grid item stretches to its
68
132
  // track, and an inline box would sit at max-content
@@ -71,15 +135,28 @@ export const ActionTiles = ({ className, items, label, minTileInlineSize = 200,
71
135
  // min-content width — one unbroken token would otherwise
72
136
  // set the track's floor and shove the grid out of its
73
137
  // container.
74
- "flex min-w-0 flex-col gap-0.5 text-start wrap-anywhere transition-colors duration-(--duration-fast) ease-standard", TILE_BOX_CLASS, TILE_PADDING_CLASS[size], TILE_TEXT_SIZE_CLASS[size],
138
+ "flex min-w-0 flex-col text-start wrap-anywhere transition-colors duration-(--duration-fast) ease-standard", STACK_GAP_CLASS[size], TILE_BOX_CLASS, TILE_PADDING_CLASS[size], TILE_TEXT_SIZE_CLASS[size],
139
+ // The bar is an overlay and takes no space in the box
140
+ // model, so the tile makes room for it or the text lands
141
+ // on top of it.
142
+ categorical === null
143
+ ? null
144
+ : toClassName(getAccentEdgeClassName({ categorical }), TILE_ACCENT_EDGE_PADDING_CLASS[size]),
75
145
  // Disabled turns the whole tile down with `opacity-60`,
76
146
  // the same family treatment as `Checkbox`, `Switch` and
77
147
  // the radio tile, rather than fading the border to a
78
- // token that disappeared on a pale theme.
148
+ // token that disappeared on a pale theme. It also drops
149
+ // every hover class: a tile that cannot be pressed must
150
+ // not light up under the pointer.
79
151
  isDisabled
80
152
  ? "cursor-not-allowed opacity-60"
81
- : "cursor-pointer hover:border-border-strong", FOCUS_RING_CLASS);
82
- const content = (_jsxs(_Fragment, { children: [icon === undefined ? null : (_jsx("span", { "aria-hidden": true, className: "flex", children: icon })), _jsx("span", { className: "font-semibold text-content-primary", children: tileLabel }), hint === undefined ? null : (_jsx("span", { className: toClassName("font-normal text-content-muted", TILE_HINT_TEXT_CLASS[size]), children: hint })), isExternal && href !== undefined ? (_jsx(VisuallyHidden, { children: newTabLabel })) : null] }));
153
+ : toClassName("cursor-pointer", categorical === null
154
+ ? "hover:border-border-strong"
155
+ : toClassName(CATEGORICAL_HOVER_BORDER_CLASS[categorical], CATEGORICAL_HOVER_CLASS[categorical]
156
+ .ghost)), FOCUS_RING_CLASS);
157
+ const content = (_jsxs(_Fragment, { children: [_jsxs("span", { className: toClassName("flex min-w-0 items-center", HEAD_GAP_CLASS[size]), children: [icon === undefined ? null : (_jsx("span", { "aria-hidden": true, className: toClassName("flex shrink-0", categorical === null
158
+ ? null
159
+ : CATEGORICAL_CONTENT_CLASS[categorical]), children: icon })), _jsx("span", { className: "min-w-0 font-semibold text-content-primary", children: tileLabel })] }), hint === undefined ? null : (_jsx("span", { className: toClassName("font-normal text-content-muted", TILE_HINT_TEXT_CLASS[size]), children: hint })), isExternal && href !== undefined ? (_jsx(VisuallyHidden, { children: newTabLabel })) : null] }));
83
160
  if (href !== undefined && !isDisabled) {
84
161
  // An external destination or a fragment is never the
85
162
  // router's — pushing `https://…` or `#credits` onto the
@@ -1 +1 @@
1
- {"version":3,"file":"ActionTiles.js","sourceRoot":"","sources":["../../src/ActionTiles/ActionTiles.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ActionTiles.js","sourceRoot":"","sources":["../../src/ActionTiles/ActionTiles.tsx"],"names":[],"mappings":";AAIA,OAAO,EACL,uBAAuB,EACvB,mBAAmB,GACpB,MAAM,qBAAqB,CAAA;AAG5B,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAA;AAClE,OAAO,EACL,yBAAyB,EACzB,8BAA8B,EAC9B,uBAAuB,GACxB,MAAM,yBAAyB,CAAA;AAChC,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,qCAAsC,CAAA;AACpE,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAA;AAC7D,OAAO,EACL,8BAA8B,EAC9B,cAAc,EACd,kBAAkB,EAClB,cAAc,EACd,oBAAoB,EACpB,6BAA6B,EAC7B,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,qCAAsC,CAAA;AAiGrE;;;;;;;GAOG;AACH,MAAM,cAAc,GAAgC;IAClD,EAAE,EAAE,OAAO;IACX,EAAE,EAAE,SAAS;IACb,EAAE,EAAE,OAAO;CACZ,CAAA;AAED,0DAA0D;AAC1D,MAAM,eAAe,GAAgC;IACnD,EAAE,EAAE,OAAO;IACX,EAAE,EAAE,OAAO;IACX,EAAE,EAAE,OAAO;CACZ,CAAA;AAED;;;;;;;;;;;GAWG;AACH,MAAM,kBAAkB,GAAG,CACzB,IAAoB,EACpB,QAAgB,EACE,EAAE,CACpB,IAAI,CAAC,WAAW;IACf,mBAAmB,CAClB,QAAQ,GAAG,uBAAuB,CACd,CAAA;AAExB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4EG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,EAC1B,MAAM,GAAG,MAAM,EACf,SAAS,EACT,KAAK,EACL,KAAK,EACL,iBAAiB,GAAG,GAAG,EACvB,WAAW,GAAG,sBAAsB,EACpC,QAAQ,EACR,IAAI,GAAG,IAAI,GACM,EAAa,EAAE;IAChC,MAAM,UAAU,GAAG,aAAa,EAAE,CAAA;IAElC,OAAO,CAEL,4BACc,KAAK,EACjB,SAAS,EAAE,WAAW,CACpB,MAAM,EACN,kBAAkB,EAClB,cAAc,CAAC,IAAI,CAAC,EACpB,SAAS,CACV,EACD,IAAI,EAAC,OAAO,EACZ,KAAK,EACH;YACE,CAAC,6BAA6B,CAAC,EAAE,GAAG,iBAAiB,IAAI;SACzC,YAGnB,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE;YAC5B,MAAM,EACJ,IAAI,EACJ,IAAI,EACJ,IAAI,EACJ,UAAU,GAAG,KAAK,EAClB,UAAU,GAAG,KAAK,EAClB,KAAK,EAAE,SAAS,EAChB,KAAK,GACN,GAAG,IAAI,CAAA;YAER,MAAM,WAAW,GACf,MAAM,KAAK,MAAM;gBACf,CAAC,CAAC,IAAI;gBACN,CAAC,CAAC,kBAAkB,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;YAExC,MAAM,aAAa,GAAG,WAAW;YAC/B,0DAA0D;YAC1D,oDAAoD;YACpD,mDAAmD;YACnD,iDAAiD;YACjD,yDAAyD;YACzD,sDAAsD;YACtD,aAAa;YACb,2GAA2G,EAC3G,eAAe,CAAC,IAAI,CAAC,EACrB,cAAc,EACd,kBAAkB,CAAC,IAAI,CAAC,EACxB,oBAAoB,CAAC,IAAI,CAAC;YAC1B,sDAAsD;YACtD,yDAAyD;YACzD,gBAAgB;YAChB,WAAW,KAAK,IAAI;gBAClB,CAAC,CAAC,IAAI;gBACN,CAAC,CAAC,WAAW,CACT,sBAAsB,CAAC,EAAE,WAAW,EAAE,CAAC,EACvC,8BAA8B,CAAC,IAAI,CAAC,CACrC;YACL,wDAAwD;YACxD,wDAAwD;YACxD,qDAAqD;YACrD,wDAAwD;YACxD,wDAAwD;YACxD,kCAAkC;YAClC,UAAU;gBACR,CAAC,CAAC,+BAA+B;gBACjC,CAAC,CAAC,WAAW,CACT,gBAAgB,EAChB,WAAW,KAAK,IAAI;oBAClB,CAAC,CAAC,4BAA4B;oBAC9B,CAAC,CAAC,WAAW,CACT,8BAA8B,CAC5B,WAAW,CACZ,EACD,uBAAuB,CAAC,WAAW,CAAC;yBACjC,KAAK,CACT,CACN,EACL,gBAAgB,CACjB,CAAA;YAED,MAAM,OAAO,GAAG,CACd,8BAME,gBACE,SAAS,EAAE,WAAW,CACpB,2BAA2B,EAC3B,cAAc,CAAC,IAAI,CAAC,CACrB,aAEA,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAC3B,oCAEE,SAAS,EAAE,WAAW,CACpB,eAAe,EACf,WAAW,KAAK,IAAI;oCAClB,CAAC,CAAC,IAAI;oCACN,CAAC,CAAC,yBAAyB,CACvB,WAAW,CACZ,CACN,YAEA,IAAI,GACA,CACR,EAED,eAAM,SAAS,EAAC,4CAA4C,YACzD,SAAS,GACL,IACF,EAEN,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAC3B,eACE,SAAS,EAAE,WAAW,CACpB,gCAAgC,EAChC,oBAAoB,CAAC,IAAI,CAAC,CAC3B,YAEA,IAAI,GACA,CACR,EAEA,UAAU,IAAI,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,CAClC,KAAC,cAAc,cAAE,WAAW,GAAkB,CAC/C,CAAC,CAAC,CAAC,IAAI,IACP,CACJ,CAAA;YAED,IAAI,IAAI,KAAK,SAAS,IAAI,CAAC,UAAU,EAAE,CAAC;gBACtC,qDAAqD;gBACrD,wDAAwD;gBACxD,uDAAuD;gBACvD,oDAAoD;gBACpD,MAAM,WAAW,GACf,UAAU,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC;oBAClC,CAAC,CAAC,GAAG;oBACL,CAAC,CAAC,UAAU,CAAA;gBAEhB,OAAO,CACL,KAAC,WAAW,IACV,SAAS,EAAE,aAAa,EACxB,IAAI,EAAE,IAAI;oBAEV,gDAAgD;oBAChD,kDAAkD;oBAClD,qDAAqD;oBACrD,6BAA6B;oBAC7B,GAAG,EACD,UAAU;wBACR,CAAC,CAAC,qBAAqB;wBACvB,CAAC,CAAC,SAAS,EAEf,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,YAExC,OAAO,IAZH,KAAK,CAaE,CACf,CAAA;YACH,CAAC;YAED,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;gBACvB,0DAA0D;gBAC1D,uDAAuD;gBACvD,uDAAuD;gBACvD,0DAA0D;gBAC1D,sDAAsD;gBACtD,sDAAsD;gBACtD,mBAAmB;gBACnB,OAAO,CAEL,6BACgB,MAAM,EACpB,SAAS,EAAE,aAAa,EAExB,IAAI,EAAC,MAAM,YAEV,OAAO,IAHH,KAAK,CAIR,CACL,CAAA;YACH,CAAC;YAED,OAAO,CACL,iBACE,SAAS,EAAE,aAAa,EACxB,QAAQ,EAAE,UAAU,EAEpB,OAAO,EAAE,GAAG,EAAE;oBACZ,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAA;gBACnB,CAAC,EACD,IAAI,EAAC,QAAQ,YAEZ,OAAO,IANH,KAAK,CAOH,CACV,CAAA;QACH,CAAC,CAAC,GACE,CACP,CAAA;AACH,CAAC,CAAA"}
@@ -0,0 +1,21 @@
1
+ import type { ReactNode } from "react";
2
+ /**
3
+ * The face, and the one piece of this component that holds state.
4
+ *
5
+ * A portrait grid is the shape that cannot absorb a broken image:
6
+ * a missing `<img>` is a torn hole where a person's face was, next
7
+ * to nine that rendered. `onError` is the only signal a browser
8
+ * gives for a 404, a wrong content type, or a file the user's
9
+ * network stripped — so the fallback the caller already supplied for
10
+ * "no picture" covers "no picture *today*" as well.
11
+ *
12
+ * Its own component because the state is per portrait. Hoisting it
13
+ * to a `Record<string, boolean>` in the set would re-render every
14
+ * tile whenever any one image failed.
15
+ */
16
+ export declare const PortraitAvatar: ({ className, imageSrc, initials, }: {
17
+ className: string;
18
+ imageSrc: string | undefined;
19
+ initials: ReactNode;
20
+ }) => ReactNode;
21
+ //# sourceMappingURL=PortraitAvatar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PortraitAvatar.d.ts","sourceRoot":"","sources":["../../src/PortraitTiles/PortraitAvatar.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGtC;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,cAAc,GAAI,oCAI5B;IACD,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAA;IAC5B,QAAQ,EAAE,SAAS,CAAA;CACpB,KAAG,SAuBH,CAAA"}
@@ -0,0 +1,29 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ /**
4
+ * The face, and the one piece of this component that holds state.
5
+ *
6
+ * A portrait grid is the shape that cannot absorb a broken image:
7
+ * a missing `<img>` is a torn hole where a person's face was, next
8
+ * to nine that rendered. `onError` is the only signal a browser
9
+ * gives for a 404, a wrong content type, or a file the user's
10
+ * network stripped — so the fallback the caller already supplied for
11
+ * "no picture" covers "no picture *today*" as well.
12
+ *
13
+ * Its own component because the state is per portrait. Hoisting it
14
+ * to a `Record<string, boolean>` in the set would re-render every
15
+ * tile whenever any one image failed.
16
+ */
17
+ export const PortraitAvatar = ({ className, imageSrc, initials, }) => {
18
+ const [isImageBroken, setIsImageBroken] = useState(false);
19
+ if (imageSrc === undefined || isImageBroken) {
20
+ return (_jsx("span", { "aria-hidden": true, className: className, children: initials }));
21
+ }
22
+ return (_jsx("img", {
23
+ // The name is beside it, inside the same tile, so an alt here
24
+ // is the name announced twice.
25
+ alt: "", className: className, onError: () => {
26
+ setIsImageBroken(true);
27
+ }, src: imageSrc }));
28
+ };
29
+ //# sourceMappingURL=PortraitAvatar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PortraitAvatar.js","sourceRoot":"","sources":["../../src/PortraitTiles/PortraitAvatar.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAEhC;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,EAC7B,SAAS,EACT,QAAQ,EACR,QAAQ,GAKT,EAAa,EAAE;IACd,MAAM,CAAC,aAAa,EAAE,gBAAgB,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAEzD,IAAI,QAAQ,KAAK,SAAS,IAAI,aAAa,EAAE,CAAC;QAC5C,OAAO,CACL,oCAAkB,SAAS,EAAE,SAAS,YACnC,QAAQ,GACJ,CACR,CAAA;IACH,CAAC;IAED,OAAO,CACL;QACE,8DAA8D;QAC9D,+BAA+B;QAC/B,GAAG,EAAC,EAAE,EACN,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,GAAG,EAAE;YACZ,gBAAgB,CAAC,IAAI,CAAC,CAAA;QACxB,CAAC,EACD,GAAG,EAAE,QAAQ,GACb,CACH,CAAA;AACH,CAAC,CAAA"}
@@ -0,0 +1,162 @@
1
+ import type { CategoricalIndex, ControlSize } from "@charcuterie/tokens";
2
+ import type { ReactNode } from "react";
3
+ /**
4
+ * Where one portrait's colour comes from. Two arms, and the fleet
5
+ * has both — the same split
6
+ * [`cardAccentEdge.ts`](../Card/cardAccentEdge.ts) already draws,
7
+ * for the same reason.
8
+ *
9
+ * - **`categorical`** — one of the ten audited hues. A colour the
10
+ * library owns, re-themed by `data-variant`, gated in both
11
+ * schemes. Right whenever the colour is a *choice from what we
12
+ * offered*.
13
+ * - **`color`** — any CSS colour, from data. Right when the colour
14
+ * **arrived from the world** and re-theming it would be lying
15
+ * about a physical object. points-market is exactly this: each
16
+ * kid's colour matches the NFC card they tap, by the workspace's
17
+ * 2026-07-20 `kid-identity-colors-match-nfc-cards` record. Ten
18
+ * palette hues cannot represent that, and swapping them in would
19
+ * have made the app disagree with the cards on the fridge.
20
+ *
21
+ * Neither is a default. A portrait that names no colour takes the
22
+ * next `categorical` hue by position.
23
+ */
24
+ type PortraitTileColour = {
25
+ categorical?: CategoricalIndex;
26
+ color?: never;
27
+ } | {
28
+ categorical?: never;
29
+ color: string;
30
+ };
31
+ export type PortraitTileItem = PortraitTileColour & {
32
+ /**
33
+ * The quiet line under the number, naming its unit — "points",
34
+ * "episodes left", "due today".
35
+ *
36
+ * Inside the tile, so it is part of the accessible name. "1,240"
37
+ * on its own tells a screen-reader user nothing.
38
+ */
39
+ hint?: ReactNode;
40
+ /** Makes the portrait a real `<a href>`. Same rules as `ActionTiles`. */
41
+ href?: string;
42
+ /**
43
+ * A picture of the subject. Falls back to {@link initials} when
44
+ * absent, and **also** when the file 404s — a broken portrait grid
45
+ * is the one failure this shape cannot absorb.
46
+ */
47
+ imageSrc?: string;
48
+ /**
49
+ * Drawn in the hue's solid fill when there is no picture. One or
50
+ * two characters; longer strings are the caller's problem to
51
+ * shorten, because "which letters" is a naming question this
52
+ * component cannot answer (a mononym, a family that shares an
53
+ * initial).
54
+ */
55
+ initials?: string;
56
+ isDisabled?: boolean;
57
+ /** Opens in a new tab, and says so to a screen reader. `href` only. */
58
+ isExternal?: boolean;
59
+ /** The name. */
60
+ label: ReactNode;
61
+ /**
62
+ * The one number this portrait is chosen by — a balance, a count,
63
+ * a streak. Optional: a picker that is only ever "who are you"
64
+ * passes none and the tile closes up around the name.
65
+ */
66
+ stat?: ReactNode;
67
+ /** Identity. The React key, and what `onChoose` is handed. */
68
+ value: string;
69
+ };
70
+ /**
71
+ * How a portrait arranges itself.
72
+ *
73
+ * - `auto` — a **row** while the set is narrow, a **column** once
74
+ * it has room. The switch is a container query on the set, not a
75
+ * media query: a picker in a 320px sidebar is a list of rows on a
76
+ * 2560px monitor, and the same picker filling a phone is a column
77
+ * per screenful.
78
+ * - `row` / `column` — fixed, for a layout that has already decided.
79
+ */
80
+ export type PortraitTilesLayout = "auto" | "column" | "row";
81
+ export type PortraitTilesProps = {
82
+ className?: string;
83
+ items: readonly PortraitTileItem[];
84
+ /** The set's accessible name. Required. */
85
+ label: string;
86
+ layout?: PortraitTilesLayout;
87
+ /** The narrowest a portrait track may be, in CSS px. */
88
+ minTileInlineSize?: number;
89
+ /** What `isExternal` announces. Not shown. */
90
+ newTabLabel?: string;
91
+ /** Fired by a portrait that carries no `href`. */
92
+ onChoose?: (value: string) => void;
93
+ size?: ControlSize;
94
+ };
95
+ /**
96
+ * A set of PEOPLE — or of anything else picked by its face — drawn
97
+ * as round pictures with a name and one big number.
98
+ *
99
+ * points-market's "Who's shopping?" is the shape this is taken from,
100
+ * and it is deliberately **not** an `ActionTiles` variant. The two
101
+ * answer different questions:
102
+ *
103
+ * - `ActionTiles` asks *what do you want to do* — a name, a line of
104
+ * help, an icon that illustrates the verb.
105
+ * - This asks *who is this* — a face, and one number that decides
106
+ * it. There is no description, because a person is not explained.
107
+ *
108
+ * Collapsing them would mean one component whose icon is sometimes a
109
+ * 20px glyph beside the title and sometimes a 144px circle above it,
110
+ * with `hint` meaning "what this does" in one mode and "points" in
111
+ * the other. That is two components wearing one name, which is the
112
+ * mistake the
113
+ * [2026-08-25 record](../../../../docs/decisions/2026-08-25-a-choice-tile-is-a-radiogroup-shape-not-a-third-component.md)
114
+ * warns about from the other direction.
115
+ *
116
+ * What they DO share is the box — `TILE_BOX_CLASS` and the same
117
+ * `auto-fill` grid — so a page carrying both does not show two
118
+ * different cards.
119
+ *
120
+ * ### It scales, and the scaling is the point
121
+ *
122
+ * The owner's one requirement when this moved into the library:
123
+ * *"that one is meant to scale. I don't wanna lose that
124
+ * functionality."* Two axes carry it.
125
+ *
126
+ * 1. **The container.** `layout="auto"` is a row while the set is
127
+ * narrow and a column once it has room, off a `cq-sm` query on
128
+ * the set itself. Nothing reads the viewport, so the same picker
129
+ * works in a sidebar and on a wall.
130
+ * 2. **The density.** Every length is a token, so `data-density="kiosk"`
131
+ * grows the type ramp and the control heights under it. points-market
132
+ * runs `kiosk` today; that is where its big numbers come from,
133
+ * and it keeps them.
134
+ *
135
+ * ### The colour is the person's, not the position's
136
+ *
137
+ * A positional hue is the default because it costs a call site
138
+ * nothing. It is also the wrong default for a household: add a
139
+ * fifth member and the first four change colour, which is exactly
140
+ * the identity the picker was using to be fast. Any set whose
141
+ * subjects persist should name its colour per subject.
142
+ *
143
+ * There are two ways to name one, and the difference is where the
144
+ * colour came from:
145
+ *
146
+ * - **`categorical`** — an index into the ten audited hues. A
147
+ * choice from what the library offered, so the library keeps
148
+ * owning it: re-themed by `data-variant`, contrast-gated in both
149
+ * schemes. The same call Docket makes for a project.
150
+ * - **`color`** — any CSS colour, straight from data. For a colour
151
+ * the system does **not** own and cannot re-theme without lying:
152
+ * points-market's kids are coloured to match the NFC cards they
153
+ * tap, so the picker and the cards on the fridge have to agree,
154
+ * and ten palette hues cannot promise that.
155
+ *
156
+ * The second arm is the narrow one. Reach for it only when the
157
+ * colour is a fact about the subject rather than a decision about
158
+ * the design — the same line `Swatch` draws, for the same reason.
159
+ */
160
+ export declare const PortraitTiles: ({ className, items, label, layout, minTileInlineSize, newTabLabel, onChoose, size, }: PortraitTilesProps) => ReactNode;
161
+ export {};
162
+ //# sourceMappingURL=PortraitTiles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PortraitTiles.d.ts","sourceRoot":"","sources":["../../src/PortraitTiles/PortraitTiles.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,WAAW,EACZ,MAAM,qBAAqB,CAAA;AAK5B,OAAO,KAAK,EAAiB,SAAS,EAAE,MAAM,OAAO,CAAA;AAqBrD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,KAAK,kBAAkB,GACnB;IAAE,WAAW,CAAC,EAAE,gBAAgB,CAAC;IAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CAAE,GACjD;IAAE,WAAW,CAAC,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAE1C,MAAM,MAAM,gBAAgB,GAAG,kBAAkB,GAAG;IAClD;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,yEAAyE;IACzE,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,uEAAuE;IACvE,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,gBAAgB;IAChB,KAAK,EAAE,SAAS,CAAA;IAChB;;;;OAIG;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,8DAA8D;IAC9D,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAA;AAE3D,MAAM,MAAM,kBAAkB,GAAG;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,SAAS,gBAAgB,EAAE,CAAA;IAClC,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,mBAAmB,CAAA;IAC5B,wDAAwD;IACxD,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,8CAA8C;IAC9C,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,kDAAkD;IAClD,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAClC,IAAI,CAAC,EAAE,WAAW,CAAA;CACnB,CAAA;AAiLD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgEG;AACH,eAAO,MAAM,aAAa,GAAI,sFAS3B,kBAAkB,KAAG,SA8MvB,CAAA"}