@orangecheck/app-design 0.2.0 → 0.3.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.
@@ -6,6 +6,29 @@ var tailwindMerge = require('tailwind-merge');
6
6
  var jsxRuntime = require('react/jsx-runtime');
7
7
  var classVarianceAuthority = require('class-variance-authority');
8
8
  var lucideReact = require('lucide-react');
9
+ var reactSlot = require('@radix-ui/react-slot');
10
+ var SeparatorPrimitive = require('@radix-ui/react-separator');
11
+ require('sonner');
12
+
13
+ function _interopNamespace(e) {
14
+ if (e && e.__esModule) return e;
15
+ var n = Object.create(null);
16
+ if (e) {
17
+ Object.keys(e).forEach(function (k) {
18
+ if (k !== 'default') {
19
+ var d = Object.getOwnPropertyDescriptor(e, k);
20
+ Object.defineProperty(n, k, d.get ? d : {
21
+ enumerable: true,
22
+ get: function () { return e[k]; }
23
+ });
24
+ }
25
+ });
26
+ }
27
+ n.default = e;
28
+ return Object.freeze(n);
29
+ }
30
+
31
+ var SeparatorPrimitive__namespace = /*#__PURE__*/_interopNamespace(SeparatorPrimitive);
9
32
 
10
33
  var merge = tailwindMerge.extendTailwindMerge({
11
34
  extend: {
@@ -49,7 +72,7 @@ function Prose({ className, ...props }) {
49
72
  {
50
73
  className: cn(
51
74
  "max-w-[42rem] text-[1.0625rem] leading-[1.7] text-muted-foreground",
52
- "[&_h2]:oca-display-3 [&_h2]:mb-4 [&_h2]:mt-14 [&_h2]:text-foreground",
75
+ "[&_h2]:oca-display-3 [&_h2]:mb-4 [&_h2]:mt-14 [&_h2]:scroll-mt-24 [&_h2]:text-foreground",
53
76
  "[&_h3]:oca-title [&_h3]:mb-3 [&_h3]:mt-10 [&_h3]:text-foreground",
54
77
  "[&_p]:my-4 [&_strong]:font-medium [&_strong]:text-foreground",
55
78
  "[&_a]:text-foreground [&_a]:underline [&_a]:decoration-border [&_a]:underline-offset-4 hover:[&_a]:decoration-foreground",
@@ -65,7 +88,9 @@ var sectionVariants = classVarianceAuthority.cva("relative w-full overflow-x-cli
65
88
  tone: {
66
89
  canvas: "bg-background text-foreground",
67
90
  card: "bg-card text-card-foreground",
68
- ink: "bg-oca-ink text-oca-ink-foreground",
91
+ // The band line is transparent in light; in dark it keeps the ink
92
+ // band from melting into the canvas.
93
+ ink: "border-y border-[var(--oca-band-line)] bg-oca-ink text-oca-ink-foreground",
69
94
  brand: "bg-brand text-brand-foreground"
70
95
  },
71
96
  space: {
@@ -100,6 +125,121 @@ function SectionIntro({
100
125
  }
101
126
  );
102
127
  }
128
+ function PageHero({
129
+ eyebrow,
130
+ title,
131
+ lead,
132
+ actions,
133
+ art,
134
+ meta,
135
+ children,
136
+ variant = "page",
137
+ size = "lg",
138
+ className
139
+ }) {
140
+ if (variant === "document") {
141
+ return /* @__PURE__ */ jsxRuntime.jsx(Section, { space: "none", className: cn("pb-12 pt-12 md:pb-16 md:pt-20", className), children: /* @__PURE__ */ jsxRuntime.jsx(DocHeader, { className: "mx-auto max-w-[42rem]", ...{ eyebrow, title, lead, meta, actions }, children }) });
142
+ }
143
+ return /* @__PURE__ */ jsxRuntime.jsx(Section, { space: "none", className: cn("pb-14 pt-12 md:pb-20 md:pt-20", className), children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("grid grid-cols-1 gap-10", art && "items-center md:grid-cols-12"), children: [
144
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex flex-col items-start gap-6", art ? "md:col-span-7" : "max-w-[52rem]"), children: [
145
+ eyebrow ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "oca-eyebrow", children: eyebrow }) : null,
146
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: size === "xl" ? "oca-display-1" : "oca-display-2", children: title }),
147
+ lead ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "oca-lead max-w-[38rem]", children: lead }) : null,
148
+ meta ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[0.875rem] text-muted-foreground", children: meta }) : null,
149
+ actions ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-3", children: actions }) : null,
150
+ children
151
+ ] }),
152
+ art ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "md:col-span-5", children: art }) : null
153
+ ] }) });
154
+ }
155
+ function DocHeader({
156
+ eyebrow,
157
+ title,
158
+ lead,
159
+ meta,
160
+ actions,
161
+ children,
162
+ className
163
+ }) {
164
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex flex-col gap-5", className), children: [
165
+ eyebrow ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "oca-eyebrow", children: eyebrow }) : null,
166
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "oca-display-2", children: title }),
167
+ lead ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "oca-lead", children: lead }) : null,
168
+ meta ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[0.875rem] text-muted-foreground", children: meta }) : null,
169
+ actions ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-1 flex flex-wrap gap-3", children: actions }) : null,
170
+ children
171
+ ] });
172
+ }
173
+ function useActiveHeading(ids) {
174
+ const [active, setActive] = react.useState(ids[0]);
175
+ const key = ids.join("|");
176
+ react.useEffect(() => {
177
+ const els = key.split("|").map((id) => document.getElementById(id)).filter((el) => el !== null);
178
+ if (els.length === 0) return;
179
+ const io = new IntersectionObserver(
180
+ (entries) => {
181
+ const hit = entries.filter((e) => e.isIntersecting).sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top)[0];
182
+ if (hit) setActive(hit.target.id);
183
+ },
184
+ { rootMargin: "-80px 0px -65% 0px" }
185
+ );
186
+ els.forEach((el) => io.observe(el));
187
+ return () => io.disconnect();
188
+ }, [key]);
189
+ return active;
190
+ }
191
+ function DocLayout({
192
+ toc,
193
+ header,
194
+ children,
195
+ className
196
+ }) {
197
+ const active = useActiveHeading(toc?.map((t) => t.id) ?? []);
198
+ const links = () => toc?.map((t) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsx(
199
+ "a",
200
+ {
201
+ href: `#${t.id}`,
202
+ "aria-current": active === t.id ? "location" : void 0,
203
+ className: cn(
204
+ "block border-l py-1.5 pl-4 text-[0.875rem] leading-snug transition-colors",
205
+ active === t.id ? "border-foreground text-foreground" : "border-border text-muted-foreground hover:text-foreground"
206
+ ),
207
+ children: t.label
208
+ }
209
+ ) }, t.id));
210
+ const hasToc = !!toc && toc.length > 0;
211
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("oca-container pb-24", header ? "pt-12 md:pt-20" : null, className), children: /* @__PURE__ */ jsxRuntime.jsxs(
212
+ "div",
213
+ {
214
+ className: cn(
215
+ "mx-auto grid grid-cols-1 gap-8",
216
+ hasToc ? "max-w-[42rem] lg:max-w-[64rem] lg:grid-cols-[13rem_minmax(0,42rem)] lg:gap-x-16 lg:gap-y-10" : "max-w-[42rem]"
217
+ ),
218
+ children: [
219
+ header ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn(hasToc && "lg:col-start-2", "pb-4"), children: header }) : null,
220
+ hasToc ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
221
+ /* @__PURE__ */ jsxRuntime.jsxs(
222
+ "nav",
223
+ {
224
+ "aria-label": "On this page",
225
+ className: cn("sticky top-24 hidden self-start lg:block", header && "lg:row-start-2"),
226
+ children: [
227
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-3 text-[0.875rem] font-medium", children: "On this page" }),
228
+ /* @__PURE__ */ jsxRuntime.jsx("ul", { children: links() })
229
+ ]
230
+ }
231
+ ),
232
+ /* @__PURE__ */ jsxRuntime.jsxs("details", { className: "group rounded-2xl border border-border bg-card px-4 py-3 lg:hidden", children: [
233
+ /* @__PURE__ */ jsxRuntime.jsx("summary", { className: "cursor-pointer list-none text-[0.9375rem] font-medium marker:hidden", children: "On this page" }),
234
+ /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "mt-3", children: links() })
235
+ ] })
236
+ ] }) : null,
237
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("min-w-0", hasToc && "lg:col-start-2", hasToc && header && "lg:row-start-2"), children })
238
+ ]
239
+ }
240
+ ) });
241
+ }
242
+ var GOOD_DOT = "border-border bg-card text-foreground before:size-1.5 before:shrink-0 before:rounded-full before:bg-success before:content-['']";
103
243
  var badgeVariants = classVarianceAuthority.cva(
104
244
  "inline-flex h-6 shrink-0 items-center gap-1.5 whitespace-nowrap rounded-full border px-2.5 text-[0.8125rem] font-medium leading-none",
105
245
  {
@@ -108,10 +248,13 @@ var badgeVariants = classVarianceAuthority.cva(
108
248
  neutral: "border-border bg-card text-foreground",
109
249
  muted: "border-transparent bg-muted text-muted-foreground",
110
250
  brand: "border-transparent bg-[color-mix(in_oklab,var(--oca-orange)_12%,var(--card))] text-oca-accent-ink",
111
- success: "border-transparent bg-[color-mix(in_oklab,var(--success)_12%,var(--card))] text-success",
112
- warning: "border-transparent bg-[color-mix(in_oklab,var(--warning)_14%,var(--card))] text-warning",
113
251
  danger: "border-transparent bg-[color-mix(in_oklab,var(--destructive)_12%,var(--card))] text-destructive",
114
- info: "border-transparent bg-[color-mix(in_oklab,var(--info)_12%,var(--card))] text-info",
252
+ // A good state is a neutral pill with a green dot, not a green pill.
253
+ success: GOOD_DOT,
254
+ // Aliases kept so older call sites keep compiling: attention is
255
+ // the brand accent, information is neutral.
256
+ warning: "border-transparent bg-[color-mix(in_oklab,var(--oca-orange)_12%,var(--card))] text-oca-accent-ink",
257
+ info: "border-border bg-card text-foreground",
115
258
  ink: "border-transparent bg-oca-ink text-oca-ink-foreground",
116
259
  onInk: "border-white/15 bg-white/5 text-white/90"
117
260
  }
@@ -179,208 +322,160 @@ function StepGrid({ items, look = "cards", className }) {
179
322
  ] }) }, i)
