@cloudgrid-io/ui 0.31.4 → 0.31.6

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 (108) hide show
  1. package/README.md +32 -0
  2. package/dist/badge/brand.d.ts +8 -0
  3. package/dist/badge/brand.d.ts.map +1 -0
  4. package/dist/badge/brand.js +22 -0
  5. package/dist/badge/brand.js.map +1 -0
  6. package/dist/badge/bubble.d.ts +11 -0
  7. package/dist/badge/bubble.d.ts.map +1 -0
  8. package/dist/badge/bubble.js +17 -0
  9. package/dist/badge/bubble.js.map +1 -0
  10. package/dist/badge/css-base.d.ts +25 -0
  11. package/dist/badge/css-base.d.ts.map +1 -0
  12. package/dist/badge/css-base.js +103 -0
  13. package/dist/badge/css-base.js.map +1 -0
  14. package/dist/badge/css-bubble.d.ts +10 -0
  15. package/dist/badge/css-bubble.d.ts.map +1 -0
  16. package/dist/badge/css-bubble.js +21 -0
  17. package/dist/badge/css-bubble.js.map +1 -0
  18. package/dist/badge/css-owner.d.ts +7 -0
  19. package/dist/badge/css-owner.d.ts.map +1 -0
  20. package/dist/badge/css-owner.js +159 -0
  21. package/dist/badge/css-owner.js.map +1 -0
  22. package/dist/badge/css-visitor.d.ts +7 -0
  23. package/dist/badge/css-visitor.d.ts.map +1 -0
  24. package/dist/badge/css-visitor.js +69 -0
  25. package/dist/badge/css-visitor.js.map +1 -0
  26. package/dist/badge/html.d.ts +26 -0
  27. package/dist/badge/html.d.ts.map +1 -0
  28. package/dist/badge/html.js +55 -0
  29. package/dist/badge/html.js.map +1 -0
  30. package/dist/badge/index.d.ts +43 -0
  31. package/dist/badge/index.d.ts.map +1 -0
  32. package/dist/badge/index.js +46 -0
  33. package/dist/badge/index.js.map +1 -0
  34. package/dist/badge/marks.d.ts +47 -0
  35. package/dist/badge/marks.d.ts.map +1 -0
  36. package/dist/badge/marks.js +131 -0
  37. package/dist/badge/marks.js.map +1 -0
  38. package/dist/badge/owner.d.ts +239 -0
  39. package/dist/badge/owner.d.ts.map +1 -0
  40. package/dist/badge/owner.js +355 -0
  41. package/dist/badge/owner.js.map +1 -0
  42. package/dist/badge/palette.d.ts +23 -0
  43. package/dist/badge/palette.d.ts.map +1 -0
  44. package/dist/badge/palette.js +86 -0
  45. package/dist/badge/palette.js.map +1 -0
  46. package/dist/badge/root.d.ts +24 -0
  47. package/dist/badge/root.d.ts.map +1 -0
  48. package/dist/badge/root.js +11 -0
  49. package/dist/badge/root.js.map +1 -0
  50. package/dist/badge/shared.d.ts +56 -0
  51. package/dist/badge/shared.d.ts.map +1 -0
  52. package/dist/badge/shared.js +53 -0
  53. package/dist/badge/shared.js.map +1 -0
  54. package/dist/badge/visitor.d.ts +96 -0
  55. package/dist/badge/visitor.d.ts.map +1 -0
  56. package/dist/badge/visitor.js +118 -0
  57. package/dist/badge/visitor.js.map +1 -0
  58. package/dist/blocks/entity-status.d.ts +44 -1
  59. package/dist/blocks/entity-status.d.ts.map +1 -1
  60. package/dist/blocks/entity-status.js +26 -1
  61. package/dist/blocks/entity-status.js.map +1 -1
  62. package/dist/blocks/expiry-notice.d.ts +86 -0
  63. package/dist/blocks/expiry-notice.d.ts.map +1 -0
  64. package/dist/blocks/expiry-notice.js +81 -0
  65. package/dist/blocks/expiry-notice.js.map +1 -0
  66. package/dist/blocks/icons.d.ts +22 -0
  67. package/dist/blocks/icons.d.ts.map +1 -1
  68. package/dist/blocks/icons.js +22 -0
  69. package/dist/blocks/icons.js.map +1 -1
  70. package/dist/blocks/notification-row.d.ts +88 -0
  71. package/dist/blocks/notification-row.d.ts.map +1 -0
  72. package/dist/blocks/notification-row.js +32 -0
  73. package/dist/blocks/notification-row.js.map +1 -0
  74. package/dist/blocks/phosphor-marks.d.ts +10 -0
  75. package/dist/blocks/phosphor-marks.d.ts.map +1 -1
  76. package/dist/blocks/phosphor-marks.js +15 -0
  77. package/dist/blocks/phosphor-marks.js.map +1 -1
  78. package/dist/brand/badge/manrope-latin-wght-normal.woff2 +0 -0
  79. package/dist/brand/badge/space-grotesk-latin-wght-normal.woff2 +0 -0
  80. package/dist/brand/email/headline-activity.png +0 -0
  81. package/dist/brand/email/headline-expired.png +0 -0
  82. package/dist/brand/email/headline-expires-tomorrow.png +0 -0
  83. package/dist/brand/email/headline-expiring-soon.png +0 -0
  84. package/dist/brand/email/headline-invited.png +0 -0
  85. package/dist/brand/email/headline-sign-in-code.png +0 -0
  86. package/dist/brand/email/mesh.png +0 -0
  87. package/dist/brand/email/wordmark.png +0 -0
  88. package/dist/email/assets.d.ts +50 -0
  89. package/dist/email/assets.d.ts.map +1 -0
  90. package/dist/email/assets.js +16 -0
  91. package/dist/email/assets.js.map +1 -0
  92. package/dist/email/index.d.ts +204 -0
  93. package/dist/email/index.d.ts.map +1 -0
  94. package/dist/email/index.js +429 -0
  95. package/dist/email/index.js.map +1 -0
  96. package/dist/email/palette.d.ts +40 -0
  97. package/dist/email/palette.d.ts.map +1 -0
  98. package/dist/email/palette.js +44 -0
  99. package/dist/email/palette.js.map +1 -0
  100. package/dist/index.d.ts +2 -0
  101. package/dist/index.d.ts.map +1 -1
  102. package/dist/index.js +2 -0
  103. package/dist/index.js.map +1 -1
  104. package/dist/ui/dropdown-menu.d.ts +8 -1
  105. package/dist/ui/dropdown-menu.d.ts.map +1 -1
  106. package/dist/ui/dropdown-menu.js +9 -2
  107. package/dist/ui/dropdown-menu.js.map +1 -1
  108. package/package.json +18 -2
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Builders both panels use: the panel frame, the Select look, the switch row.
3
+ *
4
+ * Behaviour is the badge's, not this module's. Every interactive element
5
+ * carries a `data-action` (and where it matters a `data-value`) so the badge
6
+ * can wire one delegated listener per panel, and the ARIA state the badge must
7
+ * keep in step (`aria-checked`, `aria-expanded`, `aria-selected`) is rendered
8
+ * here so the first paint is already correct.
9
+ */
10
+ import { CARET_DOWN, CHECK, X } from "./marks.js";
11
+ import { esc, glyph, wordmark } from "./html.js";
12
+ /**
13
+ * The panel frame: the wordmark, a close button, then the step. A phone layout
14
+ * also needs `scrim()` behind it, as a sibling, which the badge closes on tap.
15
+ */
16
+ export function panel({ label, body, side = "right", closeLabel = "Close", attrs = "" }) {
17
+ return (`<div class="cgb-panel" role="dialog" aria-label="${esc(label)}" data-side="${side}"${attrs ? ` ${attrs}` : ""}>` +
18
+ `<div class="cgb-head">${wordmark()}<button class="cgb-icon-btn" type="button" data-action="close" aria-label="${esc(closeLabel)}">${glyph(X)}</button></div>` +
19
+ body +
20
+ "</div>");
21
+ }
22
+ /** The phone layout's dimmer behind a bottom sheet. Hidden on desktop by the stylesheet. */
23
+ export const scrim = () => `<div class="cgb-scrim" data-action="close" aria-hidden="true"></div>`;
24
+ /**
25
+ * Select's look (`ui/select.tsx`): a 32px trigger with a chevron, and a list
26
+ * that opens with the selected option laid over the trigger, as Select's
27
+ * `alignItemWithTrigger` places it. Items are 30px, so the offset is exact.
28
+ */
29
+ export function select({ label, options, value, action = "select", open = false, attrs = "" }) {
30
+ const index = Math.max(0, options.findIndex((o) => o.value === value));
31
+ const current = options[index];
32
+ // Popup padding 4px, items 30px, trigger 32px: the selected item's centre
33
+ // lands on the trigger's centre.
34
+ const top = -(4 + index * 30) + 1;
35
+ return (`<div class="cgb-sel"${attrs ? ` ${attrs}` : ""}>` +
36
+ `<button class="cgb-sel-trigger" type="button" data-action="${action}" aria-haspopup="listbox" aria-expanded="${open}" aria-label="${esc(label)}">` +
37
+ `<span>${esc(current?.label ?? "")}</span>${glyph(CARET_DOWN)}</button>` +
38
+ `<div class="cgb-menu" role="listbox" aria-label="${esc(label)}" style="top:${top}px"${open ? "" : " hidden"}>` +
39
+ options
40
+ .map((o) => `<div class="cgb-menu-item" role="option" tabindex="-1" data-action="${action}-pick" data-value="${esc(o.value)}" aria-selected="${o.value === value}">` +
41
+ `<span>${esc(o.label)}</span>${glyph(CHECK)}</div>`)
42
+ .join("") +
43
+ "</div></div>");
44
+ }
45
+ /** A switch: `role="switch"` with `aria-checked`, the library Switch's 32x18 track. */
46
+ export function switchControl(label, checked, action, labelled = false) {
47
+ return `<button class="cgb-switch" type="button" role="switch" aria-checked="${checked}" data-action="${action}"${labelled ? "" : ` aria-label="${esc(label)}"`}></button>`;
48
+ }
49
+ /** A one-line row: a text label and a switch at the end. The whole row is the switch's label. */
50
+ export function toggleRow(label, checked, action, cls = "") {
51
+ return `<label class="cgb-toggle${cls ? ` ${cls}` : ""}"><span>${esc(label)}</span>${switchControl(label, checked, action)}</label>`;
52
+ }
53
+ //# sourceMappingURL=shared.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared.js","sourceRoot":"","sources":["../../src/badge/shared.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * The visitor's half of the badge: the feedback bubble and the feedback panel,
3
+ * with its sign-in step. Markup only; the badge wires the behaviour.
4
+ *
5
+ * Import from `@cloudgrid-io/ui/badge/visitor` in the badge's main file. The
6
+ * stylesheet for this half is `BADGE_BASE_CSS + BADGE_VISITOR_CSS`.
7
+ *
8
+ * Copy follows the spec of 2026-10-12. The word "React" is never used: the
9
+ * bubble's name is "Leave feedback on this app".
10
+ */
11
+ import { type SelectOption } from "./shared.js";
12
+ export { BADGE_BASE_CSS } from "./css-base.js";
13
+ export { BADGE_VISITOR_CSS } from "./css-visitor.js";
14
+ export { panel, scrim, select, switchControl, toggleRow } from "./shared.js";
15
+ export type { BadgeSide, PanelOptions, SelectOption, SelectOptions } from "./shared.js";
16
+ export { esc, href, glyph, wordmark, sign } from "./html.js";
17
+ export { badgeRoot, type BadgeRootOptions } from "./root.js";
18
+ export interface BubbleOptions {
19
+ /** The bubble's accessible name. */
20
+ label?: string;
21
+ /** The close x's accessible name. The bubble comes back on reload. */
22
+ closeLabel?: string;
23
+ }
24
+ /**
25
+ * The feedback bubble: a white speech bubble with a tail and no text, three
26
+ * icons (thumbs-up purple, wrench blue, chat green), drifting gently unless the
27
+ * viewer asks for reduced motion. A small x hides it until reload; it shows on
28
+ * hover, and always on a touch screen.
29
+ *
30
+ * Actions: `open-feedback` on the bubble, `hide-bubble` on the x.
31
+ */
32
+ export declare function bubble({ label, closeLabel, }?: BubbleOptions): string;
33
+ /** The five reaction kinds, as the API stores them. */
34
+ export type ReactionKind = "like" | "dislike" | "idea" | "ship" | "broken";
35
+ /** Each kind's glyph and its name, which is the button's accessible name and tooltip. */
36
+ export declare const REACTIONS: ReadonlyArray<{
37
+ kind: ReactionKind;
38
+ label: string;
39
+ paths: readonly string[];
40
+ }>;
41
+ /** Like and Dislike exclude each other: picking one clears the other. The rest combine freely. */
42
+ export declare function toggleReaction(pressed: readonly ReactionKind[], kind: ReactionKind): ReactionKind[];
43
+ /** The note's categories, as today's badge sends them. Other is the default. */
44
+ export declare const NOTE_CATEGORIES: readonly SelectOption[];
45
+ export interface VisitorPanelOptions {
46
+ /** The app's name. */
47
+ name: string;
48
+ /** Counts per kind. Each renders above zero, on all five reactions. */
49
+ counts?: Partial<Record<ReactionKind, number>>;
50
+ /** The kinds this visitor has already picked. */
51
+ pressed?: readonly ReactionKind[];
52
+ /** The note's category. Default `other`. */
53
+ category?: string;
54
+ /** Render the category menu open. */
55
+ categoryOpen?: boolean;
56
+ /** The note's text, when re-rendering. */
57
+ message?: string;
58
+ /** "Signed in as dana@atomiclabs.io". Absent: no hint. */
59
+ signedInAs?: string;
60
+ /** After Send: the note section is replaced by the sent line. */
61
+ sent?: boolean;
62
+ /** "This page is live on the grid. Learn more" points here. */
63
+ learnMoreUrl?: string;
64
+ /** `corner` (default) above the bubble. A phone shows a bottom sheet whatever this says. */
65
+ side?: "corner" | "right" | "left";
66
+ }
67
+ /**
68
+ * The feedback panel: the question, five reactions, and "Leave a note" under a
69
+ * divider. Actions: `reaction` (with `data-value` = the kind) on each reaction,
70
+ * `category` and `category-pick` on the menu, `send` on Send, `close`.
71
+ */
72
+ export declare function visitorPanel({ name, counts, pressed, category, categoryOpen, message, signedInAs, sent, learnMoreUrl, side, }: VisitorPanelOptions): string;
73
+ export interface VisitorSignInOptions {
74
+ name: string;
75
+ /** The line above the methods. Default "Sign in to leave feedback." */
76
+ line?: string;
77
+ /** Offer "Continue with Google" (`login-google`). Default true. */
78
+ google?: boolean;
79
+ /** The email field's value, when re-rendering. */
80
+ email?: string;
81
+ /** The status line beside Send sign-in link: "Check your inbox for the link." */
82
+ status?: string;
83
+ /** "Sign in on CloudGrid", the fallback that leaves the page. */
84
+ signInUrl: string;
85
+ learnMoreUrl?: string;
86
+ side?: "corner" | "right" | "left";
87
+ }
88
+ /**
89
+ * The sign-in step, today's badge restyled: on an app that requires sign-in, a
90
+ * signed-out visitor's panel opens here, then shows the reactions. Google,
91
+ * then an email link, then the CloudGrid sign-in page as the fallback.
92
+ *
93
+ * Actions: `login-google`, `login-email` (read `#cgb-email`), `signin` (a link).
94
+ */
95
+ export declare function visitorSignIn({ name, line, google, email, status, signInUrl, learnMoreUrl, side, }: VisitorSignInOptions): string;
96
+ //# sourceMappingURL=visitor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visitor.d.ts","sourceRoot":"","sources":["../../src/badge/visitor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,118 @@
1
+ /**
2
+ * The visitor's half of the badge: the feedback bubble and the feedback panel,
3
+ * with its sign-in step. Markup only; the badge wires the behaviour.
4
+ *
5
+ * Import from `@cloudgrid-io/ui/badge/visitor` in the badge's main file. The
6
+ * stylesheet for this half is `BADGE_BASE_CSS + BADGE_VISITOR_CSS`.
7
+ *
8
+ * Copy follows the spec of 2026-10-12. The word "React" is never used: the
9
+ * bubble's name is "Leave feedback on this app".
10
+ */
11
+ import { BUG, CHECK_CIRCLE, GOOGLE_LOGO, LIGHTBULB_FILAMENT, ROCKET_LAUNCH, THUMBS_DOWN, THUMBS_UP, X } from "./marks.js";
12
+ import { esc, glyph, href } from "./html.js";
13
+ import { bubbleIcons } from "./bubble.js";
14
+ import { panel, select } from "./shared.js";
15
+ export { BADGE_BASE_CSS } from "./css-base.js";
16
+ export { BADGE_VISITOR_CSS } from "./css-visitor.js";
17
+ export { panel, scrim, select, switchControl, toggleRow } from "./shared.js";
18
+ export { esc, href, glyph, wordmark, sign } from "./html.js";
19
+ export { badgeRoot } from "./root.js";
20
+ /**
21
+ * The feedback bubble: a white speech bubble with a tail and no text, three
22
+ * icons (thumbs-up purple, wrench blue, chat green), drifting gently unless the
23
+ * viewer asks for reduced motion. A small x hides it until reload; it shows on
24
+ * hover, and always on a touch screen.
25
+ *
26
+ * Actions: `open-feedback` on the bubble, `hide-bubble` on the x.
27
+ */
28
+ export function bubble({ label = "Leave feedback on this app", closeLabel = "Hide feedback until you reload", } = {}) {
29
+ return (`<div class="cgb-bubble-wrap">` +
30
+ `<button class="cgb-bubble" type="button" data-action="open-feedback" aria-label="${esc(label)}" aria-haspopup="dialog">` +
31
+ bubbleIcons() +
32
+ `</button>` +
33
+ `<button class="cgb-bubble-x" type="button" data-action="hide-bubble" aria-label="${esc(closeLabel)}">${glyph(X)}</button>` +
34
+ `</div>`);
35
+ }
36
+ /** Each kind's glyph and its name, which is the button's accessible name and tooltip. */
37
+ export const REACTIONS = [
38
+ { kind: "like", label: "Like", paths: THUMBS_UP },
39
+ { kind: "dislike", label: "Dislike", paths: THUMBS_DOWN },
40
+ { kind: "idea", label: "Clever idea", paths: LIGHTBULB_FILAMENT },
41
+ { kind: "ship", label: "Ship it", paths: ROCKET_LAUNCH },
42
+ { kind: "broken", label: "Something’s broken", paths: BUG },
43
+ ];
44
+ /** Like and Dislike exclude each other: picking one clears the other. The rest combine freely. */
45
+ export function toggleReaction(pressed, kind) {
46
+ if (pressed.includes(kind))
47
+ return pressed.filter((k) => k !== kind);
48
+ const rival = kind === "like" ? "dislike" : kind === "dislike" ? "like" : null;
49
+ return [...pressed.filter((k) => k !== rival), kind];
50
+ }
51
+ // ---- the note ------------------------------------------------------------------
52
+ /** The note's categories, as today's badge sends them. Other is the default. */
53
+ export const NOTE_CATEGORIES = [
54
+ { value: "bug", label: "Bug" },
55
+ { value: "idea", label: "Idea" },
56
+ { value: "praise", label: "Praise" },
57
+ { value: "question", label: "Question" },
58
+ { value: "other", label: "Other" },
59
+ ];
60
+ const LEARN_MORE = "https://cloudgrid.io";
61
+ const footer = (url) => `<a class="cgb-made" href="${href(url)}" target="_blank" rel="noopener">This page is live on the grid. Learn more</a>`;
62
+ const title = (name) => `<h2 class="cgb-title">What do you think of ${esc(name)}?</h2>`;
63
+ /**
64
+ * The feedback panel: the question, five reactions, and "Leave a note" under a
65
+ * divider. Actions: `reaction` (with `data-value` = the kind) on each reaction,
66
+ * `category` and `category-pick` on the menu, `send` on Send, `close`.
67
+ */
68
+ export function visitorPanel({ name, counts = {}, pressed = [], category = "other", categoryOpen = false, message = "", signedInAs, sent = false, learnMoreUrl = LEARN_MORE, side = "corner", }) {
69
+ const reactions = REACTIONS.map(({ kind, label, paths }) => {
70
+ // Every kind shows its count to visitors, Dislike and Something's broken
71
+ // included (founder, 2026-10-12, reversing the positive-only rule).
72
+ const n = counts[kind];
73
+ const count = typeof n === "number" && n > 0 ? `<span class="cgb-rxc">${esc(n)}</span>` : "";
74
+ return (`<button class="cgb-rxb" type="button" data-action="reaction" data-value="${kind}" aria-pressed="${pressed.includes(kind)}" aria-label="${esc(label)}" title="${esc(label)}">` +
75
+ glyph(paths) +
76
+ count +
77
+ `</button>`);
78
+ }).join("");
79
+ const note = sent
80
+ ? `<p class="cgb-sent" role="status">${glyph(CHECK_CIRCLE)}Sent. The maker sees it on their Feedback page.</p>`
81
+ : `<div class="cgb-note">` +
82
+ `<div class="cgb-note-head"><label class="cgb-note-label" for="cgb-msg">Leave a note</label>` +
83
+ select({ label: "Category", options: NOTE_CATEGORIES, value: category, action: "category", open: categoryOpen }) +
84
+ `</div>` +
85
+ `<textarea class="cgb-textarea" id="cgb-msg" rows="3" placeholder="Optional. Tell the maker what you noticed.">${esc(message)}</textarea>` +
86
+ `<div class="cgb-note-actions"><span class="cgb-hint">${signedInAs ? `Signed in as ${esc(signedInAs)}` : ""}</span>` +
87
+ `<button class="cgb-btn" type="button" data-action="send">Send</button></div>` +
88
+ `</div>`;
89
+ return panel({
90
+ label: `Send feedback on ${name}`,
91
+ side,
92
+ body: title(name) + `<div class="cgb-rx" role="group" aria-label="Reactions">${reactions}</div>` + note + footer(learnMoreUrl),
93
+ });
94
+ }
95
+ /**
96
+ * The sign-in step, today's badge restyled: on an app that requires sign-in, a
97
+ * signed-out visitor's panel opens here, then shows the reactions. Google,
98
+ * then an email link, then the CloudGrid sign-in page as the fallback.
99
+ *
100
+ * Actions: `login-google`, `login-email` (read `#cgb-email`), `signin` (a link).
101
+ */
102
+ export function visitorSignIn({ name, line = "Sign in to leave feedback.", google = true, email = "", status = "", signInUrl, learnMoreUrl = LEARN_MORE, side = "corner", }) {
103
+ const body = title(name) +
104
+ `<p class="cgb-desc">${esc(line)}</p>` +
105
+ `<div class="cgb-signin">` +
106
+ (google
107
+ ? `<button class="cgb-method" type="button" data-action="login-google">${glyph(GOOGLE_LOGO)}Continue with Google</button><div class="cgb-or" aria-hidden="true">OR</div>`
108
+ : "") +
109
+ `<label class="cgb-field-label" for="cgb-email">Email</label>` +
110
+ `<input class="cgb-input" id="cgb-email" type="email" autocomplete="email" placeholder="you@example.com" value="${esc(email)}">` +
111
+ `<div class="cgb-note-actions"><span class="cgb-hint" role="status">${esc(status)}</span>` +
112
+ `<button class="cgb-btn" type="button" data-action="login-email">Send sign-in link</button></div>` +
113
+ `<a class="cgb-link" href="${href(signInUrl)}" target="_top" data-action="signin">Sign in on CloudGrid</a>` +
114
+ `</div>` +
115
+ footer(learnMoreUrl);
116
+ return panel({ label: `Send feedback on ${name}`, side, body });
117
+ }
118
+ //# sourceMappingURL=visitor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visitor.js","sourceRoot":"","sources":["../../src/badge/visitor.ts"],"names":[],"mappings":"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"}
@@ -132,5 +132,48 @@ declare function EntityStateDot({ status, label, className, ...props }: Omit<Rea
132
132
  /** Overrides the announced word. There is no visible word to override. */
133
133
  label?: React.ReactNode;
134
134
  }): import("react").JSX.Element;