180
323
  ) });
181
324
  }
182
- var KIND = {
183
- package: "text-oca-code-package",
184
- tag: "text-oca-code-tag",
185
- link: "text-oca-code-link"
186
- };
187
- function PackageCard({ items, className }) {
188
- return /* @__PURE__ */ jsxRuntime.jsx(
189
- "div",
190
- {
191
- className: cn(
192
- "grid gap-x-10 gap-y-7 rounded-[1.25rem] border border-oca-ink-line bg-oca-ink-card p-6 text-oca-ink-foreground md:grid-cols-2 md:p-9",
193
- className
194
- ),
195
- children: items.map((it, i) => {
196
- const code = /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("text-[0.9375rem]", KIND[it.kind ?? "package"]), children: it.code });
197
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-col gap-1.5", children: [
198
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-[1.125rem] font-semibold tracking-[-0.01em] text-oca-ink-title", children: it.title }),
199
- it.href ? /* @__PURE__ */ jsxRuntime.jsx("a", { href: it.href, className: "w-fit underline decoration-transparent underline-offset-4 hover:decoration-current", children: code }) : code,
200
- it.note ? /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-[0.9375rem] leading-relaxed text-oca-ink-subtle", children: [
201
- /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, children: "// " }),
202
- it.note
203
- ] }) : null
204
- ] }, i);
205
- })
206
- }
207
- );
208
- }
209
- function CodeBlock({
210
- code,
211
- caption,
212
- className
213
- }) {
214
- return /* @__PURE__ */ jsxRuntime.jsxs("figure", { className: cn("overflow-hidden rounded-[1.25rem] border border-oca-ink-line bg-oca-ink-card", className), children: [
215
- caption ? /* @__PURE__ */ jsxRuntime.jsx("figcaption", { className: "border-b border-oca-ink-line px-5 py-3 text-[0.8125rem] text-oca-ink-subtle", children: caption }) : null,
216
- /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "overflow-x-auto p-5 font-mono text-[0.875rem] leading-relaxed text-oca-ink-muted", children: /* @__PURE__ */ jsxRuntime.jsx("code", { children: code }) })
217
- ] });
218
- }
219
- function SplitBar({
220
- segments,
221
- caption,
222
- className
223
- }) {
224
- return /* @__PURE__ */ jsxRuntime.jsxs("figure", { className: cn("flex flex-col gap-5", className), children: [
225
- caption ? /* @__PURE__ */ jsxRuntime.jsx("figcaption", { className: "text-[0.9375rem]", children: caption }) : null,
226
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-16 w-full gap-1.5 overflow-hidden rounded-[1.25rem] md:h-20", children: segments.map((s, i) => /* @__PURE__ */ jsxRuntime.jsx(
227
- "div",
325
+ function ScrollFade({ fade = "var(--card)", className, innerClassName, children, ...props }) {
326
+ const ref = react.useRef(null);
327
+ const [edges, setEdges] = react.useState({ left: false, right: false });
328
+ const measure = react.useCallback(() => {
329
+ const el = ref.current;
330
+ if (!el) return;
331
+ const left = el.scrollLeft > 1;
332
+ const right = el.scrollLeft + el.clientWidth < el.scrollWidth - 1;
333
+ setEdges((e) => e.left === left && e.right === right ? e : { left, right });
334
+ }, []);
335
+ react.useEffect(() => {
336
+ const el = ref.current;
337
+ if (!el) return;
338
+ measure();
339
+ const ro = new ResizeObserver(measure);
340
+ ro.observe(el);
341
+ if (el.firstElementChild) ro.observe(el.firstElementChild);
342
+ return () => ro.disconnect();
343
+ }, [measure]);
344
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("relative min-w-0", className), ...props, children: [
345
+ /* @__PURE__ */ jsxRuntime.jsx("div", { ref, onScroll: measure, className: cn("overflow-x-auto overscroll-x-contain", innerClassName), children }),
346
+ /* @__PURE__ */ jsxRuntime.jsx(
347
+ "span",
228
348
  {
349
+ "aria-hidden": true,
229
350
  className: cn(
230
- "flex min-w-0 items-end rounded-[1rem] px-4 pb-3",
231
- s.tone === "card" ? "bg-white text-oca-ink" : "bg-oca-ink text-white"
351
+ "pointer-events-none absolute inset-y-0 left-0 w-8 transition-opacity duration-150",
352
+ edges.left ? "opacity-100" : "opacity-0"
232
353
  ),
233
- style: { flexBasis: `${s.pct}%` },
234
- children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "oca-tabular text-[1.5rem] leading-none tracking-[-0.02em] md:text-[1.875rem]", children: [
235
- s.pct,
236
- "%"
237
- ] })
238
- },
239
- i
240
- )) }),
241
- /* @__PURE__ */ jsxRuntime.jsx("dl", { className: "grid gap-x-8 gap-y-2 sm:grid-cols-2", children: segments.map((s, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-baseline gap-2.5", children: [
242
- /* @__PURE__ */ jsxRuntime.jsxs("dt", { className: "sr-only", children: [
243
- s.pct,
244
- "%"
245
- ] }),
246
- /* @__PURE__ */ jsxRuntime.jsx(
247
- "span",
248
- {
249
- "aria-hidden": true,
250
- className: cn(
251
- "inline-block size-2.5 shrink-0 translate-y-[-1px] rounded-full",
252
- s.tone === "card" ? "bg-white" : "bg-oca-ink"
253
- )
254
- }
255
- ),
256
- /* @__PURE__ */ jsxRuntime.jsx("dd", { className: "text-[0.9375rem] leading-snug", children: s.label })
257
- ] }, i)) })
258
- ] });
259
- }
260
- var LABEL = {
261
- yes: "Yes",
262
- testing: "In testing",
263
- "not-yet": "Not yet",
264
- no: "No"
265
- };
266
- var TONE = {
267
- yes: "text-oca-accent-ink",
268
- testing: "text-foreground",
269
- "not-yet": "text-muted-foreground",
270
- no: "text-muted-foreground"
271
- };
272
- function TruthTable({
273
- rows,
274
- statusHeading = "Status",
275
- whatHeading = "What",
276
- className
277
- }) {
278
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("overflow-hidden rounded-xl border border-oca-hairline-strong", className), children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "w-full border-collapse text-left text-[0.9375rem] md:text-[1rem]", children: [
279
- /* @__PURE__ */ jsxRuntime.jsx("thead", { className: "bg-oca-table-head", children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
280
- /* @__PURE__ */ jsxRuntime.jsx("th", { scope: "col", className: "w-[30%] border-b border-oca-hairline-strong px-4 py-3 font-semibold md:w-[22%]", children: statusHeading }),
281
- /* @__PURE__ */ jsxRuntime.jsx("th", { scope: "col", className: "border-b border-l border-oca-hairline-strong px-4 py-3 font-semibold", children: whatHeading })
282
- ] }) }),
283
- /* @__PURE__ */ jsxRuntime.jsx("tbody", { className: "bg-card", children: rows.map((r, i) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "align-top", children: [
284
- /* @__PURE__ */ jsxRuntime.jsx(
285
- "td",
286
- {
287
- className: cn(
288
- "border-t border-oca-hairline-strong px-4 py-3 font-medium",
289
- i === 0 && "border-t-0",
290
- TONE[r.status]
291
- ),
292
- children: LABEL[r.status]
293
- }
294
- ),
295
- /* @__PURE__ */ jsxRuntime.jsx(
296
- "td",
297
- {
298
- className: cn(
299
- "border-l border-t border-oca-hairline-strong px-4 py-3 leading-relaxed",
300
- i === 0 && "border-t-0"
301
- ),
302
- children: r.what
303
- }
304
- )
305
- ] }, i)) })
306
- ] }) });
307
- }
308
- var INT = new Intl.NumberFormat("en-US", { maximumFractionDigits: 0 });
309
- var COMPACT = new Intl.NumberFormat("en-US", { notation: "compact", maximumFractionDigits: 1 });
310
- function satsText(value, opts = {}) {
311
- if (value == null || !Number.isFinite(value)) return null;
312
- const whole = Math.trunc(value);
313
- const body = (opts.compact ? COMPACT : INT).format(Math.abs(whole));
314
- const sign = whole < 0 ? "\u2212" : opts.signed && whole > 0 ? "+" : "";
315
- const unit = opts.unit === "none" ? "" : whole === 1 || whole === -1 ? " sat" : " sats";
316
- return `${sign}${body}${unit}`;
317
- }
318
- function Sats({ value, unit, compact, signed, unknown = "\u2014", className, ...props }) {
319
- const text = satsText(value, { unit, compact, signed });
320
- if (text === null) {
321
- return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: cn("oca-tabular text-muted-foreground", className), ...props, children: [
322
- /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, children: unknown }),
323
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sr-only", children: "unknown" })
324
- ] });
325
- }
326
- return /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("oca-tabular whitespace-nowrap", className), ...props, children: text });
327
- }
328
- function UsdApprox({
329
- sats,
330
- usdPerBtc,
331
- className
332
- }) {
333
- if (sats == null || usdPerBtc == null || !Number.isFinite(sats) || !Number.isFinite(usdPerBtc)) return null;
334
- const usd = sats / 1e8 * usdPerBtc;
335
- const text = new Intl.NumberFormat("en-US", {
336
- style: "currency",
337
- currency: "USD",
338
- maximumFractionDigits: usd < 1 ? 4 : 2
339
- }).format(usd);
340
- return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: cn("oca-tabular text-muted-foreground", className), children: [
341
- "\u2248 ",
342
- text
354
+ style: { background: `linear-gradient(to right, ${fade}, transparent)` }
355
+ }
356
+ ),
357
+ /* @__PURE__ */ jsxRuntime.jsx(
358
+ "span",
359
+ {
360
+ "aria-hidden": true,
361
+ className: cn(
362
+ "pointer-events-none absolute inset-y-0 right-0 w-10 transition-opacity duration-150",
363
+ edges.right ? "opacity-100" : "opacity-0"
364
+ ),
365
+ style: { background: `linear-gradient(to left, ${fade}, transparent)` }
366
+ }
367
+ )
343
368
  ] });
344
369
  }
345
- function SatsFigure({
346
- label,
347
- value,
348
- hint,
349
- className
350
- }) {
351
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex flex-col gap-1.5", className), children: [
352
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[0.9375rem] text-muted-foreground", children: label }),
353
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "oca-display-3 oca-tabular", children: [
354
- /* @__PURE__ */ jsxRuntime.jsx(Sats, { value, unit: "none" }),
355
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-2 text-[0.5em] tracking-normal text-muted-foreground", children: "sats" })
356
- ] }),
357
- hint ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[0.875rem] text-muted-foreground", children: hint }) : null
358
- ] });
370
+ var ShellContext = react.createContext({});
371
+ function useShellPlace() {
372
+ return react.useContext(ShellContext);
359
373
  }
360
- var TOP = "0,3 2,2 6,4 14,0 16,1 6,6";
361
- var LEFT = "0,3 6,6 6,8 0,5";
362
- var RIGHT = "6,6 16,1 16,3 6,8";
363
- var MARK_FACES = { top: "#FED8AB", left: "#FB933C", right: "#EA580B" };
364
- function Mark({ title, size, className, ...rest }) {
374
+ function Spinner({ className, label }) {
365
375
  return /* @__PURE__ */ jsxRuntime.jsxs(
366
376
  "svg",
367
377
  {
368
- viewBox: "0 0 16 8",
369
- width: size,
370
- height: size ? size / 2 : void 0,
371
- role: title ? "img" : void 0,
372
- "aria-hidden": title ? void 0 : true,
373
- className: cn("shrink-0", className),
374
- ...rest,
378
+ viewBox: "0 0 24 24",
379
+ fill: "none",
380
+ role: label ? "status" : void 0,
381
+ "aria-label": label,
382
+ "aria-hidden": label ? void 0 : true,
383
+ className: cn("size-4 animate-spin", className),
375
384
  children: [
376
- title ? /* @__PURE__ */ jsxRuntime.jsx("title", { children: title }) : null,
377
- /* @__PURE__ */ jsxRuntime.jsx("polygon", { points: LEFT, fill: MARK_FACES.left }),
378
- /* @__PURE__ */ jsxRuntime.jsx("polygon", { points: RIGHT, fill: MARK_FACES.right }),
379
- /* @__PURE__ */ jsxRuntime.jsx("polygon", { points: TOP, fill: MARK_FACES.top })
385
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "9", stroke: "currentColor", strokeOpacity: "0.2", strokeWidth: "2.5" }),
386
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M21 12a9 9 0 0 0-9-9", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round" })
380
387
  ]