135
- export { EntityStatus, EntityStateDot };
135
+ /**
136
+ * HOW SOON SOMETHING EXPIRES. The tag a card and the page header show from five
137
+ * days before expiry (ui#295, spec "Expiry warning and Keep", 2026-10-11).
138
+ *
139
+ * Not a status. "Expires in 5 days" is not something an entity IS: the
140
+ * platform's status for the same page is still `live`, or `charged` for an app
141
+ * that was never published. So it is its own component beside `EntityStatus`
142
+ * rather than an eleventh row in the status table, and the ten statuses above
143
+ * are unchanged. A surface shows this INSTEAD of the status chip while
144
+ * something is about to expire, which is the spec's rule for the card.
145
+ *
146
+ * ## Two tones, from the number
147
+ *
148
+ * More than one day left is the amber of `expired` (`soon`). One day left, or
149
+ * less, is the red of `error` (`urgent`): the spec's "urgent on the last day".
150
+ * The caller passes the whole days left and nothing else, so no surface can
151
+ * paint "Expires tomorrow" amber.
152
+ *
153
+ * ## The word is coloured here, unlike `EntityStatus`
154
+ *
155
+ * The founder-approved mock draws the word in the state colour with a border,
156
+ * and the tokens for it exist: `--cg-expired-strong` and `--cg-danger-strong`
157
+ * are the TEXT tones of those states (7.63:1 and 5.02:1 on their fills, per their
158
+ * notes in tokens.css), not the indicator tones `EntityStatus` refuses to put
159
+ * on a word. On the purple and ink surfaces both strong tokens resolve to the
160
+ * surface's own text colour and the fill to a translucent wash, so the chip
161
+ * flips with no help. The dot stays, and the word always renders, so the tone
162
+ * is never colour alone: the urgent chip SAYS "tomorrow".
163
+ */
164
+ export type EntityExpiryTone = "soon" | "urgent";
165
+ /** `urgent` on the last day (one day left or fewer), `soon` before it. */
166
+ export declare function entityExpiryTone(daysLeft: number): EntityExpiryTone;
167
+ /**
168
+ * The words, from whole days left. "Expires in 5 days", "Expires tomorrow",
169
+ * "Expires today". The number is always the real one.
170
+ */
171
+ export declare function entityExpiryLabel(daysLeft: number): string;
172
+ declare function EntityExpiryStatus({ daysLeft, label, className, ...props }: Omit<React.ComponentProps<"span">, "children"> & {
173
+ /** Whole days until expiry. 1 is tomorrow, 0 is today. */
174
+ daysLeft: number;
175
+ /** Overrides the default words. The tone still follows `daysLeft`. */
176
+ label?: React.ReactNode;
177
+ }): import("react").JSX.Element;
178
+ export { EntityStatus, EntityStateDot, EntityExpiryStatus };
136
179
  //# sourceMappingURL=entity-status.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"entity-status.d.ts","sourceRoot":"","sources":["../../src/blocks/entity-status.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,qHAWtB,CAAA;AAEV,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAA;AAEnE,kEAAkE;AAClE,MAAM,MAAM,gBAAgB,GACxB,QAAQ,GACR,WAAW,GACX,SAAS,GACT,SAAS,GACT,SAAS,GACT,QAAQ,CAAA;AAEZ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,gBAAgB,EAAE,gBAAgB,CAWzE,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,gBAAgB,EAAE,MAAM,CAehE,CAAA;AAmBD,iBAAS,YAAY,CAAC,EACpB,MAAM,EACN,KAAK,EACL,SAAS,EACT,GAAG,KAAK,EACT,EAAE,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC,GAAG;IAClD,MAAM,EAAE,gBAAgB,CAAA;IACxB,sFAAsF;IACtF,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CACxB,+BAqCA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,iBAAS,cAAc,CAAC,EACtB,MAAM,EACN,KAAK,EACL,SAAS,EACT,GAAG,KAAK,EACT,EAAE,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC,GAAG;IAClD,MAAM,EAAE,gBAAgB,CAAA;IACxB,0EAA0E;IAC1E,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CACxB,+BAwBA;AAED,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,CAAA"}