381
388
  }
382
389
  );
383
390
  }
391
+ function Skeleton({ className, ...props }) {
392
+ return /* @__PURE__ */ jsxRuntime.jsx(
393
+ "div",
394
+ {
395
+ "data-ui": "skeleton",
396
+ className: cn("animate-pulse rounded-lg bg-[var(--oca-skeleton)]", className),
397
+ ...props
398
+ }
399
+ );
400
+ }
401
+ function useTimedOut(active, ms = 12e3) {
402
+ const [timedOut, setTimedOut] = react.useState(false);
403
+ react.useEffect(() => {
404
+ const t = setTimeout(() => setTimedOut(true), ms);
405
+ return () => clearTimeout(t);
406
+ }, [active, ms]);
407
+ return timedOut;
408
+ }
409
+ react.forwardRef(function Separator2({ className, orientation = "horizontal", decorative = true, ...props }, ref) {
410
+ return /* @__PURE__ */ jsxRuntime.jsx(
411
+ SeparatorPrimitive__namespace.Root,
412
+ {
413
+ ref,
414
+ decorative,
415
+ orientation,
416
+ className: cn(
417
+ "shrink-0 bg-border",
418
+ orientation === "horizontal" ? "h-px w-full" : "h-full w-px",
419
+ className
420
+ ),
421
+ ...props
422
+ }
423
+ );
424
+ });
425
+ var buttonVariants = classVarianceAuthority.cva(
426
+ [
427
+ "inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-full font-medium",
428
+ "transition-[background-color,border-color,color,box-shadow,transform] duration-150",
429
+ "outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
430
+ "active:translate-y-px disabled:pointer-events-none",
431
+ // Disabled is one look for every variant: a quiet grey pill, never a
432
+ // faded black or orange slab that reads as broken.
433
+ "disabled:border disabled:border-border disabled:bg-muted disabled:text-oca-subtle disabled:shadow-none",
434
+ "[&_svg]:pointer-events-none [&_svg]:size-[1.1em] [&_svg]:shrink-0"
435
+ ],
436
+ {
437
+ variants: {
438
+ variant: {
439
+ primary: "bg-primary text-primary-foreground hover:bg-primary/85",
440
+ secondary: "border border-border bg-secondary text-secondary-foreground hover:border-oca-hairline-strong hover:bg-accent",
441
+ brand: "bg-brand text-brand-foreground hover:bg-oca-orange-deep",
442
+ ghost: "text-foreground hover:bg-accent",
443
+ danger: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
444
+ link: "h-auto rounded-none px-0 text-foreground underline decoration-border underline-offset-4 hover:decoration-foreground",
445
+ onInk: "bg-white text-oca-ink hover:bg-white/90",
446
+ onInkOutline: "border border-white/20 text-white hover:bg-white/10"
447
+ },
448
+ size: {
449
+ sm: "h-9 px-4 text-sm",
450
+ md: "h-11 px-5 text-[0.9375rem]",
451
+ lg: "h-14 px-7 text-[1.0625rem]",
452
+ icon: "size-10",
453
+ "icon-sm": "size-8"
454
+ }
455
+ },
456
+ compoundVariants: [{ variant: "link", className: "h-auto px-0" }],
457
+ defaultVariants: { variant: "primary", size: "md" }
458
+ }
459
+ );
460
+ var Button = react.forwardRef(function Button2({ className, variant, size, asChild = false, loading = false, disabled, children, type, ...props }, ref) {
461
+ const Comp = asChild ? reactSlot.Slot : "button";
462
+ return /* @__PURE__ */ jsxRuntime.jsx(
463
+ Comp,
464
+ {
465
+ ref,
466
+ "data-ui": "button",
467
+ className: cn(buttonVariants({ variant, size }), className),
468
+ disabled: asChild ? void 0 : disabled || loading,
469
+ "aria-busy": loading || void 0,
470
+ type: asChild ? void 0 : type ?? "button",
471
+ ...props,
472
+ children: asChild ? children : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
473
+ loading ? /* @__PURE__ */ jsxRuntime.jsx(Spinner, { className: "size-[1.1em]" }) : null,
474
+ children
475
+ ] })
476
+ }
477
+ );
478
+ });
384
479
  function PageHeader({
385
480
  eyebrow,
386
481
  title,
@@ -388,29 +483,117 @@ function PageHeader({
388
483
  actions,
389
484
  className
390
485
  }) {
486
+ const place = useShellPlace();
487
+ const eb = eyebrow === void 0 ? place.section : eyebrow;
391
488
  return /* @__PURE__ */ jsxRuntime.jsxs("header", { className: cn("flex flex-col gap-5 md:flex-row md:items-end md:justify-between", className), children: [
392
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex max-w-[44rem] flex-col gap-3", children: [
393
- eyebrow ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "oca-eyebrow", children: eyebrow }) : null,
489
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 max-w-[44rem] flex-col gap-2.5", children: [
490
+ eb ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "oca-eyebrow", children: eb }) : null,
394
491
  /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "oca-display-3", children: title }),
395
- description ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "oca-body text-[1.0625rem]", children: description }) : null
492
+ description ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "oca-body max-w-[40rem] text-[1.0625rem]", children: description }) : null
396
493
  ] }),
397
- actions ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex shrink-0 flex-wrap items-center gap-3", children: actions }) : null
494
+ actions ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap items-center gap-2.5 md:shrink-0 md:justify-end", children: actions }) : null
398
495
  ] });
399
496
  }
400
- function StatCard({
401
- label,
402
- value,
403
- hint,
404
- className
405
- }) {
406
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex flex-col gap-2 rounded-[1.25rem] border border-border bg-card p-5 md:p-6", className), children: [
407
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[0.875rem] text-muted-foreground", children: label }),
408
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "oca-tabular text-[1.75rem] leading-tight tracking-[-0.02em]", children: value }),
409
- hint ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[0.8125rem] leading-snug text-muted-foreground", children: hint }) : null
410
- ] });
497
+ var InStatGrid = react.createContext(false);
498
+ function StatCard({ label, value, hint, className }) {
499
+ const inGrid = react.useContext(InStatGrid);
500
+ return /* @__PURE__ */ jsxRuntime.jsxs(
501
+ "div",
502
+ {
503
+ "data-ui": "stat",
504
+ className: cn(
505
+ "rounded-[1.25rem] border border-border bg-card p-5 md:p-6",
506
+ // In a grid, label, value and hint each share a row with their
507
+ // neighbours, so values line up whatever wraps above them.
508
+ inGrid ? "row-span-3 grid grid-rows-subgrid gap-1.5" : "flex flex-col gap-2",
509
+ className
510
+ ),
511
+ children: [
512
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[0.875rem] leading-snug text-muted-foreground", children: label }),
513
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "self-end break-words oca-tabular text-[1.5rem] leading-tight tracking-[-0.02em] md:text-[1.75rem]", children: value }),
514
+ hint ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[0.8125rem] leading-snug text-muted-foreground", children: hint }) : inGrid ? /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true }) : null
515
+ ]
516
+ }
517
+ );
518
+ }
519
+ var MD_COLS = {
520
+ 1: "md:grid-cols-1",
521
+ 2: "md:grid-cols-2",
522
+ 3: "md:grid-cols-3",
523
+ 4: "md:grid-cols-4"
524
+ };
525
+ var LG_COLS = {
526
+ 1: "lg:grid-cols-1",
527
+ 2: "lg:grid-cols-2",
528
+ 3: "lg:grid-cols-3",
529
+ 4: "lg:grid-cols-4",
530
+ 5: "lg:grid-cols-5"
531
+ };
532
+ var MD_SPAN = {
533
+ 1: "md:col-span-1",
534
+ 2: "md:col-span-2",
535
+ 3: "md:col-span-3",
536
+ 4: "md:col-span-4"
537
+ };
538
+ var LG_SPAN = {
539
+ 1: "lg:col-span-1",
540
+ 2: "lg:col-span-2",
541
+ 3: "lg:col-span-3",
542
+ 4: "lg:col-span-4"
543
+ };
544
+ function statColumns(n) {
545
+ if (n <= 5) return Math.max(1, n);
546
+ if (n % 4 === 0) return 4;
547
+ if (n % 3 === 0) return 3;
548
+ return 4;
411
549
  }
412
- function StatGrid({ className, ...props }) {
413
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("grid grid-cols-2 gap-3 md:grid-cols-4 md:gap-4", className), ...props });
550
+ function lastSpan(n, cols) {
551
+ const rest = n % cols;
552
+ return rest === 0 ? 1 : cols - rest + 1;
553
+ }
554
+ function EmptyLine({ children, className }) {
555
+ return /* @__PURE__ */ jsxRuntime.jsx(
556
+ "p",
557
+ {
558
+ className: cn(
559
+ "rounded-[1.25rem] border border-dashed border-oca-hairline-strong px-5 py-4 text-[0.9375rem] text-muted-foreground",
560
+ className
561
+ ),
562
+ children
563
+ }
564
+ );
565
+ }
566
+ function StatGrid({ columns, empty, isEmpty, className, children, ...props }) {
567
+ const items = react.Children.toArray(children).filter(react.isValidElement);
568
+ const n = items.length;
569
+ const allZero = n > 0 && items.every((el) => "raw" in el.props && (el.props.raw === 0 || el.props.raw === null));
570
+ if (isEmpty ?? allZero) return /* @__PURE__ */ jsxRuntime.jsx(EmptyLine, { className, children: empty ?? "Nothing here yet." });
571
+ const lg = columns ?? statColumns(n);
572
+ const md = lg > 4 ? 3 : lg;
573
+ const mdSpan = lastSpan(n, md);
574
+ const lgSpan = lastSpan(n, lg);
575
+ return /* @__PURE__ */ jsxRuntime.jsx(InStatGrid.Provider, { value: true, children: /* @__PURE__ */ jsxRuntime.jsx(
576
+ "div",
577
+ {
578
+ className: cn(
579
+ "grid gap-3 md:gap-4",
580
+ n === 1 ? "grid-cols-1" : "grid-cols-2",
581
+ MD_COLS[md],
582
+ LG_COLS[lg],
583
+ className
584
+ ),
585
+ ...props,
586
+ children: items.map((el, i) => {
587
+ if (i !== n - 1) return el;
588
+ const span = cn(
589
+ n % 2 === 1 && n > 1 && "max-md:col-span-2",
590
+ (mdSpan > 1 || lgSpan > 1) && MD_SPAN[mdSpan],
591
+ (mdSpan > 1 || lgSpan > 1) && LG_SPAN[lgSpan]
592
+ );
593
+ return span ? react.cloneElement(el, { className: cn(el.props.className, span) }) : el;
594
+ })
595
+ }
596
+ ) });
414
597
  }
415
598
  function KeyValueList({ items, className }) {
416
599
  return /* @__PURE__ */ jsxRuntime.jsx("dl", { className: cn("divide-y divide-border rounded-[1.25rem] border border-border bg-card", className), children: items.map((it, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1 px-5 py-3.5 sm:flex-row sm:items-baseline sm:gap-6", children: [
@@ -424,75 +607,103 @@ function DataTable({
424
607
  rowKey,
425
608
  empty,
426
609
  caption,
610
+ phone = "cards",
427
611
  className
428
612
  }) {
429
613
  if (rows.length === 0 && empty) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: empty });
430
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("overflow-x-auto rounded-[1.25rem] border border-border bg-card", className), children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "w-full border-collapse text-left text-[0.9375rem]", children: [
431
- caption ? /* @__PURE__ */ jsxRuntime.jsx("caption", { className: "sr-only", children: caption }) : null,
432
- /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsx("tr", { className: "border-b border-border", children: columns.map((c) => /* @__PURE__ */ jsxRuntime.jsx(
433
- "th",
434
- {
435
- scope: "col",
436
- className: cn(
437
- "whitespace-nowrap px-5 py-3 text-[0.8125rem] font-medium text-muted-foreground",
438
- c.align === "end" && "text-right",
439
- c.wide && "hidden md:table-cell"
440
- ),
441
- children: c.header
442
- },
443
- c.key
444
- )) }) }),
445
- /* @__PURE__ */ jsxRuntime.jsx("tbody", { className: "divide-y divide-border", children: rows.map((r, i) => /* @__PURE__ */ jsxRuntime.jsx("tr", { className: "transition-colors hover:bg-accent/60", children: columns.map((c) => /* @__PURE__ */ jsxRuntime.jsx(
446
- "td",
614
+ const primary = columns.find((c) => c.primary) ?? columns[0];
615
+ const right = (c) => c.numeric || c.align === "end";
616
+ const table = /* @__PURE__ */ jsxRuntime.jsx(
617
+ "div",
618
+ {
619
+ className: cn(
620
+ "overflow-hidden rounded-[1.25rem] border border-border bg-card",
621
+ phone === "cards" && "max-sm:hidden",
622
+ className
623
+ ),
624
+ children: /* @__PURE__ */ jsxRuntime.jsx(ScrollFade, { fade: "var(--card)", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "w-full border-collapse text-left text-[0.9375rem]", children: [
625
+ caption ? /* @__PURE__ */ jsxRuntime.jsx("caption", { className: "sr-only", children: caption }) : null,
626
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsx("tr", { className: "border-b border-border", children: columns.map((c) => /* @__PURE__ */ jsxRuntime.jsx(
627
+ "th",
628
+ {
629
+ scope: "col",
630
+ style: { width: c.width, minWidth: c.minWidth },
631
+ className: cn(
632
+ "whitespace-nowrap px-5 py-3 text-[0.8125rem] font-medium text-muted-foreground",
633
+ right(c) && "text-right",
634
+ c.wide && "hidden md:table-cell"
635
+ ),
636
+ children: c.header
637
+ },
638
+ c.key
639
+ )) }) }),
640
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { className: "divide-y divide-border", children: rows.map((r, i) => /* @__PURE__ */ jsxRuntime.jsx("tr", { className: "transition-colors hover:bg-accent/60", children: columns.map((c) => /* @__PURE__ */ jsxRuntime.jsx(
641
+ "td",
642
+ {
643
+ style: { minWidth: c.minWidth },
644
+ className: cn(
645
+ "px-5 py-3.5 align-middle",
646
+ right(c) && "text-right",
647
+ c.numeric && "whitespace-nowrap oca-tabular",
648
+ c.wide && "hidden md:table-cell"
649
+ ),
650
+ children: c.truncate ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "truncate", style: { maxWidth: c.width ?? "16rem" }, children: c.cell(r) }) : c.cell(r)
651
+ },
652
+ c.key
653
+ )) }, rowKey(r, i))) })
654
+ ] }) })
655
+ }
656
+ );
657
+ if (phone !== "cards") return table;
658
+ const rest = columns.filter((c) => c !== primary && !c.hideOnPhone);
659
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
660
+ table,
661
+ /* @__PURE__ */ jsxRuntime.jsx(
662
+ "ul",
447
663
  {
448
- className: cn(
449
- "px-5 py-3.5 align-middle",
450
- c.align === "end" && "text-right",
451
- c.wide && "hidden md:table-cell"
452
- ),
453
- children: c.cell(r)
454
- },
455
- c.key
456
- )) }, rowKey(r, i))) })
457
- ] }) });
664
+ "aria-label": caption,
665
+ className: cn("divide-y divide-border rounded-[1.25rem] border border-border bg-card sm:hidden", className),
666
+ children: rows.map((r, i) => /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "flex flex-col gap-2.5 px-4 py-4", children: [
667
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0 break-words text-[0.9375rem] font-medium", children: primary?.cell(r) }),
668
+ rest.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("dl", { className: "grid grid-cols-[minmax(5.5rem,auto)_minmax(0,1fr)] gap-x-4 gap-y-1.5 text-[0.875rem]", children: rest.map((c) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "contents", children: [
669
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { className: "text-muted-foreground", children: c.header }),
670
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { className: cn("min-w-0 break-words", c.numeric && "oca-tabular"), children: c.cell(r) })
671
+ ] }, c.key)) }) : null
672
+ ] }, rowKey(r, i)))
673
+ }
674
+ )
675
+ ] });
458
676
  }