1
+ {"version":3,"file":"entity-status.d.ts","sourceRoot":"","sources":["../../src/blocks/entity-status.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,qHAWtB,CAAA;AAEV,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAA;AAEnE,kEAAkE;AAClE,MAAM,MAAM,gBAAgB,GACxB,QAAQ,GACR,WAAW,GACX,SAAS,GACT,SAAS,GACT,SAAS,GACT,QAAQ,CAAA;AAEZ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,gBAAgB,EAAE,gBAAgB,CAWzE,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,gBAAgB,EAAE,MAAM,CAehE,CAAA;AAmBD,iBAAS,YAAY,CAAC,EACpB,MAAM,EACN,KAAK,EACL,SAAS,EACT,GAAG,KAAK,EACT,EAAE,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC,GAAG;IAClD,MAAM,EAAE,gBAAgB,CAAA;IACxB,sFAAsF;IACtF,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CACxB,+BAqCA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,iBAAS,cAAc,CAAC,EACtB,MAAM,EACN,KAAK,EACL,SAAS,EACT,GAAG,KAAK,EACT,EAAE,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC,GAAG;IAClD,MAAM,EAAE,gBAAgB,CAAA;IACxB,0EAA0E;IAC1E,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CACxB,+BAwBA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,QAAQ,CAAA;AAEhD,0EAA0E;AAC1E,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,gBAAgB,CAEnE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAK1D;AAOD,iBAAS,kBAAkB,CAAC,EAC1B,QAAQ,EACR,KAAK,EACL,SAAS,EACT,GAAG,KAAK,EACT,EAAE,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC,GAAG;IAClD,0DAA0D;IAC1D,QAAQ,EAAE,MAAM,CAAA;IAChB,sEAAsE;IACtE,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CACxB,+BAwBA;AAED,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAA"}
@@ -192,5 +192,30 @@ function EntityStateDot({ status, label, className, ...props }) {
192
192
  // consumer's repo and a rule in our stylesheet does not.
193
193
  status === "building" && "motion-safe:animate-pulse", className), ...props, children: _jsx("span", { className: "sr-only", children: label ?? ENTITY_STATUS_LABEL[status] }) }));