459
677
  function EmptyState({
460
678
  title,
461
679
  children,
462
680
  action,
681
+ icon,
463
682
  className
464
683
  }) {
465
684
  return /* @__PURE__ */ jsxRuntime.jsxs(
466
685
  "div",
467
686
  {
468
687
  className: cn(
469
- "flex flex-col items-center gap-4 rounded-[1.25rem] border border-dashed border-oca-hairline-strong px-6 py-12 text-center",
688
+ "flex flex-col items-center gap-3 rounded-[1.25rem] border border-dashed border-oca-hairline-strong px-6 py-10 text-center",
470
689
  className
471
690
  ),
472
691
  children: [
473
- /* @__PURE__ */ jsxRuntime.jsx(Mark, { size: 56 }),
692
+ icon ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mb-1 flex size-11 items-center justify-center rounded-full bg-muted text-muted-foreground [&_svg]:size-5", children: icon }) : null,
474
693
  /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "oca-title-sm", children: title }),
475
694
  children ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "oca-body max-w-md text-[0.9375rem]", children }) : null,
476
- action ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-1 flex flex-wrap justify-center gap-3", children: action }) : null
695
+ action ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2 flex flex-wrap justify-center gap-3", children: action }) : null
477
696
  ]
478
697
  }
479
698
  );
480
699
  }
481
- var NOTICE = {
482
- neutral: "border-border bg-card",
483
- info: "border-[color-mix(in_oklab,var(--info)_30%,var(--border))] bg-[color-mix(in_oklab,var(--info)_6%,var(--card))]",
484
- success: "border-[color-mix(in_oklab,var(--success)_30%,var(--border))] bg-[color-mix(in_oklab,var(--success)_6%,var(--card))]",
485
- warning: "border-[color-mix(in_oklab,var(--warning)_35%,var(--border))] bg-[color-mix(in_oklab,var(--warning)_7%,var(--card))]",
486
- danger: "border-[color-mix(in_oklab,var(--destructive)_32%,var(--border))] bg-[color-mix(in_oklab,var(--destructive)_6%,var(--card))]",
487
- brand: "border-[color-mix(in_oklab,var(--oca-orange)_35%,var(--border))] bg-[color-mix(in_oklab,var(--oca-orange)_7%,var(--card))]"
488
- };
489
- var NOTICE_TITLE = {
490
- neutral: "text-foreground",
491
- info: "text-info",
492
- success: "text-success",
493
- warning: "text-warning",
494
- danger: "text-destructive",
495
- brand: "text-oca-accent-ink"
700
+ var NOTICE_KIND = {
701
+ neutral: "neutral",
702
+ info: "neutral",
703
+ success: "neutral",
704
+ brand: "brand",
705
+ warning: "brand",
706
+ danger: "danger"
496
707
  };
497
708
  function Notice({
498
709
  tone = "neutral",
@@ -501,25 +712,60 @@ function Notice({
501
712
  action,
502
713
  className
503
714
  }) {
715
+ const kind = NOTICE_KIND[tone];
504
716
  return /* @__PURE__ */ jsxRuntime.jsxs(
505
717
  "div",
506
718
  {
507
719
  role: tone === "danger" || tone === "warning" ? "alert" : "note",
720
+ "data-tone": kind,
508
721
  className: cn(
509
- "flex flex-col gap-3 rounded-2xl border px-5 py-4 sm:flex-row sm:items-start sm:justify-between",
510
- NOTICE[tone],
722
+ "flex flex-col gap-3 rounded-2xl border border-border bg-card px-5 py-4 sm:flex-row sm:items-start sm:justify-between",
723
+ kind === "danger" && "border-[color-mix(in_oklab,var(--destructive)_40%,var(--border))]",
511
724
  className
512
725
  ),
513
726
  children: [
514
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-col gap-1", children: [
515
- title ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: cn("text-[0.9375rem] font-medium", NOTICE_TITLE[tone]), children: title }) : null,
516
- children ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-[0.9375rem] leading-relaxed text-foreground/85", children }) : null
727
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 gap-3", children: [
728
+ kind !== "neutral" ? /* @__PURE__ */ jsxRuntime.jsx(
729
+ "span",
730
+ {
731
+ "aria-hidden": true,
732
+ className: cn(
733
+ "mt-[0.45rem] size-2 shrink-0 rounded-full",
734
+ kind === "brand" ? "bg-oca-orange" : "bg-destructive"
735
+ )
736
+ }
737
+ ) : null,
738
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-col gap-1", children: [
739
+ title ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: cn("text-[0.9375rem] font-medium", kind === "danger" && "text-destructive"), children: title }) : null,
740
+ children ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-[0.9375rem] leading-relaxed text-muted-foreground", children }) : null
741
+ ] })
517
742
  ] }),
518
743
  action ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "shrink-0", children: action }) : null
519
744
  ]
520
745
  }
521
746
  );
522
747
  }