194
194
  }
195
- export { EntityStatus, EntityStateDot };
195
+ /** `urgent` on the last day (one day left or fewer), `soon` before it. */
196
+ export function entityExpiryTone(daysLeft) {
197
+ return daysLeft <= 1 ? "urgent" : "soon";
198
+ }
199
+ /**
200
+ * The words, from whole days left. "Expires in 5 days", "Expires tomorrow",
201
+ * "Expires today". The number is always the real one.
202
+ */
203
+ export function entityExpiryLabel(daysLeft) {
204
+ const days = Math.max(0, Math.floor(daysLeft));
205
+ if (days === 0)
206
+ return "Expires today";
207
+ if (days === 1)
208
+ return "Expires tomorrow";
209
+ return `Expires in ${days} days`;
210
+ }
211
+ const EXPIRY_STYLE = {
212
+ soon: { chip: "border-expired-border bg-expired-fill text-expired-strong", dot: "bg-expired" },
213
+ urgent: { chip: "border-danger-border bg-danger-fill text-danger-strong", dot: "bg-danger" },
214
+ };
215
+ function EntityExpiryStatus({ daysLeft, label, className, ...props }) {
216
+ const tone = entityExpiryTone(daysLeft);
217
+ const style = EXPIRY_STYLE[tone];
218
+ return (_jsxs("span", { "data-slot": "entity-expiry-status", "data-tone": tone, "data-days-left": Math.max(0, Math.floor(daysLeft)), className: cn("inline-flex w-fit shrink-0 items-center gap-1.5 rounded-pill border px-2.5 py-0.5 text-caption font-medium whitespace-nowrap", style.chip, className), ...props, children: [_jsx("span", { "aria-hidden": "true", "data-slot": "entity-expiry-status-dot", className: cn("size-1.5 shrink-0 rounded-pill", style.dot) }), label ?? entityExpiryLabel(daysLeft)] }));
219
+ }
220
+ export { EntityStatus, EntityStateDot, EntityExpiryStatus };
196
221
  //# sourceMappingURL=entity-status.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"entity-status.js","sourceRoot":"","sources":["../../src/blocks/entity-status.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"entity-status.js","sourceRoot":"","sources":["../../src/blocks/entity-status.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,86 @@
1
+ import * as React from "react";
2
+ /**
3
+ * THE EXPIRY NOTICE. The row an item's page shows while it is about to expire,
4
+ * or after it has (ui#295, spec "Expiry warning and Keep", 2026-10-11).
5
+ *
6
+ * ```
7
+ * This page expires in 5 days. [ Keep ]
8
+ * Nobody has opened it in 25 days. Keep it and it won't expire.
9
+ * ```
10
+ *
11
+ * It is the Console's app-page status row (`AppStateBanner`, the row that says
12
+ * Paused, Building and the rest) with a ground: one sentence, one reason, at
13
+ * most one action, the action under the sentence on a phone. The type matches
14
+ * that row: the sentence 16px semibold, the reason a step down in the secondary
15
+ * tone. The founder-approved mock tints it so it reads as a warning rather than
16
+ * a resting state.
17
+ *
18
+ * ## Variants
19
+ *
20
+ * - `soon`: five days to one day before. The amber of the expired state.
21
+ * - `urgent`: the last day. The danger fill.
22
+ * - `from-email`: the reader arrived from the warning email's Keep link. The
23
+ * brand tint with a 2px brand outline, and the Console passes `focusAction` so
24
+ * Keep has focus and is one press away. The link never keeps by itself, since
25
+ * email scanners open every link; this is the click that does.
26
+ * - `expired`: after expiry, before the Shelf. The same amber as `soon`; the
27
+ * words are what differ ("This page expired.").
28
+ *
29
+ * No action is a variant of its own in effect, not a value: leave `action` out
30
+ * for a member who is not the owner or a grid admin and cannot keep it. The
31
+ * row still says what is happening and the reason line says who can act.
32
+ *
33
+ * ## Accessibility
34
+ *
35
+ * `role="status"`, like the Console row it matches: a polite region, so a
36
+ * notice that appears while the reader is on the page is announced without
37
+ * taking focus. Not `alert`: nothing here is an error and nothing interrupts.
38
+ * The region is named (`label`, "Expiry" by default) so a reader moving by
39
+ * region hears what it is about.
40
+ *
41
+ * The action is a real `<button>`, described by the sentence
42
+ * (`aria-describedby`), so a screen reader that lands on "Keep" hears what it
43
+ * keeps and when it expires. The tone is never colour alone: the sentence
44
+ * carries it ("expires tomorrow", "expired"), and the from-email outline is a
45
+ * shape as well as a colour.
46
+ *
47
+ * ## Surfaces
48
+ *
49
+ * `soon`, `urgent` and `expired` are tokens all the way down: on the purple
50
+ * panel and on ink the fills resolve to a translucent wash and the words to the
51
+ * surface's text, with no help. `from-email` is a self-contained light card on
52
+ * every surface (`data-theme="default"`, the base scope tokens.css declares
53
+ * beside `:root`), the arrangement `EntityCard`'s panel and `ShowcaseScroll`
54
+ * use: `--cg-primary-tint` does not flip, so ink-on-tint has to be kept inside
55
+ * it rather than inherited as white-on-tint.
56
+ */
57
+ export declare const EXPIRY_NOTICE_VARIANTS: readonly ["soon", "urgent", "from-email", "expired"];
58
+ export type ExpiryNoticeVariant = (typeof EXPIRY_NOTICE_VARIANTS)[number];
59
+ export interface ExpiryNoticeAction {
60
+ /** The word on the button. "Keep" in every spec'd case. */
61
+ label: string;
62
+ onClick: () => void;
63
+ /** While the request is in flight: the button is disabled and says `pendingLabel`. */
64
+ pending?: boolean;
65
+ /** Defaults to "Keeping…". */
66
+ pendingLabel?: string;
67
+ }
68
+ export interface ExpiryNoticeProps extends Omit<React.ComponentProps<"section">, "title" | "children"> {
69
+ variant: ExpiryNoticeVariant;
70
+ /** The sentence. "This page expires in 5 days." */
71
+ title: React.ReactNode;
72
+ /** The reason, or who can act. "Nobody has opened it in 25 days. Keep it and it won't expire." */
73
+ reason?: React.ReactNode;
74
+ /** Leave out for a reader who cannot keep it. */
75
+ action?: ExpiryNoticeAction;
76
+ /**
77
+ * Focus the action when the notice mounts. For the arrival from the email
78
+ * (`from-email`); ignored when there is no action.
79
+ */
80
+ focusAction?: boolean;
81
+ /** The region's name. Defaults to "Expiry". */
82
+ label?: string;
83
+ }
84
+ declare function ExpiryNotice({ variant, title, reason, action, focusAction, label, className, ...props }: ExpiryNoticeProps): React.JSX.Element;
85
+ export { ExpiryNotice };
86
+ //# sourceMappingURL=expiry-notice.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expiry-notice.d.ts","sourceRoot":"","sources":["../../src/blocks/expiry-notice.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAK9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AAEH,eAAO,MAAM,sBAAsB,sDAAuD,CAAA;AAE1F,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,CAAC,CAAA;AAEzE,MAAM,WAAW,kBAAkB;IACjC,2DAA2D;IAC3D,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,sFAAsF;IACtF,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,8BAA8B;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,iBACf,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,SAAS,CAAC,EAAE,OAAO,GAAG,UAAU,CAAC;IACnE,OAAO,EAAE,mBAAmB,CAAA;IAC5B,mDAAmD;IACnD,KAAK,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB,kGAAkG;IAClG,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACxB,iDAAiD;IACjD,MAAM,CAAC,EAAE,kBAAkB,CAAA;IAC3B;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AASD,iBAAS,YAAY,CAAC,EACpB,OAAO,EACP,KAAK,EACL,MAAM,EACN,MAAM,EACN,WAAmB,EACnB,KAAgB,EAChB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,iBAAiB,qBA6DnB;AAED,OAAO,EAAE,YAAY,EAAE,CAAA"}
@@ -0,0 +1,81 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { cn } from "../lib/cloudgrid-cn.js";
5
+ import { Button } from "../ui/button.js";
6
+ /**
7
+ * THE EXPIRY NOTICE. The row an item's page shows while it is about to expire,
8
+ * or after it has (ui#295, spec "Expiry warning and Keep", 2026-10-11).
9
+ *
10
+ * ```
11
+ * This page expires in 5 days. [ Keep ]
12
+ * Nobody has opened it in 25 days. Keep it and it won't expire.
13
+ * ```
14
+ *
15
+ * It is the Console's app-page status row (`AppStateBanner`, the row that says
16
+ * Paused, Building and the rest) with a ground: one sentence, one reason, at
17
+ * most one action, the action under the sentence on a phone. The type matches
18
+ * that row: the sentence 16px semibold, the reason a step down in the secondary
19
+ * tone. The founder-approved mock tints it so it reads as a warning rather than
20
+ * a resting state.
21
+ *
22
+ * ## Variants
23
+ *
24
+ * - `soon`: five days to one day before. The amber of the expired state.
25
+ * - `urgent`: the last day. The danger fill.
26
+ * - `from-email`: the reader arrived from the warning email's Keep link. The
27
+ * brand tint with a 2px brand outline, and the Console passes `focusAction` so
28
+ * Keep has focus and is one press away. The link never keeps by itself, since
29
+ * email scanners open every link; this is the click that does.
30
+ * - `expired`: after expiry, before the Shelf. The same amber as `soon`; the
31
+ * words are what differ ("This page expired.").
32
+ *
33
+ * No action is a variant of its own in effect, not a value: leave `action` out
34
+ * for a member who is not the owner or a grid admin and cannot keep it. The
35
+ * row still says what is happening and the reason line says who can act.
36
+ *
37
+ * ## Accessibility
38
+ *
39
+ * `role="status"`, like the Console row it matches: a polite region, so a
40
+ * notice that appears while the reader is on the page is announced without
41
+ * taking focus. Not `alert`: nothing here is an error and nothing interrupts.
42
+ * The region is named (`label`, "Expiry" by default) so a reader moving by
43
+ * region hears what it is about.
44
+ *
45
+ * The action is a real `<button>`, described by the sentence
46
+ * (`aria-describedby`), so a screen reader that lands on "Keep" hears what it
47
+ * keeps and when it expires. The tone is never colour alone: the sentence
48
+ * carries it ("expires tomorrow", "expired"), and the from-email outline is a
49
+ * shape as well as a colour.
50
+ *
51
+ * ## Surfaces
52
+ *
53
+ * `soon`, `urgent` and `expired` are tokens all the way down: on the purple
54
+ * panel and on ink the fills resolve to a translucent wash and the words to the
55
+ * surface's text, with no help. `from-email` is a self-contained light card on
56
+ * every surface (`data-theme="default"`, the base scope tokens.css declares
57
+ * beside `:root`), the arrangement `EntityCard`'s panel and `ShowcaseScroll`
58
+ * use: `--cg-primary-tint` does not flip, so ink-on-tint has to be kept inside
59
+ * it rather than inherited as white-on-tint.
60
+ */
61
+ export const EXPIRY_NOTICE_VARIANTS = ["soon", "urgent", "from-email", "expired"];
62
+ const VARIANT_STYLE = {
63
+ soon: "bg-expired-fill",
64
+ urgent: "bg-danger-fill",
65
+ "from-email": "bg-brand-tint inset-ring-2 inset-ring-brand",
66
+ expired: "bg-expired-fill",
67
+ };
68
+ function ExpiryNotice({ variant, title, reason, action, focusAction = false, label = "Expiry", className, ...props }) {
69
+ const titleId = React.useId();
70
+ const buttonRef = React.useRef(null);
71
+ React.useEffect(() => {
72
+ // On arrival, and again only if the prop itself turns on. Not on every
73
+ // render: that would steal focus back from a reader who has moved on.
74
+ if (focusAction)
75
+ buttonRef.current?.focus();
76
+ }, [focusAction]);
77
+ const pending = action?.pending ?? false;
78
+ return (_jsxs("section", { role: "status", "aria-label": label, "data-slot": "expiry-notice", "data-variant": variant, ...(variant === "from-email" ? { "data-theme": "default" } : {}), className: cn("flex flex-wrap items-center justify-between gap-4 rounded-xl p-5 text-ink", "max-md:flex-col max-md:items-stretch", VARIANT_STYLE[variant], className), ...props, children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("p", { id: titleId, "data-slot": "expiry-notice-title", className: "m-0 text-body-md font-semibold text-ink", children: title }), reason ? (_jsx("p", { "data-slot": "expiry-notice-reason", className: "m-0 mt-1 text-body-sm text-body", children: reason })) : null] }), action ? (_jsx(Button, { ref: buttonRef, type: "button", variant: "cta", "data-slot": "expiry-notice-action", "aria-describedby": titleId, disabled: pending, onClick: action.onClick, className: "shrink-0 rounded-pill px-3.5 max-md:w-full", children: pending ? (action.pendingLabel ?? "Keeping…") : action.label })) : null] }));
79
+ }
80
+ export { ExpiryNotice };
81
+ //# sourceMappingURL=expiry-notice.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expiry-notice.js","sourceRoot":"","sources":["../../src/blocks/expiry-notice.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAE,EAAE,EAAE,MAAM,oBAAoB,CAAA;AACvC,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAA;AAE7C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AAEH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,SAAS,CAAU,CAAA;AAgC1F,MAAM,aAAa,GAAwC;IACzD,IAAI,EAAE,iBAAiB;IACvB,MAAM,EAAE,gBAAgB;IACxB,YAAY,EAAE,6CAA6C;IAC3D,OAAO,EAAE,iBAAiB;CAC3B,CAAA;AAED,SAAS,YAAY,CAAC,EACpB,OAAO,EACP,KAAK,EACL,MAAM,EACN,MAAM,EACN,WAAW,GAAG,KAAK,EACnB,KAAK,GAAG,QAAQ,EAChB,SAAS,EACT,GAAG,KAAK,EACU;IAClB,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,EAAE,CAAA;IAC7B,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,CAAoB,IAAI,CAAC,CAAA;IAEvD,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,uEAAuE;QACvE,sEAAsE;QACtE,IAAI,WAAW;YAAE,SAAS,CAAC,OAAO,EAAE,KAAK,EAAE,CAAA;IAC7C,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAA;IAEjB,MAAM,OAAO,GAAG,MAAM,EAAE,OAAO,IAAI,KAAK,CAAA;IAExC,OAAO,CACL,mBACE,IAAI,EAAC,QAAQ,gBACD,KAAK,eACP,eAAe,kBACX,OAAO,KACjB,CAAC,OAAO,KAAK,YAAY,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EACjE,SAAS,EAAE,EAAE,CACX,2EAA2E,EAC3E,sCAAsC,EACtC,aAAa,CAAC,OAAO,CAAC,EACtB,SAAS,CACV,KACG,KAAK,aAET,eAAK,SAAS,EAAC,gBAAgB,aAC7B,YACE,EAAE,EAAE,OAAO,eACD,qBAAqB,EAC/B,SAAS,EAAC,yCAAyC,YAElD,KAAK,GACJ,EACH,MAAM,CAAC,CAAC,CAAC,CACR,yBAAa,sBAAsB,EAAC,SAAS,EAAC,iCAAiC,YAC5E,MAAM,GACL,CACL,CAAC,CAAC,CAAC,IAAI,IACJ,EAEL,MAAM,CAAC,CAAC,CAAC,CACR,KAAC,MAAM,IACL,GAAG,EAAE,SAAS,EACd,IAAI,EAAC,QAAQ,EACb,OAAO,EAAC,KAAK,eAIH,sBAAsB,sBACd,OAAO,EACzB,QAAQ,EAAE,OAAO,EACjB,OAAO,EAAE,MAAM,CAAC,OAAO,EACvB,SAAS,EAAC,4CAA4C,YAErD,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,YAAY,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,GACtD,CACV,CAAC,CAAC,CAAC,IAAI,IACA,CACX,CAAA;AACH,CAAC;AAED,OAAO,EAAE,YAAY,EAAE,CAAA"}
@@ -371,6 +371,28 @@ export { TerminalWindowMark as TerminalIcon, ChatCircleMark as ChatAppIcon, Gith
371
371
  export { ArchiveMark as JunkyardIcon, } from "./phosphor-marks.js";
372
372
  /** Reactions: what a person can say about an entity without writing anything. */
373
373
  export { FireMark as ReactionResonatedIcon, HeartMark as ReactionLoveIcon, WrenchMark as ReactionUsefulIcon, SquaresFourMark as ReactionBuildOnIcon, StarMark as ReactionImpressiveIcon, } from "./phosphor-marks.js";
374
+ /**
375
+ * The badge's reaction set (#299, founder spec 2026-10-12): Like, Dislike,
376
+ * Clever idea, Ship it, Something's broken. It supersedes the Decision 034 set
377
+ * above on the badge and in the console's Feedback page; those five names stay
378
+ * exported because retired kinds are still read in history.
379
+ *
380
+ * `ReactionLikeIcon` is `thumbs-up`, the same artwork as `ReactionsIcon`, and
381
+ * `ReactionIdeaIcon` is `lightbulb-filament`, the same as `KindInspirationIcon`.
382
+ * Second role names, not renames: a count of all reactions and one reaction
383
+ * kind are two ideas, and so are an entity kind and a reaction. The
384
+ * `SpaceIcon` / `OverviewIcon` precedent (#225).
385
+ */
386
+ export { ThumbsUpMark as ReactionLikeIcon, ThumbsDownMark as ReactionDislikeIcon, LightbulbFilamentMark as ReactionIdeaIcon, RocketLaunchMark as ReactionShipItIcon, BugMark as ReactionBrokenIcon, } from "./phosphor-marks.js";
387
+ /**
388
+ * Sharing a link, and what nobody but you can see (#299).
389
+ *
390
+ * `HiddenIcon` is `eye-slash`: something nobody but you can see. It is NOT
391
+ * `PrivateIcon` (`lock-simple`), which says who can OPEN a thing; this says who
392
+ * can SEE it. Generated for the badge's owner panel; the founder then set that
393
+ * line as plain text (2026-10-12) and kept the mark.
394
+ */
395
+ export { ShareNetworkMark as ShareIcon, EyeSlashMark as HiddenIcon, } from "./phosphor-marks.js";
374
396
  /** The weight departures, each replacing a numeric prop lucide used to carry. */
375
397
  export { PulseBoldMark as ActivityBoldIcon, CompassBoldMark as DiscoverBoldIcon, StackBoldMark as LayersBoldIcon, UserBoldMark as PersonBoldIcon, HouseSimpleBoldMark as HomeBoldIcon, GridFourBoldMark as SpaceBoldIcon, PushPinFillMark as SaveFilledIcon, } from "./phosphor-marks.js";
376
398
  /**