748
+ function LoadingBlock({
749
+ rows = 3,
750
+ timeoutMs = 12e3,
751
+ onRetry,
752
+ label = "Loading",
753
+ className
754
+ }) {
755
+ const timedOut = useTimedOut(true, timeoutMs);
756
+ if (timedOut) {
757
+ return /* @__PURE__ */ jsxRuntime.jsx(
758
+ Notice,
759
+ {
760
+ className,
761
+ title: "This is taking longer than it should.",
762
+ action: /* @__PURE__ */ jsxRuntime.jsx(Button, { size: "sm", variant: "secondary", onClick: onRetry ?? (() => window.location.reload()), children: "Try again" }),
763
+ children: "Check your connection, then try again."
764
+ }
765
+ );
766
+ }
767
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "status", "aria-label": label, className: cn("flex flex-col gap-3", className), children: Array.from({ length: rows }, (_, i) => /* @__PURE__ */ jsxRuntime.jsx(Skeleton, { className: "h-16 rounded-2xl" }, i)) });
768
+ }
523
769
  function middle(v, head, tail) {
524
770
  return v.length <= head + tail + 1 ? v : `${v.slice(0, head)}\u2026${v.slice(-tail)}`;
525
771
  }
@@ -625,6 +871,252 @@ function ActivityList({ items, empty, className }) {
625
871
  return /* @__PURE__ */ jsxRuntime.jsx("li", { children: it.href ? /* @__PURE__ */ jsxRuntime.jsx("a", { href: it.href, className: "block transition-colors hover:bg-accent/60", children: body }) : body }, it.id);
626
872
  }) });
627
873
  }
874
+ var KIND = {
875
+ package: "text-oca-code-package",
876
+ tag: "text-oca-code-tag",
877
+ link: "text-oca-code-link"
878
+ };
879
+ function PackageCard({ items, className }) {
880
+ return /* @__PURE__ */ jsxRuntime.jsx(
881
+ "div",
882
+ {
883
+ className: cn(
884
+ "grid gap-x-10 gap-y-7 rounded-[1.25rem] border border-oca-ink-line bg-oca-ink-card p-6 text-oca-ink-foreground md:grid-cols-2 md:p-9",
885
+ className
886
+ ),
887
+ children: items.map((it, i) => {
888
+ const code = /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("text-[0.9375rem]", KIND[it.kind ?? "package"]), children: it.code });
889
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-col gap-1.5", children: [
890
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-[1.125rem] font-semibold tracking-[-0.01em] text-oca-ink-title", children: it.title }),
891
+ it.href ? /* @__PURE__ */ jsxRuntime.jsx("a", { href: it.href, className: "w-fit underline decoration-transparent underline-offset-4 hover:decoration-current", children: code }) : code,
892
+ it.note ? /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-[0.9375rem] leading-relaxed text-oca-ink-subtle", children: [
893
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, children: "// " }),
894
+ it.note
895
+ ] }) : null
896
+ ] }, i);
897
+ })
898
+ }
899
+ );
900
+ }
901
+ function CodeBlock({
902
+ code,
903
+ caption,
904
+ copy = true,
905
+ maxLines,
906
+ className
907
+ }) {
908
+ const lines = code.split("\n").length;
909
+ const foldable = maxLines !== void 0 && lines > maxLines + 2;
910
+ const [open, setOpen] = react.useState(false);
911
+ const folded = foldable && !open;
912
+ return /* @__PURE__ */ jsxRuntime.jsxs(
913
+ "figure",
914
+ {
915
+ className: cn(
916
+ "overflow-hidden rounded-[1.25rem] border border-[var(--oca-code-line)] bg-[var(--oca-code-surface)]",
917
+ className
918
+ ),
919
+ children: [
920
+ /* @__PURE__ */ jsxRuntime.jsxs("figcaption", { className: "flex h-11 items-center justify-between gap-3 border-b border-[var(--oca-code-line)] pl-5 pr-2", children: [
921
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-[0.8125rem] text-oca-ink-subtle", children: caption ?? "Code" }),
922
+ copy ? /* @__PURE__ */ jsxRuntime.jsx(
923
+ CopyButton,
924
+ {
925
+ value: code,
926
+ label: "Copy code",
927
+ className: "text-oca-ink-subtle hover:bg-white/10 hover:text-white"
928
+ }
929
+ ) : null
930
+ ] }),
931
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
932
+ /* @__PURE__ */ jsxRuntime.jsx(
933
+ "div",
934
+ {
935
+ className: cn(folded && "overflow-hidden"),
936
+ style: folded ? { maxHeight: `calc(${maxLines} * 1.625em + 2.5rem)` } : void 0,
937
+ children: /* @__PURE__ */ jsxRuntime.jsx(ScrollFade, { fade: "var(--oca-code-surface)", children: /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "w-max min-w-full p-5 font-mono text-[0.875rem] leading-[1.625] text-oca-ink-muted", children: /* @__PURE__ */ jsxRuntime.jsx("code", { children: code }) }) })
938
+ }
939
+ ),
940
+ foldable ? /* @__PURE__ */ jsxRuntime.jsx(
941
+ "div",
942
+ {
943
+ className: cn(
944
+ "flex justify-center px-5 pb-4",
945
+ folded && "absolute inset-x-0 bottom-0 pt-16"
946
+ ),
947
+ style: folded ? { background: "linear-gradient(to top, var(--oca-code-surface) 40%, transparent)" } : void 0,
948
+ children: /* @__PURE__ */ jsxRuntime.jsx(
949
+ "button",
950
+ {
951
+ type: "button",
952
+ onClick: () => setOpen((v) => !v),
953
+ className: "rounded-full border border-white/20 px-4 py-1.5 text-[0.8125rem] text-white transition-colors hover:bg-white/10",
954
+ children: open ? "Show less" : `Show all ${lines} lines`
955
+ }
956
+ )
957
+ }
958
+ ) : null
959
+ ] })
960
+ ]
961
+ }
962
+ );
963
+ }
964
+ function SplitBar({
965
+ segments,
966
+ caption,
967
+ className
968
+ }) {
969
+ return /* @__PURE__ */ jsxRuntime.jsxs("figure", { className: cn("flex flex-col gap-5", className), children: [
970
+ caption ? /* @__PURE__ */ jsxRuntime.jsx("figcaption", { className: "text-[0.9375rem]", children: caption }) : null,
971
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-16 w-full gap-1.5 overflow-hidden rounded-[1.25rem] md:h-20", children: segments.map((s, i) => /* @__PURE__ */ jsxRuntime.jsx(
972
+ "div",
973
+ {
974
+ className: cn(
975
+ "flex min-w-[4.25rem] items-end rounded-[1rem] px-3 pb-2.5 md:px-4 md:pb-3",
976
+ s.tone === "card" ? "bg-white text-oca-ink" : "bg-oca-ink text-white"
977
+ ),
978
+ style: { flexBasis: `${s.pct}%` },
979
+ children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "oca-tabular text-[1.25rem] leading-none tracking-[-0.02em] md:text-[1.875rem]", children: [
980
+ s.pct,
981
+ "%"
982
+ ] })
983
+ },
984
+ i
985
+ )) }),
986
+ /* @__PURE__ */ jsxRuntime.jsx("dl", { className: "grid gap-x-8 gap-y-2 sm:grid-cols-2", children: segments.map((s, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-baseline gap-2.5", children: [
987
+ /* @__PURE__ */ jsxRuntime.jsxs("dt", { className: "sr-only", children: [
988
+ s.pct,
989
+ "%"
990
+ ] }),
991
+ /* @__PURE__ */ jsxRuntime.jsx(
992
+ "span",
993
+ {
994
+ "aria-hidden": true,
995
+ className: cn(
996
+ "inline-block size-2.5 shrink-0 translate-y-[-1px] rounded-full",
997
+ s.tone === "card" ? "bg-white" : "bg-oca-ink"
998
+ )
999
+ }
1000
+ ),
1001
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { className: "text-[0.9375rem] leading-snug", children: s.label })
1002
+ ] }, i)) })
1003
+ ] });
1004
+ }
1005
+ var LABEL = {
1006
+ yes: "Yes",
1007
+ testing: "In testing",
1008
+ "not-yet": "Not yet",
1009
+ no: "No"
1010
+ };
1011
+ var TONE = {
1012
+ yes: "text-oca-accent-ink",
1013
+ testing: "text-foreground",
1014
+ "not-yet": "text-muted-foreground",
1015
+ no: "text-muted-foreground"
1016
+ };
1017
+ function TruthTable({
1018
+ rows,
1019
+ statusHeading = "Status",
1020
+ whatHeading = "What",
1021
+ className
1022
+ }) {
1023
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1024
+ /* @__PURE__ */ jsxRuntime.jsx(
1025
+ "ul",
1026
+ {
1027
+ className: cn(
1028
+ "divide-y divide-oca-hairline-strong overflow-hidden rounded-xl border border-oca-hairline-strong bg-card sm:hidden",
1029
+ className
1030
+ ),
1031
+ children: rows.map((r, i) => /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "flex flex-col gap-1.5 px-4 py-3.5", children: [
1032
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("text-[0.875rem] font-medium", TONE[r.status]), children: LABEL[r.status] }),
1033
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[0.9375rem] leading-relaxed", children: r.what })
1034
+ ] }, i))
1035
+ }
1036
+ ),
1037
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("overflow-hidden rounded-xl border border-oca-hairline-strong max-sm:hidden", className), children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "w-full border-collapse text-left text-[0.9375rem] md:text-[1rem]", children: [
1038
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { className: "bg-oca-table-head", children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
1039
+ /* @__PURE__ */ jsxRuntime.jsx("th", { scope: "col", className: "w-[30%] border-b border-oca-hairline-strong px-4 py-3 font-semibold md:w-[22%]", children: statusHeading }),
1040
+ /* @__PURE__ */ jsxRuntime.jsx("th", { scope: "col", className: "border-b border-l border-oca-hairline-strong px-4 py-3 font-semibold", children: whatHeading })
1041
+ ] }) }),
1042
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { className: "bg-card", children: rows.map((r, i) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "align-top", children: [
1043
+ /* @__PURE__ */ jsxRuntime.jsx(
1044
+ "td",
1045
+ {
1046
+ className: cn(
1047
+ "border-t border-oca-hairline-strong px-4 py-3 font-medium",
1048
+ i === 0 && "border-t-0",
1049
+ TONE[r.status]
1050
+ ),
1051
+ children: LABEL[r.status]
1052
+ }
1053
+ ),
1054
+ /* @__PURE__ */ jsxRuntime.jsx(
1055
+ "td",
1056
+ {
1057
+ className: cn(
1058
+ "border-l border-t border-oca-hairline-strong px-4 py-3 leading-relaxed",
1059
+ i === 0 && "border-t-0"
1060
+ ),
1061
+ children: r.what
1062
+ }
1063
+ )
1064
+ ] }, i)) })
1065
+ ] }) })
1066
+ ] });
1067
+ }
1068
+ var INT = new Intl.NumberFormat("en-US", { maximumFractionDigits: 0 });
1069
+ var COMPACT = new Intl.NumberFormat("en-US", { notation: "compact", maximumFractionDigits: 1 });
1070
+ function satsText(value, opts = {}) {
1071
+ if (value == null || !Number.isFinite(value)) return null;
1072
+ const whole = Math.trunc(value);
1073
+ const body = (opts.compact ? COMPACT : INT).format(Math.abs(whole));
1074
+ const sign = whole < 0 ? "\u2212" : opts.signed && whole > 0 ? "+" : "";
1075
+ const unit = opts.unit === "none" ? "" : whole === 1 || whole === -1 ? " sat" : " sats";
1076
+ return `${sign}${body}${unit}`;
1077
+ }
1078
+ function Sats({ value, unit, compact, signed, unknown = "\u2014", className, ...props }) {
1079
+ const text = satsText(value, { unit, compact, signed });
1080
+ if (text === null) {
1081
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: cn("oca-tabular text-muted-foreground", className), ...props, children: [
1082
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, children: unknown }),
1083
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sr-only", children: "unknown" })
1084
+ ] });
1085
+ }
1086
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("oca-tabular whitespace-nowrap", className), ...props, children: text });
1087
+ }
1088
+ function UsdApprox({
1089
+ sats,
1090
+ usdPerBtc,
1091
+ className
1092
+ }) {
1093
+ if (sats == null || usdPerBtc == null || !Number.isFinite(sats) || !Number.isFinite(usdPerBtc)) return null;
1094
+ const usd = sats / 1e8 * usdPerBtc;
1095
+ const text = new Intl.NumberFormat("en-US", {
1096
+ style: "currency",
1097
+ currency: "USD",
1098
+ maximumFractionDigits: usd < 1 ? 4 : 2
1099
+ }).format(usd);
1100
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: cn("oca-tabular text-muted-foreground", className), children: [
1101
+ "\u2248 ",
1102
+ text
1103
+ ] });
1104
+ }
1105
+ function SatsFigure({
1106
+ label,
1107
+ value,
1108
+ hint,
1109
+ className
1110
+ }) {
1111
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex flex-col gap-1.5", className), children: [
1112
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[0.9375rem] text-muted-foreground", children: label }),
1113
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "oca-display-3 oca-tabular", children: [
1114
+ /* @__PURE__ */ jsxRuntime.jsx(Sats, { value, unit: "none" }),
1115
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-2 text-[0.5em] tracking-normal text-muted-foreground", children: "sats" })
1116
+ ] }),
1117
+ hint ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[0.875rem] text-muted-foreground", children: hint }) : null
1118
+ ] });
1119
+ }
628
1120
 
629
1121
  exports.Accent = Accent;
630
1122
  exports.ActivityList = ActivityList;
@@ -633,15 +1125,20 @@ exports.Container = Container;
633
1125
  exports.CopyButton = CopyButton;
634
1126
  exports.DataTable = DataTable;
635
1127
  exports.Display = Display;
1128
+ exports.DocHeader = DocHeader;
1129
+ exports.DocLayout = DocLayout;
1130
+ exports.EmptyLine = EmptyLine;
636
1131
  exports.EmptyState = EmptyState;
637
1132
  exports.Eyebrow = Eyebrow;
638
1133
  exports.Footnote = Footnote;
639
1134
  exports.HashChip = HashChip;
640
1135
  exports.KeyValueList = KeyValueList;
641
1136
  exports.Lead = Lead;
1137
+ exports.LoadingBlock = LoadingBlock;
642
1138
  exports.Notice = Notice;
643
1139
  exports.PackageCard = PackageCard;
644
1140
  exports.PageHeader = PageHeader;
1141
+ exports.PageHero = PageHero;
645
1142
  exports.ProgressSteps = ProgressSteps;
646
1143
  exports.Prose = Prose;
647
1144
  exports.Sats = Sats;