@boost33/ui 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +27 -1
  2. package/dist/components/AccountMenu/AccountMenu.d.ts +131 -0
  3. package/dist/components/AccountMenu/icons.d.ts +11 -0
  4. package/dist/components/AccountMenu/labels.d.ts +39 -0
  5. package/dist/components/AppShell/AppShell.d.ts +71 -18
  6. package/dist/components/AppShell/AppShell.js +33 -35
  7. package/dist/components/AppShell/AppShell.js.map +1 -1
  8. package/dist/components/AppShell/AppShell.module.css.js +11 -11
  9. package/dist/components/AppSwitcher/AppSwitcher.d.ts +23 -1
  10. package/dist/components/AppSwitcher/AppSwitcher.js +49 -43
  11. package/dist/components/AppSwitcher/AppSwitcher.js.map +1 -1
  12. package/dist/components/AppSwitcher/AppSwitcher.module.css.js +36 -36
  13. package/dist/components/AppSwitcher/labels.js.map +1 -1
  14. package/dist/components/CommandPalette/CommandPalette.d.ts +127 -0
  15. package/dist/components/CommandPalette/icons.d.ts +8 -0
  16. package/dist/components/CommandPalette/labels.d.ts +94 -0
  17. package/dist/components/DotMatrixLoader/DotMatrixLoader.d.ts +21 -0
  18. package/dist/components/DotMatrixLoader/DotMatrixLoader.js +18 -0
  19. package/dist/components/DotMatrixLoader/DotMatrixLoader.js.map +1 -0
  20. package/dist/components/DotMatrixLoader/DotMatrixLoader.module.css.js +12 -0
  21. package/dist/components/DotMatrixLoader/DotMatrixLoader.module.css.js.map +1 -0
  22. package/dist/components/LaunchLoader/LaunchLoader.d.ts +50 -0
  23. package/dist/components/LaunchLoader/LaunchLoader.js +70 -0
  24. package/dist/components/LaunchLoader/LaunchLoader.js.map +1 -0
  25. package/dist/components/LaunchLoader/LaunchLoader.module.css.js +26 -0
  26. package/dist/components/LaunchLoader/LaunchLoader.module.css.js.map +1 -0
  27. package/dist/components/NotificationCenter/NotificationCenter.d.ts +162 -0
  28. package/dist/components/NotificationCenter/icons.d.ts +34 -0
  29. package/dist/components/NotificationCenter/labels.d.ts +112 -0
  30. package/dist/components/ProductMark/ProductMark.d.ts +54 -1
  31. package/dist/components/ProductMark/ProductMark.js +23 -19
  32. package/dist/components/ProductMark/ProductMark.js.map +1 -1
  33. package/dist/components/Sidebar/Sidebar.d.ts +17 -3
  34. package/dist/components/Sidebar/Sidebar.js +5 -5
  35. package/dist/components/Sidebar/Sidebar.js.map +1 -1
  36. package/dist/components/WalletChip/WalletChip.d.ts +150 -27
  37. package/dist/components/WalletChip/WalletChip.js +38 -35
  38. package/dist/components/WalletChip/WalletChip.js.map +1 -1
  39. package/dist/components/WalletChip/WalletChip.module.css.js +36 -36
  40. package/dist/components/WalletChip/labels.d.ts +65 -47
  41. package/dist/components/WalletChip/labels.js +15 -20
  42. package/dist/components/WalletChip/labels.js.map +1 -1
  43. package/dist/fill-template.d.ts +10 -0
  44. package/dist/fill-template.js +10 -0
  45. package/dist/fill-template.js.map +1 -0
  46. package/dist/index.d.ts +10 -4
  47. package/dist/index.js +38 -39
  48. package/dist/index.js.map +1 -1
  49. package/dist/launch-gradient.d.ts +112 -0
  50. package/dist/launch-gradient.js +60 -0
  51. package/dist/launch-gradient.js.map +1 -0
  52. package/dist/nav-height-budget.d.ts +4 -0
  53. package/dist/nav-height-budget.js +18 -19
  54. package/dist/nav-height-budget.js.map +1 -1
  55. package/dist/style.css +1 -1
  56. package/dist/theme.css +40 -14
  57. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -1,45 +1,44 @@
1
1
  /* empty css */
2
2
  import { Sidebar as t } from "./components/Sidebar/Sidebar.js";
3
- import { NavSection as p } from "./components/NavSection/NavSection.js";
4
- import { Wordmark as m } from "./components/Wordmark/Wordmark.js";
5
- import { BOOST33_MARK_MIN_LEGIBLE_PX as S, Boost33Mark as f } from "./components/Boost33Mark/Boost33Mark.js";
6
- import { PRODUCT_SLUGS as N, ProductMark as x } from "./components/ProductMark/ProductMark.js";
7
- import { NavItem as L } from "./components/NavItem/NavItem.js";
8
- import { WalletChip as T } from "./components/WalletChip/WalletChip.js";
9
- import { defaultWalletChipLabels as l } from "./components/WalletChip/labels.js";
10
- import { TopBar as h } from "./components/TopBar/TopBar.js";
11
- import { PageHeader as I } from "./components/PageHeader/PageHeader.js";
12
- import { AppShell as d } from "./components/AppShell/AppShell.js";
13
- import { AppSwitcher as B } from "./components/AppSwitcher/AppSwitcher.js";
14
- import { defaultAppSwitcherLabels as c } from "./components/AppSwitcher/labels.js";
15
- import { NAV_FOCUS_GUTTER_PX as u, NAV_HEIGHT_BUDGET_PX as v, NAV_ITEM_PX as M, NAV_SECTION_GAP_PX as U, NAV_SECTION_HEADER_PX as n, NAV_SHELL_CONSUMER_SLOTS_PX as D, NAV_SHELL_FIXED_PX as R, NAV_SHELL_PACKAGE_FIXED_PX as k, checkNavHeightBudget as s, measureNavHeight as F, navBudgetForViewport as b, navContentHeight as w } from "./nav-height-budget.js";
3
+ import { NavSection as a } from "./components/NavSection/NavSection.js";
4
+ import { Wordmark as f } from "./components/Wordmark/Wordmark.js";
5
+ import { BOOST33_MARK_MIN_LEGIBLE_PX as d, Boost33Mark as l } from "./components/Boost33Mark/Boost33Mark.js";
6
+ import { ProductMark as c, hasProductMark as i } from "./components/ProductMark/ProductMark.js";
7
+ import { NavItem as n } from "./components/NavItem/NavItem.js";
8
+ import { WalletChip as L } from "./components/WalletChip/WalletChip.js";
9
+ import { defaultWalletChipLabels as k } from "./components/WalletChip/labels.js";
10
+ import { TopBar as M } from "./components/TopBar/TopBar.js";
11
+ import { PageHeader as v } from "./components/PageHeader/PageHeader.js";
12
+ import { AppShell as C } from "./components/AppShell/AppShell.js";
13
+ import { AppSwitcher as P } from "./components/AppSwitcher/AppSwitcher.js";
14
+ import { defaultAppSwitcherLabels as b } from "./components/AppSwitcher/labels.js";
15
+ import { DotMatrixLoader as H } from "./components/DotMatrixLoader/DotMatrixLoader.js";
16
+ import { LaunchLoader as W } from "./components/LaunchLoader/LaunchLoader.js";
17
+ import { launchBackdropCss as G, launchGround as O, launchSolid as T } from "./launch-gradient.js";
18
+ import { checkNavHeightBudget as F, navBudgetForViewport as K, navContentHeight as R } from "./nav-height-budget.js";
16
19
  export {
17
- d as AppShell,
18
- B as AppSwitcher,
19
- S as BOOST33_MARK_MIN_LEGIBLE_PX,
20
- f as Boost33Mark,
21
- u as NAV_FOCUS_GUTTER_PX,
22
- v as NAV_HEIGHT_BUDGET_PX,
23
- M as NAV_ITEM_PX,
24
- U as NAV_SECTION_GAP_PX,
25
- n as NAV_SECTION_HEADER_PX,
26
- D as NAV_SHELL_CONSUMER_SLOTS_PX,
27
- R as NAV_SHELL_FIXED_PX,
28
- k as NAV_SHELL_PACKAGE_FIXED_PX,
29
- L as NavItem,
30
- p as NavSection,
31
- N as PRODUCT_SLUGS,
32
- I as PageHeader,
33
- x as ProductMark,
20
+ C as AppShell,
21
+ P as AppSwitcher,
22
+ d as BOOST33_MARK_MIN_LEGIBLE_PX,
23
+ l as Boost33Mark,
24
+ H as DotMatrixLoader,
25
+ W as LaunchLoader,
26
+ n as NavItem,
27
+ a as NavSection,
28
+ v as PageHeader,
29
+ c as ProductMark,
34
30
  t as Sidebar,
35
- h as TopBar,
36
- T as WalletChip,
37
- m as Wordmark,
38
- s as checkNavHeightBudget,
39
- c as defaultAppSwitcherLabels,
40
- l as defaultWalletChipLabels,
41
- F as measureNavHeight,
42
- b as navBudgetForViewport,
43
- w as navContentHeight
31
+ M as TopBar,
32
+ L as WalletChip,
33
+ f as Wordmark,
34
+ F as checkNavHeightBudget,
35
+ b as defaultAppSwitcherLabels,
36
+ k as defaultWalletChipLabels,
37
+ i as hasProductMark,
38
+ G as launchBackdropCss,
39
+ O as launchGround,
40
+ T as launchSolid,
41
+ K as navBudgetForViewport,
42
+ R as navContentHeight
44
43
  };
45
44
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;"}
@@ -0,0 +1,112 @@
1
+ /**
2
+ * The destination product's four gradient stops, ordered darkest first.
3
+ *
4
+ * The order is a contract, not a style choice, and it carries the screen's
5
+ * contrast:
6
+ *
7
+ * - `stops[0]` is the darkest tone, and the flat colour `prefers-contrast`
8
+ * paints.
9
+ * - `stops[1]` and the mixes between it and `stops[0]` are the tones behind
10
+ * the loader, the bar and the caption. Both must stay at or under relative
11
+ * luminance 0.183 (linearised sRGB), the ceiling for white text at 4.5:1:
12
+ * the caption sits straight on the mesh with no plate behind it.
13
+ * - `stops[2]` is the one light accent, a glow low on the screen, clear of the
14
+ * content. `stops[3]` is not painted at all (see launchRamp); it stays in
15
+ * the tuple so a palette is still four colours, darkest first.
16
+ *
17
+ * The values are the consumer's: this package knows no product and holds no
18
+ * product's colours (CLAUDE.md). scripts/launch-contrast-check.mts measures the
19
+ * composited result behind the content in a real browser.
20
+ */
21
+ export type LaunchGradientStops = readonly [string, string, string, string];
22
+ /**
23
+ * The six tones the mesh is painted in, all derived from the four stops, so the
24
+ * stops contract and palette-agnosticism both survive. `stops[3]` is not used:
25
+ * on Score it is near-white, and it is what blew out every composition that
26
+ * included it.
27
+ *
28
+ * The mix percentages are tuned in sRGB to reproduce the three Score mixes
29
+ * Jamal approved in Paper on 14 Oct 2026 (recorded on B33-1389) to within a few
30
+ * levels a channel; Paper's own figures are not an sRGB mix at round
31
+ * percentages. They are mixes rather than hexes so Wallet's greens take the
32
+ * same shape.
33
+ */
34
+ export interface LaunchRamp {
35
+ /** stops[0]. The darkest shape. */
36
+ r0: string;
37
+ r1: string;
38
+ /** The mesh's ground, under every shape and behind its blurred edges. */
39
+ r2: string;
40
+ /** stops[1]. */
41
+ r3: string;
42
+ r4: string;
43
+ /** stops[2]. The one light glow, low on the screen. */
44
+ accent: string;
45
+ }
46
+ export declare function launchRamp(stops: LaunchGradientStops): LaunchRamp;
47
+ /**
48
+ * One shape in the mesh: a solid tone with an irregular radius, placed and
49
+ * sized as a percentage of the mesh container, which overhangs the viewport by
50
+ * 12% on every side.
51
+ */
52
+ export interface LaunchMeshShape {
53
+ top: number;
54
+ left: number;
55
+ width: number;
56
+ height: number;
57
+ tone: keyof LaunchRamp;
58
+ radius: string;
59
+ rotate: number;
60
+ }
61
+ /**
62
+ * The composition Jamal approved in Paper on 14 Oct 2026, in paint order. A lit
63
+ * left field, a dark diagonal channel through the centre, a soft lit sweep
64
+ * down the right edge, one accent glow along the bottom. The dark channel is
65
+ * what sits behind the content, so the caption's contrast holds by the
66
+ * composition rather than by tuning -- scripts/launch-contrast-check.mts
67
+ * measures it.
68
+ *
69
+ * Solid shapes melted by one blur on their container, not gradients: stacked
70
+ * radial-gradients never covered the field, and the ground showing between
71
+ * them read as holes. Irregular radii and rotation give the sweeping edges.
72
+ */
73
+ export declare const LAUNCH_MESH: readonly LaunchMeshShape[];
74
+ /**
75
+ * One shape's inline style. Built here rather than in the component so the CSS
76
+ * it assembles sits beside the rest of the mesh's CSS text, outside the
77
+ * no-hardcoded-strings scan of src/components.
78
+ */
79
+ export declare function launchShapeStyle(shape: LaunchMeshShape, ramp: LaunchRamp): {
80
+ top: string;
81
+ left: string;
82
+ width: string;
83
+ height: string;
84
+ borderRadius: string;
85
+ transform: string | undefined;
86
+ background: string;
87
+ };
88
+ /** How far the mesh container overhangs the viewport on every side, as a fraction of it. */
89
+ export declare const LAUNCH_MESH_OVERHANG = 0.12;
90
+ /**
91
+ * The ground the mesh sits on, as a CSS colour: the tone its blurred edges
92
+ * fade into, so the blur never darkens or lightens the viewport's rim.
93
+ */
94
+ export declare function launchGround(stops: LaunchGradientStops): string;
95
+ /** The flat colour `prefers-contrast` paints instead of the gradient: the darkest stop. */
96
+ export declare function launchSolid(stops: LaunchGradientStops): string;
97
+ /**
98
+ * The launch screen's ground as a raw CSS string, for a consumer to inline in
99
+ * a `<style>` block with no stylesheet request (B33-1389).
100
+ *
101
+ * The mesh itself is markup -- LaunchLoader renders its shapes with inline
102
+ * styles -- so it paints in the same HTML chunk as the component, on both sides
103
+ * of a hub-to-product navigation, from the same props: that is what keeps the
104
+ * hop seamless now. This paints the one thing that can show before that markup
105
+ * parses: the root in the mesh's own ground colour, so the first frame is
106
+ * already the screen's base tone rather than white.
107
+ *
108
+ * `selector` defaults to `html`. The contrast branch is the boolean
109
+ * `(prefers-contrast)`, not `(prefers-contrast: more)`, and paints the flat
110
+ * darkest stop, as the component does.
111
+ */
112
+ export declare function launchBackdropCss(stops: LaunchGradientStops, selector?: string): string;
@@ -0,0 +1,60 @@
1
+ const d = /^[#a-zA-Z0-9(),.%\s/+-]+$/;
2
+ function i(t) {
3
+ if (t.length !== 4)
4
+ throw new Error(`LaunchLoader takes exactly four gradient stops, got ${t.length}`);
5
+ for (const r of t)
6
+ if (!d.test(r))
7
+ throw new Error(`LaunchLoader: ${JSON.stringify(r)} is not a CSS colour this package will inline`);
8
+ return t;
9
+ }
10
+ function u(t) {
11
+ const [r, o, e] = i(t), n = (a, h, c) => `color-mix(in srgb, ${a} ${h}%, ${c})`;
12
+ return {
13
+ r0: r,
14
+ r1: n(r, 85, o),
15
+ r2: n(r, 67, o),
16
+ r3: o,
17
+ r4: n(o, 64, e),
18
+ accent: e
19
+ };
20
+ }
21
+ const g = [
22
+ { top: 40, left: 22, width: 80, height: 58, tone: "r1", radius: "30% 70% 40% 60% / 72% 36% 64% 28%", rotate: -5 },
23
+ { top: -20, left: 52, width: 72, height: 66, tone: "r0", radius: "70% 30% 26% 74% / 40% 70% 30% 60%", rotate: 12 },
24
+ { top: 4, left: -6, width: 44, height: 42, tone: "r3", radius: "46% 54% 60% 40% / 52% 48% 52% 48%", rotate: 0 },
25
+ { top: -24, left: -20, width: 78, height: 56, tone: "r4", radius: "26% 74% 60% 40% / 68% 32% 66% 34%", rotate: -11 },
26
+ { top: 64, left: 50, width: 72, height: 56, tone: "accent", radius: "36% 64% 74% 26% / 62% 28% 72% 38%", rotate: 20 },
27
+ { top: 64, left: -22, width: 70, height: 52, tone: "r3", radius: "74% 26% 56% 44% / 34% 66% 32% 68%", rotate: -6 },
28
+ { top: 4, left: 76, width: 54, height: 88, tone: "r3", radius: "62% 38% 28% 72% / 36% 64% 36% 64%", rotate: -10 },
29
+ { top: -22, left: 86, width: 52, height: 54, tone: "r4", radius: "38% 62% 70% 30% / 58% 34% 66% 42%", rotate: 22 }
30
+ ];
31
+ function s(t, r) {
32
+ return {
33
+ top: `${t.top}%`,
34
+ left: `${t.left}%`,
35
+ width: `${t.width}%`,
36
+ height: `${t.height}%`,
37
+ borderRadius: t.radius,
38
+ transform: t.rotate ? `rotate(${t.rotate}deg)` : void 0,
39
+ background: r[t.tone]
40
+ };
41
+ }
42
+ function l(t) {
43
+ return u(t).r2;
44
+ }
45
+ function f(t) {
46
+ return i(t)[0];
47
+ }
48
+ function w(t, r = "html") {
49
+ const o = l(t), e = f(t);
50
+ return `${r}{min-height:100%;background-color:${o}}@media (prefers-contrast){${r}{background-color:${e}}}`;
51
+ }
52
+ export {
53
+ g as LAUNCH_MESH,
54
+ w as launchBackdropCss,
55
+ l as launchGround,
56
+ u as launchRamp,
57
+ s as launchShapeStyle,
58
+ f as launchSolid
59
+ };
60
+ //# sourceMappingURL=launch-gradient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launch-gradient.js","sources":["../src/launch-gradient.ts"],"sourcesContent":["/*\n * LaunchLoader's background, as data and CSS text (B33-1389). Beside the\n * components rather than inside one, the way nav-height-budget.ts is: it\n * renders nothing, and what it assembles is CSS a consumer inlines, not copy a\n * person reads -- so it sits outside the no-hardcoded-strings scan, which reads\n * src/components for rendered words.\n */\n\n/**\n * The destination product's four gradient stops, ordered darkest first.\n *\n * The order is a contract, not a style choice, and it carries the screen's\n * contrast:\n *\n * - `stops[0]` is the darkest tone, and the flat colour `prefers-contrast`\n * paints.\n * - `stops[1]` and the mixes between it and `stops[0]` are the tones behind\n * the loader, the bar and the caption. Both must stay at or under relative\n * luminance 0.183 (linearised sRGB), the ceiling for white text at 4.5:1:\n * the caption sits straight on the mesh with no plate behind it.\n * - `stops[2]` is the one light accent, a glow low on the screen, clear of the\n * content. `stops[3]` is not painted at all (see launchRamp); it stays in\n * the tuple so a palette is still four colours, darkest first.\n *\n * The values are the consumer's: this package knows no product and holds no\n * product's colours (CLAUDE.md). scripts/launch-contrast-check.mts measures the\n * composited result behind the content in a real browser.\n */\nexport type LaunchGradientStops = readonly [string, string, string, string]\n\n/*\n * A stop is written straight into CSS, and launchBackdropCss's output is meant\n * to be inlined in a <style> block in the first HTML chunk. A value carrying\n * `;`, `{`, `}` or `<` could close the declaration, the rule or the element and\n * write whatever follows. Stops will come from a served registry, not a literal,\n * so this is checked rather than trusted: hex, a colour function, or a keyword,\n * and nothing that can leave the declaration.\n */\nconst SAFE_COLOR = /^[#a-zA-Z0-9(),.%\\s/+-]+$/\n\nfunction checkedStops(stops: LaunchGradientStops): LaunchGradientStops {\n if (stops.length !== 4) {\n throw new Error(`LaunchLoader takes exactly four gradient stops, got ${stops.length}`)\n }\n for (const stop of stops) {\n if (!SAFE_COLOR.test(stop)) {\n throw new Error(`LaunchLoader: ${JSON.stringify(stop)} is not a CSS colour this package will inline`)\n }\n }\n return stops\n}\n\n/**\n * The six tones the mesh is painted in, all derived from the four stops, so the\n * stops contract and palette-agnosticism both survive. `stops[3]` is not used:\n * on Score it is near-white, and it is what blew out every composition that\n * included it.\n *\n * The mix percentages are tuned in sRGB to reproduce the three Score mixes\n * Jamal approved in Paper on 14 Oct 2026 (recorded on B33-1389) to within a few\n * levels a channel; Paper's own figures are not an sRGB mix at round\n * percentages. They are mixes rather than hexes so Wallet's greens take the\n * same shape.\n */\nexport interface LaunchRamp {\n /** stops[0]. The darkest shape. */\n r0: string\n r1: string\n /** The mesh's ground, under every shape and behind its blurred edges. */\n r2: string\n /** stops[1]. */\n r3: string\n r4: string\n /** stops[2]. The one light glow, low on the screen. */\n accent: string\n}\n\nexport function launchRamp(stops: LaunchGradientStops): LaunchRamp {\n const [s0, s1, s2] = checkedStops(stops)\n const mix = (a: string, pc: number, b: string) => `color-mix(in srgb, ${a} ${pc}%, ${b})`\n return {\n r0: s0,\n r1: mix(s0, 85, s1),\n r2: mix(s0, 67, s1),\n r3: s1,\n r4: mix(s1, 64, s2),\n accent: s2,\n }\n}\n\n/**\n * One shape in the mesh: a solid tone with an irregular radius, placed and\n * sized as a percentage of the mesh container, which overhangs the viewport by\n * 12% on every side.\n */\nexport interface LaunchMeshShape {\n top: number\n left: number\n width: number\n height: number\n tone: keyof LaunchRamp\n radius: string\n rotate: number\n}\n\n/**\n * The composition Jamal approved in Paper on 14 Oct 2026, in paint order. A lit\n * left field, a dark diagonal channel through the centre, a soft lit sweep\n * down the right edge, one accent glow along the bottom. The dark channel is\n * what sits behind the content, so the caption's contrast holds by the\n * composition rather than by tuning -- scripts/launch-contrast-check.mts\n * measures it.\n *\n * Solid shapes melted by one blur on their container, not gradients: stacked\n * radial-gradients never covered the field, and the ground showing between\n * them read as holes. Irregular radii and rotation give the sweeping edges.\n */\nexport const LAUNCH_MESH: readonly LaunchMeshShape[] = [\n { top: 40, left: 22, width: 80, height: 58, tone: 'r1', radius: '30% 70% 40% 60% / 72% 36% 64% 28%', rotate: -5 },\n { top: -20, left: 52, width: 72, height: 66, tone: 'r0', radius: '70% 30% 26% 74% / 40% 70% 30% 60%', rotate: 12 },\n { top: 4, left: -6, width: 44, height: 42, tone: 'r3', radius: '46% 54% 60% 40% / 52% 48% 52% 48%', rotate: 0 },\n { top: -24, left: -20, width: 78, height: 56, tone: 'r4', radius: '26% 74% 60% 40% / 68% 32% 66% 34%', rotate: -11 },\n { top: 64, left: 50, width: 72, height: 56, tone: 'accent', radius: '36% 64% 74% 26% / 62% 28% 72% 38%', rotate: 20 },\n { top: 64, left: -22, width: 70, height: 52, tone: 'r3', radius: '74% 26% 56% 44% / 34% 66% 32% 68%', rotate: -6 },\n { top: 4, left: 76, width: 54, height: 88, tone: 'r3', radius: '62% 38% 28% 72% / 36% 64% 36% 64%', rotate: -10 },\n { top: -22, left: 86, width: 52, height: 54, tone: 'r4', radius: '38% 62% 70% 30% / 58% 34% 66% 42%', rotate: 22 },\n]\n\n/**\n * One shape's inline style. Built here rather than in the component so the CSS\n * it assembles sits beside the rest of the mesh's CSS text, outside the\n * no-hardcoded-strings scan of src/components.\n */\nexport function launchShapeStyle(shape: LaunchMeshShape, ramp: LaunchRamp) {\n return {\n top: `${shape.top}%`,\n left: `${shape.left}%`,\n width: `${shape.width}%`,\n height: `${shape.height}%`,\n borderRadius: shape.radius,\n transform: shape.rotate ? `rotate(${shape.rotate}deg)` : undefined,\n background: ramp[shape.tone],\n }\n}\n\n/** How far the mesh container overhangs the viewport on every side, as a fraction of it. */\nexport const LAUNCH_MESH_OVERHANG = 0.12\n\n/**\n * The ground the mesh sits on, as a CSS colour: the tone its blurred edges\n * fade into, so the blur never darkens or lightens the viewport's rim.\n */\nexport function launchGround(stops: LaunchGradientStops): string {\n return launchRamp(stops).r2\n}\n\n/** The flat colour `prefers-contrast` paints instead of the gradient: the darkest stop. */\nexport function launchSolid(stops: LaunchGradientStops): string {\n return checkedStops(stops)[0]\n}\n\n/**\n * The launch screen's ground as a raw CSS string, for a consumer to inline in\n * a `<style>` block with no stylesheet request (B33-1389).\n *\n * The mesh itself is markup -- LaunchLoader renders its shapes with inline\n * styles -- so it paints in the same HTML chunk as the component, on both sides\n * of a hub-to-product navigation, from the same props: that is what keeps the\n * hop seamless now. This paints the one thing that can show before that markup\n * parses: the root in the mesh's own ground colour, so the first frame is\n * already the screen's base tone rather than white.\n *\n * `selector` defaults to `html`. The contrast branch is the boolean\n * `(prefers-contrast)`, not `(prefers-contrast: more)`, and paints the flat\n * darkest stop, as the component does.\n */\nexport function launchBackdropCss(stops: LaunchGradientStops, selector = 'html'): string {\n const ground = launchGround(stops)\n const solid = launchSolid(stops)\n return `${selector}{min-height:100%;background-color:${ground}}` + `@media (prefers-contrast){${selector}{background-color:${solid}}}`\n}\n"],"names":["SAFE_COLOR","checkedStops","stops","stop","launchRamp","s0","s1","s2","mix","pc","b","LAUNCH_MESH","launchShapeStyle","shape","ramp","launchGround","launchSolid","launchBackdropCss","selector","ground","solid"],"mappings":"AAsCA,MAAMA,IAAa;AAEnB,SAASC,EAAaC,GAAiD;AACrE,MAAIA,EAAM,WAAW;AACnB,UAAM,IAAI,MAAM,uDAAuDA,EAAM,MAAM,EAAE;AAEvF,aAAWC,KAAQD;AACjB,QAAI,CAACF,EAAW,KAAKG,CAAI;AACvB,YAAM,IAAI,MAAM,iBAAiB,KAAK,UAAUA,CAAI,CAAC,+CAA+C;AAGxG,SAAOD;AACT;AA2BO,SAASE,EAAWF,GAAwC;AACjE,QAAM,CAACG,GAAIC,GAAIC,CAAE,IAAIN,EAAaC,CAAK,GACjCM,IAAM,CAAC,GAAWC,GAAYC,MAAc,sBAAsB,CAAC,IAAID,CAAE,MAAMC,CAAC;AACtF,SAAO;AAAA,IACL,IAAIL;AAAA,IACJ,IAAIG,EAAIH,GAAI,IAAIC,CAAE;AAAA,IAClB,IAAIE,EAAIH,GAAI,IAAIC,CAAE;AAAA,IAClB,IAAIA;AAAA,IACJ,IAAIE,EAAIF,GAAI,IAAIC,CAAE;AAAA,IAClB,QAAQA;AAAA,EAAA;AAEZ;AA6BO,MAAMI,IAA0C;AAAA,EACrD,EAAE,KAAK,IAAI,MAAM,IAAI,OAAO,IAAI,QAAQ,IAAI,MAAM,MAAM,QAAQ,qCAAqC,QAAQ,GAAA;AAAA,EAC7G,EAAE,KAAK,KAAK,MAAM,IAAI,OAAO,IAAI,QAAQ,IAAI,MAAM,MAAM,QAAQ,qCAAqC,QAAQ,GAAA;AAAA,EAC9G,EAAE,KAAK,GAAG,MAAM,IAAI,OAAO,IAAI,QAAQ,IAAI,MAAM,MAAM,QAAQ,qCAAqC,QAAQ,EAAA;AAAA,EAC5G,EAAE,KAAK,KAAK,MAAM,KAAK,OAAO,IAAI,QAAQ,IAAI,MAAM,MAAM,QAAQ,qCAAqC,QAAQ,IAAA;AAAA,EAC/G,EAAE,KAAK,IAAI,MAAM,IAAI,OAAO,IAAI,QAAQ,IAAI,MAAM,UAAU,QAAQ,qCAAqC,QAAQ,GAAA;AAAA,EACjH,EAAE,KAAK,IAAI,MAAM,KAAK,OAAO,IAAI,QAAQ,IAAI,MAAM,MAAM,QAAQ,qCAAqC,QAAQ,GAAA;AAAA,EAC9G,EAAE,KAAK,GAAG,MAAM,IAAI,OAAO,IAAI,QAAQ,IAAI,MAAM,MAAM,QAAQ,qCAAqC,QAAQ,IAAA;AAAA,EAC5G,EAAE,KAAK,KAAK,MAAM,IAAI,OAAO,IAAI,QAAQ,IAAI,MAAM,MAAM,QAAQ,qCAAqC,QAAQ,GAAA;AAChH;AAOO,SAASC,EAAiBC,GAAwBC,GAAkB;AACzE,SAAO;AAAA,IACL,KAAK,GAAGD,EAAM,GAAG;AAAA,IACjB,MAAM,GAAGA,EAAM,IAAI;AAAA,IACnB,OAAO,GAAGA,EAAM,KAAK;AAAA,IACrB,QAAQ,GAAGA,EAAM,MAAM;AAAA,IACvB,cAAcA,EAAM;AAAA,IACpB,WAAWA,EAAM,SAAS,UAAUA,EAAM,MAAM,SAAS;AAAA,IACzD,YAAYC,EAAKD,EAAM,IAAI;AAAA,EAAA;AAE/B;AASO,SAASE,EAAab,GAAoC;AAC/D,SAAOE,EAAWF,CAAK,EAAE;AAC3B;AAGO,SAASc,EAAYd,GAAoC;AAC9D,SAAOD,EAAaC,CAAK,EAAE,CAAC;AAC9B;AAiBO,SAASe,EAAkBf,GAA4BgB,IAAW,QAAgB;AACvF,QAAMC,IAASJ,EAAab,CAAK,GAC3BkB,IAAQJ,EAAYd,CAAK;AAC/B,SAAO,GAAGgB,CAAQ,qCAAqCC,CAAM,8BAAmCD,CAAQ,qBAAqBE,CAAK;AACpI;"}
@@ -21,6 +21,10 @@ export interface NavSectionShape {
21
21
  * taller than 17px -- `Segoe UI` is the known candidate, unmeasured -- gets a
22
22
  * model that under-counts, and can correct it here rather than forking the
23
23
  * check. Measure a rendered row's `offsetHeight`, do not estimate it.
24
+ *
25
+ * The NAV_*_PX constants named below are this module's own and are not published
26
+ * (B33-483); they are the defaults, not something a caller reads. Omitting a
27
+ * field here takes the default, which is why there is nothing to import.
24
28
  */
25
29
  export interface NavRowCosts {
26
30
  /** What one nav row costs. Defaults to NAV_ITEM_PX, the expanded row in Inter. */
@@ -1,16 +1,16 @@
1
- const P = 185, X = 86, c = 271, H = 33, L = 30, V = 16, T = 8, S = r(900);
2
- function C(_, t = {}) {
1
+ const P = 185, S = 86, H = 33, X = 30, T = 16, V = 8, N = c(900);
2
+ function i(_, t = {}) {
3
3
  if (_.length === 0) return 0;
4
- const E = t.itemPx ?? 33, e = t.sectionHeaderPx ?? 30, A = _.reduce(
5
- (o, n) => o + (n.hasHeader ? e : 0) + n.itemCount * E,
4
+ const e = t.itemPx ?? 33, E = t.sectionHeaderPx ?? 30, o = _.reduce(
5
+ (A, n) => A + (n.hasHeader ? E : 0) + n.itemCount * e,
6
6
  0
7
- ), N = 16 * (_.length - 1);
8
- return A + N + 8;
7
+ ), r = 16 * (_.length - 1);
8
+ return o + r + 8;
9
9
  }
10
- function r(_, t = 86) {
10
+ function c(_, t = 86) {
11
11
  return _ - 185 - t;
12
12
  }
13
- function i(_, t = S) {
13
+ function a(_, t = N) {
14
14
  return {
15
15
  usedPx: _,
16
16
  budgetPx: t,
@@ -18,21 +18,20 @@ function i(_, t = S) {
18
18
  overBy: Math.max(0, _ - t)
19
19
  };
20
20
  }
21
- function a(_) {
21
+ function C(_) {
22
22
  return _.scrollHeight;
23
23
  }
24
24
  export {
25
- T as NAV_FOCUS_GUTTER_PX,
26
- S as NAV_HEIGHT_BUDGET_PX,
25
+ V as NAV_FOCUS_GUTTER_PX,
26
+ N as NAV_HEIGHT_BUDGET_PX,
27
27
  H as NAV_ITEM_PX,
28
- V as NAV_SECTION_GAP_PX,
29
- L as NAV_SECTION_HEADER_PX,
30
- X as NAV_SHELL_CONSUMER_SLOTS_PX,
31
- c as NAV_SHELL_FIXED_PX,
28
+ T as NAV_SECTION_GAP_PX,
29
+ X as NAV_SECTION_HEADER_PX,
30
+ S as NAV_SHELL_CONSUMER_SLOTS_PX,
32
31
  P as NAV_SHELL_PACKAGE_FIXED_PX,
33
- i as checkNavHeightBudget,
34
- a as measureNavHeight,
35
- r as navBudgetForViewport,
36
- C as navContentHeight
32
+ a as checkNavHeightBudget,
33
+ C as measureNavHeight,
34
+ c as navBudgetForViewport,
35
+ i as navContentHeight
37
36
  };
38
37
  //# sourceMappingURL=nav-height-budget.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"nav-height-budget.js","sources":["../src/nav-height-budget.ts"],"sourcesContent":["// A sidebar's nav has to fit above the wallet chip and footer without\n// scrolling. Boost Outbound's admin role has already blown past that once, so\n// this is a real check, not a hypothetical.\n//\n// Sidebar calls checkNavHeightBudget and measureNavHeight itself now, and warns\n// outside a production build when a nav does not fit the column it was given\n// (B33-500, ADR-0005). These stay exported for the other half of the job:\n// checking a nav's shape from its data, before it is ever rendered.\n//\n// ===========================================================================\n// WHAT THIS IS A MODEL OF (B33-762)\n// ===========================================================================\n//\n// Every number below was read off a real render. That much was settled in\n// B33-752. What B33-762 found is that the module never said *which* render --\n// which shell's furniture it measured, which nav state its row costs describe,\n// or whose font metrics those costs came from. Three silent assumptions, and a\n// consumer importing the constants could not see any of them.\n//\n// So they are named here, each with the measurement behind it and each with the\n// direction its error runs. A budget that is wrong and documented is\n// recoverable. A budget that is right and undocumented is what produced this\n// ticket.\n//\n// All three measurements were taken on the built specimen in Chromium at\n// 1440x900, deviceScaleFactor 2, reduced motion -- the screenshot harness's own\n// conditions -- and re-taken on 23 Sep 2026 for this pass.\n//\n// ---------------------------------------------------------------------------\n// Assumption 1: the column is the stress specimen's, and part of it is not ours\n// ---------------------------------------------------------------------------\n//\n// NAV_SHELL_FIXED_PX is not one number this package can be correct about. Most\n// of the column is the package's own furniture, but the switcher, the wallet\n// chip and the footer are *slots* -- the consumer's markup, whose height the\n// package neither sets nor knows. Measured across the two routes that render\n// the whole shell:\n//\n// stress (820px) Wallet (900px)\n// wordmark block 56 56\n// switcher wrapper padding 16 16\n// .foot padding-block-start + gap 32 32\n// nav's negative gutter margin -8 -8\n// WalletChip, expanded 89 89\n// -- package subtotal 185 185\n// switcher slot contents 42 42\n// footer slot contents 44 32\n// -- consumer subtotal 86 74\n// == fixed furniture 271 259\n//\n// The package half is identical on both routes, which is the point: 185px is a\n// number this package owns and can defend. The 12px of difference is entirely\n// the footer slot -- the stress route's avatar row is 8+28+8, the Wallet\n// route's link row is 12+16+4.\n//\n// The default therefore stays pinned to the stress route's 271, unchanged from\n// B33-752. Re-pinning it to the Wallet, as this ticket was originally filed to\n// do, would move a published constant 12px toward the false-\"fits\" direction on\n// the strength of one consumer's footer -- and a consumer with a taller footer\n// than either route would then be handed a budget that over-counts their\n// column, which is exactly the B33-752 defect returning by another door.\n//\n// A consumer whose slots differ passes their own measured total as the second\n// argument to navBudgetForViewport. That is the honest interface: the package\n// states what it owns, and the caller states what they own.\n//\n// ---------------------------------------------------------------------------\n// Assumption 2: the row costs describe the expanded nav, not the rail\n// ---------------------------------------------------------------------------\n//\n// NAV_ITEM_PX and NAV_SECTION_HEADER_PX are expanded-nav costs, and the rail is\n// deliberately out of this model's scope. On the rail the label goes out of\n// flow (position: absolute, clipped), so the 16px icon is the tallest in-flow\n// child and a row measures 8+16+8 = 32px, not 33; and AppShell passes\n// `label={collapsed ? undefined : section.label}`, so NavSection renders no\n// header element at all and a headed section costs 0, not 30.\n//\n// Modelling that would mean a NAV_RAIL_ITEM_PX and a `collapsed` flag on\n// NavSectionShape -- a public API change to a module four products import, to\n// describe a state that cannot overflow. The measurement says it cannot. Same\n// specimen, same 13-item shape, same 820px stage:\n//\n// expanded rail\n// nav scrollHeight 575 472\n// column left for the nav 549 587\n// verdict scrolls fits, 115px spare\n//\n// The rail is shorter on both sides at once: its rows lose the label's line\n// box, and its furniture shrinks too (the wallet chip collapses 89 -> 75, the\n// switcher 42 -> 34, the footer 44 -> 28). So checking a rail nav against the\n// expanded model over-counts its content by 103px and under-counts its column\n// by 38px -- 141px of slack, both errors in the safe direction. The expanded\n// model is a conservative bound on the rail, and scripts/nav-height-budget-\n// check.mts now drives the rail stage and asserts exactly that, so the claim\n// fails CI if it ever stops being true.\n//\n// The runtime half was never affected: Sidebar measures the real DOM, so its\n// dev warning is correct in either state.\n//\n// ---------------------------------------------------------------------------\n// Assumption 3: the row costs are Inter's line boxes, and the package ships no\n// font\n// ---------------------------------------------------------------------------\n//\n// Neither NavItem's row and label nor NavSection's header sets a line-height;\n// the computed value is `normal`, so half of each row cost is token-derived\n// padding and the rest is whatever line box the consumer's font draws. This\n// package ships no font -- there is no @font-face anywhere in src/, `files` is\n// [\"dist\"], and a guard test forbids src/ from reaching the specimen's font\n// file. The faces are the consumer's; the tokens carry only a fallback stack.\n//\n// Swapping --font-sans on the built specimen, same stage, same 13-item shape:\n//\n// family row header nav scrollHeight\n// Inter (the tokens' head) 33 30 575\n// system-ui 33 29 572\n// Helvetica 32 29 559\n// Arial 32 28 556\n// Georgia 32 28 556\n//\n// Every face reachable on macOS renders shorter than Inter, so the model\n// over-counts -- the safe direction. The constants therefore stay as they are\n// and this dependency is recorded rather than removed.\n//\n// Removing it would mean setting an explicit line-height from the --leading-*\n// tokens, the way PageHeader already does (its title state measures a flat\n// 156px across every face). That is the better model, and it is not done here\n// on purpose: --leading-tight at --text-2xs gives a 29px section header against\n// today's 30, which moves the sidebar-*, navsection-* and appshell-* renders by\n// a pixel. A baseline change is a human approving a design change (CLAUDE.md),\n// and the pixel-ratio diff would wave a 1px header through in silence anyway\n// (B33-548, B33-772). It wants its own ticket with a human at the fidelity gate.\n//\n// >>> THE MEASUREMENT THIS MODEL IS STILL MISSING <<<\n//\n// 'Segoe UI' sits in the fallback stack and is reported to carry a ~1.33 normal\n// line-height factor, which at 14px would give a ~19px line box and a 35px row\n// -- an UNDER-count, the one direction B33-752 exists to forbid. It is NOT\n// recorded above because it could not be measured: this pass ran on macOS,\n// where Segoe UI is not installed. Requesting it resolves to the fallback --\n// canvas measureText at 14px gives 'Segoe UI' 241.097px for the same string as\n// a deliberately nonsense family name, against 249.737px for Helvetica and\n// 268.837px for Inter -- so the 32/29 a naive swap reports is Helvetica wearing\n// Segoe UI's name, not Segoe UI.\n//\n// A human on Windows has to take it, and nothing here should be tuned to a\n// guessed number in the meantime. Until then the honest statement of this\n// module's scope is: these costs hold for a --font-sans whose `normal` line box\n// at 14px is 17px or less, which is every face measured. A Windows consumer is\n// outside that and is protected only by Sidebar's runtime warning, which\n// measures the real DOM and is right whatever the font does.\n//\n// The browser check cannot close this either -- the specimen bundles Inter, so\n// it can only ever confirm Inter's numbers.\n//\n// ===========================================================================\n//\n// NOTE for a human: boostledger/src/ui/shell/nav.ts carries the same constants\n// and the same navHeight arithmetic, inherited from the architecture doc's\n// §4.1, and has the same under-count/over-count. It is in another repo and\n// another GitHub org, so it is not corrected here; it wants the same fix\n// (B33-752).\n\n// The part of the fixed furniture this package draws itself, and is therefore\n// the only part it can be correct about: the wordmark block (56), the switcher\n// wrapper's padding (16), `.foot`'s padding-block-start and gap (32) and the\n// expanded WalletChip (89), less the 8px the nav takes back as negative margin\n// for its focus gutter. Measured identical on both shell routes -- see\n// \"Assumption 1\" above. The nav's own scroll gutter is NOT part of this: it\n// sits inside the nav's client box, so it lives in the budget (and in\n// navContentHeight), not in the furniture -- counting it here as well would\n// double it.\nexport const NAV_SHELL_PACKAGE_FIXED_PX = 185\n\n// The slots the consumer fills, as the stress specimen fills them: a 42px\n// switcher button and a 44px account footer. This is an ALLOWANCE, not a\n// package fact -- the Wallet route's own slots total 74px, and a consumer with\n// a taller footer than either exceeds it. A consumer who knows their own slot\n// heights passes the total to navBudgetForViewport instead of taking this.\nexport const NAV_SHELL_CONSUMER_SLOTS_PX = 86\n\n// The sidebar's fixed furniture above and below the nav, as the stress\n// specimen renders it: the package's own 185 plus that route's 86 of slots.\n// Equal to the column height less the nav's own client box (820 - 549), which\n// is how B33-752 measured it and why the value is unchanged from it.\nexport const NAV_SHELL_FIXED_PX = NAV_SHELL_PACKAGE_FIXED_PX + NAV_SHELL_CONSUMER_SLOTS_PX\n\n// The per-row costs of the EXPANDED nav, each the padding the CSS sets plus the\n// line box the text draws into. A nav item is --spacing-2 top and bottom (16px)\n// around a 14px label's 17px line box; a section header is the same 16px around\n// an 11px label's 14px line box. Measured in the specimen render, not derived\n// from Paper (see NavItem.module.css `.row` and NavSection.module.css\n// `.header`).\n//\n// Two things these numbers do not cover, both argued in full above. They are\n// the rail's costs only by accident -- a rail row is 32px and a rail section\n// header is nothing at all (assumption 2) -- and their line-box half belongs to\n// the consumer's font, not to this package (assumption 3). Both errors run in\n// the over-counting direction for every face that could be measured; the\n// Windows case is unmeasured and is the one candidate for the other direction.\nexport const NAV_ITEM_PX = 33\nexport const NAV_SECTION_HEADER_PX = 30\n\n// The gap the nav sets between its sections (--spacing-4, AppShell.module.css\n// `.nav`), counted once between each adjacent pair.\nexport const NAV_SECTION_GAP_PX = 16\n\n// The nav's own scroll gutter: --spacing-1 of padding top and bottom, the 4px\n// each way that keeps NavItem's focus ring off the scrollport edge (ADR-0005).\n// It is part of the nav's box, so it counts toward the height the nav takes.\nexport const NAV_FOCUS_GUTTER_PX = 8\n\n// The nav budget at the reference 900px viewport: what the column leaves once\n// the fixed furniture is taken. Derived from NAV_SHELL_FIXED_PX so the two\n// cannot disagree; used as the default budget and named in the dev warning.\nexport const NAV_HEIGHT_BUDGET_PX = navBudgetForViewport(900)\n\n/** One section of a nav, described by its shape rather than its rendered rows. */\nexport interface NavSectionShape {\n /** Whether the section carries a header label (its NAV_SECTION_HEADER_PX cost). */\n hasHeader: boolean\n /** How many nav items the section holds (NAV_ITEM_PX each). */\n itemCount: number\n}\n\n/**\n * The per-row costs to charge, for a consumer who has measured their own.\n *\n * The defaults are Inter's, and are right for every face this package could\n * measure. A consumer rendering in a font whose `normal` line box at 14px is\n * taller than 17px -- `Segoe UI` is the known candidate, unmeasured -- gets a\n * model that under-counts, and can correct it here rather than forking the\n * check. Measure a rendered row's `offsetHeight`, do not estimate it.\n */\nexport interface NavRowCosts {\n /** What one nav row costs. Defaults to NAV_ITEM_PX, the expanded row in Inter. */\n itemPx?: number\n /** What one section header costs. Defaults to NAV_SECTION_HEADER_PX. */\n sectionHeaderPx?: number\n}\n\n/**\n * The vertical space an EXPANDED nav takes, summed from its sections' shapes --\n * the rows and headers, the gaps the nav sets between sections, and the nav's\n * own scroll gutter. This is the whole box the nav occupies (its rendered\n * scrollHeight), so the budget can be checked from the nav's data alone, before\n * it is rendered into a sidebar that might not fit it -- Boost Outbound's admin\n * role has overflowed this once already.\n *\n * The rail is out of scope and needs no separate model: a collapsed nav is\n * shorter than the same nav expanded, in a column that is taller, so this is a\n * conservative bound on it (B33-762, and the check that pins it).\n *\n * Pass `costs` only if you have measured a rendered row in your own font; the\n * defaults are the package's, measured in Inter.\n */\nexport function navContentHeight(sections: NavSectionShape[], costs: NavRowCosts = {}): number {\n if (sections.length === 0) return 0\n const itemPx = costs.itemPx ?? NAV_ITEM_PX\n const sectionHeaderPx = costs.sectionHeaderPx ?? NAV_SECTION_HEADER_PX\n const rows = sections.reduce(\n (used, section) => used + (section.hasHeader ? sectionHeaderPx : 0) + section.itemCount * itemPx,\n 0,\n )\n const gaps = NAV_SECTION_GAP_PX * (sections.length - 1)\n return rows + gaps + NAV_FOCUS_GUTTER_PX\n}\n\n/**\n * The nav's budget at a given viewport: what is left once the fixed shell parts\n * are taken.\n *\n * `consumerSlotsPx` is the total height of the three slots this package does\n * not own -- the switcher, the wallet chip's neighbours and the sidebar footer\n * as the consumer renders them. It defaults to the stress specimen's 86px\n * allowance, which is what NAV_SHELL_FIXED_PX assumes. A consumer whose footer\n * or switcher is taller than that MUST pass their own measured total, or this\n * will over-count their column and can report a fit that scrolls.\n */\nexport function navBudgetForViewport(\n viewportPx: number,\n consumerSlotsPx: number = NAV_SHELL_CONSUMER_SLOTS_PX,\n): number {\n return viewportPx - NAV_SHELL_PACKAGE_FIXED_PX - consumerSlotsPx\n}\n\nexport interface NavHeightBudget {\n usedPx: number\n budgetPx: number\n fits: boolean\n overBy: number\n}\n\nexport function checkNavHeightBudget(usedPx: number, budgetPx: number = NAV_HEIGHT_BUDGET_PX): NavHeightBudget {\n return {\n usedPx,\n budgetPx,\n fits: usedPx <= budgetPx,\n overBy: Math.max(0, usedPx - budgetPx),\n }\n}\n\nexport function measureNavHeight(nav: HTMLElement): number {\n return nav.scrollHeight\n}\n"],"names":["NAV_SHELL_PACKAGE_FIXED_PX","NAV_SHELL_CONSUMER_SLOTS_PX","NAV_SHELL_FIXED_PX","NAV_ITEM_PX","NAV_SECTION_HEADER_PX","NAV_SECTION_GAP_PX","NAV_FOCUS_GUTTER_PX","NAV_HEIGHT_BUDGET_PX","navBudgetForViewport","navContentHeight","sections","costs","itemPx","sectionHeaderPx","rows","used","section","gaps","viewportPx","consumerSlotsPx","checkNavHeightBudget","usedPx","budgetPx","measureNavHeight","nav"],"mappings":"AA4KO,MAAMA,IAA6B,KAO7BC,IAA8B,IAM9BC,IAAqB,KAerBC,IAAc,IACdC,IAAwB,IAIxBC,IAAqB,IAKrBC,IAAsB,GAKtBC,IAAuBC,EAAqB,GAAG;AAyCrD,SAASC,EAAiBC,GAA6BC,IAAqB,IAAY;AAC7F,MAAID,EAAS,WAAW,EAAG,QAAO;AAClC,QAAME,IAASD,EAAM,UAAU,IACzBE,IAAkBF,EAAM,mBAAmB,IAC3CG,IAAOJ,EAAS;AAAA,IACpB,CAACK,GAAMC,MAAYD,KAAQC,EAAQ,YAAYH,IAAkB,KAAKG,EAAQ,YAAYJ;AAAA,IAC1F;AAAA,EAAA,GAEIK,IAAO,MAAsBP,EAAS,SAAS;AACrD,SAAOI,IAAOG,IAAO;AACvB;AAaO,SAAST,EACdU,GACAC,IAA0B,IAClB;AACR,SAAOD,IAAa,MAA6BC;AACnD;AASO,SAASC,EAAqBC,GAAgBC,IAAmBf,GAAuC;AAC7G,SAAO;AAAA,IACL,QAAAc;AAAA,IACA,UAAAC;AAAA,IACA,MAAMD,KAAUC;AAAA,IAChB,QAAQ,KAAK,IAAI,GAAGD,IAASC,CAAQ;AAAA,EAAA;AAEzC;AAEO,SAASC,EAAiBC,GAA0B;AACzD,SAAOA,EAAI;AACb;"}
1
+ {"version":3,"file":"nav-height-budget.js","sources":["../src/nav-height-budget.ts"],"sourcesContent":["// A sidebar's nav has to fit above the wallet chip and footer without\n// scrolling. Boost Outbound's admin role has already blown past that once, so\n// this is a real check, not a hypothetical.\n//\n// Sidebar calls checkNavHeightBudget and measureNavHeight itself now, and warns\n// outside a production build when a nav does not fit the column it was given\n// (B33-500, ADR-0005). These stay exported for the other half of the job:\n// checking a nav's shape from its data, before it is ever rendered.\n//\n// WHAT OF THIS IS PUBLISHED (B33-483)\n//\n// src/index.ts re-exports three functions -- navContentHeight,\n// navBudgetForViewport, checkNavHeightBudget -- and three types:\n// NavHeightBudget, NavRowCosts, NavSectionShape. Everything else below is\n// module-internal: the eight NAV_*_PX constants and measureNavHeight are\n// exported from this file for the tests, for NavHeightBudgetWarning and for\n// scripts/nav-height-budget-check.mts, and they do not reach a consumer.\n//\n// That is deliberate and it is what the rest of this header argues for. Every\n// number here is provisional in a documented way -- four of them changed value\n// in a release with no note (B33-763), and the row costs' font dependency is\n// still unmeasured for Windows (the \">>> THE MEASUREMENT THIS MODEL IS STILL\n// MISSING <<<\" block below, B33-1009). Published, they become values a 1.0\n// cannot move without a major bump. Internal, they stay free to move with the\n// measurement that finally pins them. Do not re-add them to the barrel to save\n// a caller a line: the functions apply them as defaults and NavRowCosts takes\n// the override, so no caller needs to read one.\n//\n// ===========================================================================\n// WHAT THIS IS A MODEL OF (B33-762)\n// ===========================================================================\n//\n// Every number below was read off a real render. That much was settled in\n// B33-752. What B33-762 found is that the module never said *which* render --\n// which shell's furniture it measured, which nav state its row costs describe,\n// or whose font metrics those costs came from. Three silent assumptions, and a\n// consumer importing the constants could not see any of them.\n//\n// So they are named here, each with the measurement behind it and each with the\n// direction its error runs. A budget that is wrong and documented is\n// recoverable. A budget that is right and undocumented is what produced this\n// ticket.\n//\n// All three measurements were taken on the built specimen in Chromium at\n// 1440x900, deviceScaleFactor 2, reduced motion -- the screenshot harness's own\n// conditions -- and re-taken on 23 Sep 2026 for this pass.\n//\n// ---------------------------------------------------------------------------\n// Assumption 1: the column is the stress specimen's, and part of it is not ours\n// ---------------------------------------------------------------------------\n//\n// NAV_SHELL_FIXED_PX is not one number this package can be correct about. Most\n// of the column is the package's own furniture, but the switcher, the wallet\n// chip and the footer are *slots* -- the consumer's markup, whose height the\n// package neither sets nor knows. Measured across the two routes that render\n// the whole shell:\n//\n// stress (820px) Wallet (900px)\n// wordmark block 56 56\n// switcher wrapper padding 16 16\n// .foot padding-block-start + gap 32 32\n// nav's negative gutter margin -8 -8\n// WalletChip, expanded 89 89\n// -- package subtotal 185 185\n// switcher slot contents 42 42\n// footer slot contents 44 32\n// -- consumer subtotal 86 74\n// == fixed furniture 271 259\n//\n// The package half is identical on both routes, which is the point: 185px is a\n// number this package owns and can defend. The 12px of difference is entirely\n// the footer slot -- the stress route's avatar row is 8+28+8, the Wallet\n// route's link row is 12+16+4.\n//\n// The default therefore stays pinned to the stress route's 271, unchanged from\n// B33-752. Re-pinning it to the Wallet, as this ticket was originally filed to\n// do, would move a published constant 12px toward the false-\"fits\" direction on\n// the strength of one consumer's footer -- and a consumer with a taller footer\n// than either route would then be handed a budget that over-counts their\n// column, which is exactly the B33-752 defect returning by another door.\n//\n// A consumer whose slots differ passes their own measured total as the second\n// argument to navBudgetForViewport. That is the honest interface: the package\n// states what it owns, and the caller states what they own.\n//\n// ---------------------------------------------------------------------------\n// Assumption 2: the row costs describe the expanded nav, not the rail\n// ---------------------------------------------------------------------------\n//\n// NAV_ITEM_PX and NAV_SECTION_HEADER_PX are expanded-nav costs, and the rail is\n// deliberately out of this model's scope. On the rail the label goes out of\n// flow (position: absolute, clipped), so the 16px icon is the tallest in-flow\n// child and a row measures 8+16+8 = 32px, not 33; and AppShell passes\n// `label={collapsed ? undefined : section.label}`, so NavSection renders no\n// header element at all and a headed section costs 0, not 30.\n//\n// Modelling that would mean a NAV_RAIL_ITEM_PX and a `collapsed` flag on\n// NavSectionShape -- a public API change to a module four products import, to\n// describe a state that cannot overflow. The measurement says it cannot. Same\n// specimen, same 13-item shape, same 820px stage:\n//\n// expanded rail\n// nav scrollHeight 575 472\n// column left for the nav 549 587\n// verdict scrolls fits, 115px spare\n//\n// The rail is shorter on both sides at once: its rows lose the label's line\n// box, and its furniture shrinks too (the wallet chip collapses 89 -> 75, the\n// switcher 42 -> 34, the footer 44 -> 28). So checking a rail nav against the\n// expanded model over-counts its content by 103px and under-counts its column\n// by 38px -- 141px of slack, both errors in the safe direction. The expanded\n// model is a conservative bound on the rail, and scripts/nav-height-budget-\n// check.mts now drives the rail stage and asserts exactly that, so the claim\n// fails CI if it ever stops being true.\n//\n// The runtime half was never affected: Sidebar measures the real DOM, so its\n// dev warning is correct in either state.\n//\n// ---------------------------------------------------------------------------\n// Assumption 3: the row costs are Inter's line boxes, and the package ships no\n// font\n// ---------------------------------------------------------------------------\n//\n// Neither NavItem's row and label nor NavSection's header sets a line-height;\n// the computed value is `normal`, so half of each row cost is token-derived\n// padding and the rest is whatever line box the consumer's font draws. This\n// package ships no font -- there is no @font-face anywhere in src/, `files` is\n// [\"dist\"], and a guard test forbids src/ from reaching the specimen's font\n// file. The faces are the consumer's; the tokens carry only a fallback stack.\n//\n// Swapping --font-sans on the built specimen, same stage, same 13-item shape:\n//\n// family row header nav scrollHeight\n// Inter (the tokens' head) 33 30 575\n// system-ui 33 29 572\n// Helvetica 32 29 559\n// Arial 32 28 556\n// Georgia 32 28 556\n//\n// Every face reachable on macOS renders shorter than Inter, so the model\n// over-counts -- the safe direction. The constants therefore stay as they are\n// and this dependency is recorded rather than removed.\n//\n// Removing it would mean setting an explicit line-height from the --leading-*\n// tokens, the way PageHeader already does (its title state measures a flat\n// 156px across every face). That is the better model, and it is not done here\n// on purpose: --leading-tight at --text-2xs gives a 29px section header against\n// today's 30, which moves the sidebar-*, navsection-* and appshell-* renders by\n// a pixel. A baseline change is a human approving a design change (CLAUDE.md),\n// and the pixel-ratio diff would wave a 1px header through in silence anyway\n// (B33-548, B33-772). It wants its own ticket with a human at the fidelity gate.\n//\n// >>> THE MEASUREMENT THIS MODEL IS STILL MISSING <<<\n//\n// 'Segoe UI' sits in the fallback stack and is reported to carry a ~1.33 normal\n// line-height factor, which at 14px would give a ~19px line box and a 35px row\n// -- an UNDER-count, the one direction B33-752 exists to forbid. It is NOT\n// recorded above because it could not be measured: this pass ran on macOS,\n// where Segoe UI is not installed. Requesting it resolves to the fallback --\n// canvas measureText at 14px gives 'Segoe UI' 241.097px for the same string as\n// a deliberately nonsense family name, against 249.737px for Helvetica and\n// 268.837px for Inter -- so the 32/29 a naive swap reports is Helvetica wearing\n// Segoe UI's name, not Segoe UI.\n//\n// A human on Windows has to take it, and nothing here should be tuned to a\n// guessed number in the meantime. Until then the honest statement of this\n// module's scope is: these costs hold for a --font-sans whose `normal` line box\n// at 14px is 17px or less, which is every face measured. A Windows consumer is\n// outside that and is protected only by Sidebar's runtime warning, which\n// measures the real DOM and is right whatever the font does.\n//\n// The browser check cannot close this either -- the specimen bundles Inter, so\n// it can only ever confirm Inter's numbers.\n//\n// ===========================================================================\n//\n// NOTE for a human: boostledger/src/ui/shell/nav.ts carries the same constants\n// and the same navHeight arithmetic, inherited from the architecture doc's\n// §4.1, and has the same under-count/over-count. It is in another repo and\n// another GitHub org, so it is not corrected here; it wants the same fix\n// (B33-752).\n\n// The part of the fixed furniture this package draws itself, and is therefore\n// the only part it can be correct about: the wordmark block (56), the switcher\n// wrapper's padding (16), `.foot`'s padding-block-start and gap (32) and the\n// expanded WalletChip (89), less the 8px the nav takes back as negative margin\n// for its focus gutter. Measured identical on both shell routes -- see\n// \"Assumption 1\" above. The nav's own scroll gutter is NOT part of this: it\n// sits inside the nav's client box, so it lives in the budget (and in\n// navContentHeight), not in the furniture -- counting it here as well would\n// double it.\nexport const NAV_SHELL_PACKAGE_FIXED_PX = 185\n\n// The slots the consumer fills, as the stress specimen fills them: a 42px\n// switcher button and a 44px account footer. This is an ALLOWANCE, not a\n// package fact -- the Wallet route's own slots total 74px, and a consumer with\n// a taller footer than either exceeds it. A consumer who knows their own slot\n// heights passes the total to navBudgetForViewport instead of taking this.\nexport const NAV_SHELL_CONSUMER_SLOTS_PX = 86\n\n// The sidebar's fixed furniture above and below the nav, as the stress\n// specimen renders it: the package's own 185 plus that route's 86 of slots.\n// Equal to the column height less the nav's own client box (820 - 549), which\n// is how B33-752 measured it and why the value is unchanged from it.\nexport const NAV_SHELL_FIXED_PX = NAV_SHELL_PACKAGE_FIXED_PX + NAV_SHELL_CONSUMER_SLOTS_PX\n\n// The per-row costs of the EXPANDED nav, each the padding the CSS sets plus the\n// line box the text draws into. A nav item is --spacing-2 top and bottom (16px)\n// around a 14px label's 17px line box; a section header is the same 16px around\n// an 11px label's 14px line box. Measured in the specimen render, not derived\n// from Paper (see NavItem.module.css `.row` and NavSection.module.css\n// `.header`).\n//\n// Two things these numbers do not cover, both argued in full above. They are\n// the rail's costs only by accident -- a rail row is 32px and a rail section\n// header is nothing at all (assumption 2) -- and their line-box half belongs to\n// the consumer's font, not to this package (assumption 3). Both errors run in\n// the over-counting direction for every face that could be measured; the\n// Windows case is unmeasured and is the one candidate for the other direction.\nexport const NAV_ITEM_PX = 33\nexport const NAV_SECTION_HEADER_PX = 30\n\n// The gap the nav sets between its sections (--spacing-4, AppShell.module.css\n// `.nav`), counted once between each adjacent pair.\nexport const NAV_SECTION_GAP_PX = 16\n\n// The nav's own scroll gutter: --spacing-1 of padding top and bottom, the 4px\n// each way that keeps NavItem's focus ring off the scrollport edge (ADR-0005).\n// It is part of the nav's box, so it counts toward the height the nav takes.\nexport const NAV_FOCUS_GUTTER_PX = 8\n\n// The nav budget at the reference 900px viewport: what the column leaves once\n// the fixed furniture is taken. Derived from NAV_SHELL_FIXED_PX so the two\n// cannot disagree; used as the default budget and named in the dev warning.\nexport const NAV_HEIGHT_BUDGET_PX = navBudgetForViewport(900)\n\n/** One section of a nav, described by its shape rather than its rendered rows. */\nexport interface NavSectionShape {\n /** Whether the section carries a header label (its NAV_SECTION_HEADER_PX cost). */\n hasHeader: boolean\n /** How many nav items the section holds (NAV_ITEM_PX each). */\n itemCount: number\n}\n\n/**\n * The per-row costs to charge, for a consumer who has measured their own.\n *\n * The defaults are Inter's, and are right for every face this package could\n * measure. A consumer rendering in a font whose `normal` line box at 14px is\n * taller than 17px -- `Segoe UI` is the known candidate, unmeasured -- gets a\n * model that under-counts, and can correct it here rather than forking the\n * check. Measure a rendered row's `offsetHeight`, do not estimate it.\n *\n * The NAV_*_PX constants named below are this module's own and are not published\n * (B33-483); they are the defaults, not something a caller reads. Omitting a\n * field here takes the default, which is why there is nothing to import.\n */\nexport interface NavRowCosts {\n /** What one nav row costs. Defaults to NAV_ITEM_PX, the expanded row in Inter. */\n itemPx?: number\n /** What one section header costs. Defaults to NAV_SECTION_HEADER_PX. */\n sectionHeaderPx?: number\n}\n\n/**\n * The vertical space an EXPANDED nav takes, summed from its sections' shapes --\n * the rows and headers, the gaps the nav sets between sections, and the nav's\n * own scroll gutter. This is the whole box the nav occupies (its rendered\n * scrollHeight), so the budget can be checked from the nav's data alone, before\n * it is rendered into a sidebar that might not fit it -- Boost Outbound's admin\n * role has overflowed this once already.\n *\n * The rail is out of scope and needs no separate model: a collapsed nav is\n * shorter than the same nav expanded, in a column that is taller, so this is a\n * conservative bound on it (B33-762, and the check that pins it).\n *\n * Pass `costs` only if you have measured a rendered row in your own font; the\n * defaults are the package's, measured in Inter.\n */\nexport function navContentHeight(sections: NavSectionShape[], costs: NavRowCosts = {}): number {\n if (sections.length === 0) return 0\n const itemPx = costs.itemPx ?? NAV_ITEM_PX\n const sectionHeaderPx = costs.sectionHeaderPx ?? NAV_SECTION_HEADER_PX\n const rows = sections.reduce(\n (used, section) => used + (section.hasHeader ? sectionHeaderPx : 0) + section.itemCount * itemPx,\n 0,\n )\n const gaps = NAV_SECTION_GAP_PX * (sections.length - 1)\n return rows + gaps + NAV_FOCUS_GUTTER_PX\n}\n\n/**\n * The nav's budget at a given viewport: what is left once the fixed shell parts\n * are taken.\n *\n * `consumerSlotsPx` is the total height of the three slots this package does\n * not own -- the switcher, the wallet chip's neighbours and the sidebar footer\n * as the consumer renders them. It defaults to the stress specimen's 86px\n * allowance, which is what NAV_SHELL_FIXED_PX assumes. A consumer whose footer\n * or switcher is taller than that MUST pass their own measured total, or this\n * will over-count their column and can report a fit that scrolls.\n */\nexport function navBudgetForViewport(\n viewportPx: number,\n consumerSlotsPx: number = NAV_SHELL_CONSUMER_SLOTS_PX,\n): number {\n return viewportPx - NAV_SHELL_PACKAGE_FIXED_PX - consumerSlotsPx\n}\n\nexport interface NavHeightBudget {\n usedPx: number\n budgetPx: number\n fits: boolean\n overBy: number\n}\n\nexport function checkNavHeightBudget(usedPx: number, budgetPx: number = NAV_HEIGHT_BUDGET_PX): NavHeightBudget {\n return {\n usedPx,\n budgetPx,\n fits: usedPx <= budgetPx,\n overBy: Math.max(0, usedPx - budgetPx),\n }\n}\n\nexport function measureNavHeight(nav: HTMLElement): number {\n return nav.scrollHeight\n}\n"],"names":["NAV_SHELL_PACKAGE_FIXED_PX","NAV_SHELL_CONSUMER_SLOTS_PX","NAV_ITEM_PX","NAV_SECTION_HEADER_PX","NAV_SECTION_GAP_PX","NAV_FOCUS_GUTTER_PX","NAV_HEIGHT_BUDGET_PX","navBudgetForViewport","navContentHeight","sections","costs","itemPx","sectionHeaderPx","rows","used","section","gaps","viewportPx","consumerSlotsPx","checkNavHeightBudget","usedPx","budgetPx","measureNavHeight","nav"],"mappings":"AA+LO,MAAMA,IAA6B,KAO7BC,IAA8B,IAqB9BC,IAAc,IACdC,IAAwB,IAIxBC,IAAqB,IAKrBC,IAAsB,GAKtBC,IAAuBC,EAAqB,GAAG;AA6CrD,SAASC,EAAiBC,GAA6BC,IAAqB,IAAY;AAC7F,MAAID,EAAS,WAAW,EAAG,QAAO;AAClC,QAAME,IAASD,EAAM,UAAU,IACzBE,IAAkBF,EAAM,mBAAmB,IAC3CG,IAAOJ,EAAS;AAAA,IACpB,CAACK,GAAMC,MAAYD,KAAQC,EAAQ,YAAYH,IAAkB,KAAKG,EAAQ,YAAYJ;AAAA,IAC1F;AAAA,EAAA,GAEIK,IAAO,MAAsBP,EAAS,SAAS;AACrD,SAAOI,IAAOG,IAAO;AACvB;AAaO,SAAST,EACdU,GACAC,IAA0B,IAClB;AACR,SAAOD,IAAa,MAA6BC;AACnD;AASO,SAASC,EAAqBC,GAAgBC,IAAmBf,GAAuC;AAC7G,SAAO;AAAA,IACL,QAAAc;AAAA,IACA,UAAAC;AAAA,IACA,MAAMD,KAAUC;AAAA,IAChB,QAAQ,KAAK,IAAI,GAAGD,IAASC,CAAQ;AAAA,EAAA;AAEzC;AAEO,SAASC,EAAiBC,GAA0B;AACzD,SAAOA,EAAI;AACb;"}
package/dist/style.css CHANGED
@@ -1 +1 @@
1
- .btn-primary,.btn-quiet,.btn-danger{cursor:pointer;transition:background-color var(--dur-fast) var(--ease-out),border-color var(--dur-fast) var(--ease-out),transform var(--dur-instant) var(--ease-out)}.btn-primary{border:none;background:var(--color-primary);color:var(--color-primary-foreground)}.btn-primary:hover:not(:disabled){background:var(--color-primary-hover)}.btn-quiet{background:transparent;color:var(--color-foreground-secondary)}.btn-quiet:hover:not(:disabled){background:var(--color-surface-muted)}.btn-danger{border:1px solid var(--color-danger);background:var(--color-danger);color:var(--color-neutral-0)}.btn-danger:hover:not(:disabled){border-color:color-mix(in srgb,var(--color-danger) 85%,var(--color-neutral-950));background:color-mix(in srgb,var(--color-danger) 85%,var(--color-neutral-950))}.btn-primary:disabled,.btn-quiet:disabled,.btn-danger:disabled{opacity:.55;cursor:not-allowed}.spinner{box-sizing:border-box;width:var(--spacing-4_5);height:var(--spacing-4_5);border:2px solid var(--color-neutral-0);border-right-color:transparent;border-radius:var(--radius-full);animation:boost33-spin var(--dur-spin) linear infinite}.spinner--sm{width:var(--spacing-3_5);height:var(--spacing-3_5)}.spinner--primary{border-color:var(--color-primary);border-right-color:transparent}@keyframes boost33-spin{to{transform:rotate(360deg)}}.sk{display:block;border-radius:var(--radius-md);background:var(--color-surface-muted);animation:boost33-pulse var(--dur-pulse) ease-in-out infinite}.sk-group{animation:boost33-appear var(--dur-base) var(--ease-out) var(--dur-fast) both}.reveal{animation:boost33-appear var(--dur-base) var(--ease-out) both}@keyframes boost33-pulse{50%{opacity:.55}}@keyframes boost33-appear{0%{opacity:0}}@media(prefers-reduced-motion:no-preference){.btn-primary:active:not(:disabled),.btn-quiet:active:not(:disabled),.btn-danger:active:not(:disabled){transform:scale(.98)}}@media(prefers-reduced-motion:reduce){.sk,.sk-group,.reveal,.spinner{animation:none}*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:0s!important;scroll-behavior:auto!important}}._sidebar_17kel_7{display:flex;flex-direction:column;box-sizing:border-box;width:var(--container-sidebar);height:100%;padding-inline:var(--spacing-3);border-right:1px solid var(--color-border);background:var(--color-background);font-family:var(--font-sans)}._sidebar_17kel_7[data-collapsed=true]{width:var(--container-sidebar-rail)}._sidebar_17kel_7>nav{flex-shrink:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;margin:calc(-1 * var(--spacing-1));padding:var(--spacing-1);scroll-padding:var(--spacing-1)}@media(prefers-reduced-motion:reduce){._sidebar_17kel_7>nav{scroll-behavior:auto}}._section_3sxsg_5{display:flex;flex-direction:column}._header_3sxsg_13{margin:0;padding:var(--spacing-2) var(--spacing-2_5);color:var(--color-foreground-muted);font-family:var(--font-sans);font-size:var(--text-2xs);font-weight:var(--font-weight-semibold);letter-spacing:var(--tracking-caps);text-transform:uppercase}._wordmark_1k88r_4{display:flex;align-items:center;gap:var(--spacing-2_5);font-family:var(--font-display)}._mark_1k88r_17{display:inline-flex;flex:0 0 var(--spacing-6);align-items:center;justify-content:center;width:var(--spacing-6);height:var(--spacing-6)}._mark_1k88r_17 svg{width:100%;height:100%}._name_1k88r_35{color:var(--color-foreground);font-family:var(--font-display);font-size:var(--text-lg);font-weight:var(--font-weight-semibold);letter-spacing:var(--tracking-tight)}._wordmark_1k88r_4[data-collapsed=true] ._name_1k88r_35{position:absolute;overflow:hidden;clip-path:inset(50%);width:1px;height:1px;white-space:nowrap}._wordmark_1k88r_4[data-collapsed=true]{justify-content:center}._tile_5vhrh_12{fill:var(--color-neutral-950)}._ring_5vhrh_16{fill:var(--color-primary)}._mark_5vhrh_24{display:block}._mark_1jt5g_6{display:block}._ground_1jt5g_17{rx:var(--radius-product-tile)}._row_w21mf_4{display:flex;align-items:center;width:100%;gap:var(--spacing-2_5);padding:var(--spacing-2) var(--spacing-2_5);border:none;border-radius:var(--radius-lg);background:transparent;font-family:var(--font-sans);text-align:left;text-decoration:none;cursor:pointer;transition:background-color var(--dur-fast) var(--ease-out)}._row_w21mf_4:hover{background:var(--color-surface-muted)}._row_w21mf_4[data-active=true]{background:var(--color-surface)}._row_w21mf_4:focus-visible{outline:2px solid var(--color-primary);outline-offset:2px}._icon_w21mf_41{display:inline-flex;flex:0 0 var(--spacing-4);align-items:center;justify-content:center;width:var(--spacing-4);height:var(--spacing-4);color:var(--color-foreground-secondary)}._icon_w21mf_41 svg{width:100%;height:100%}._row_w21mf_4[data-active=true] ._icon_w21mf_41{color:var(--color-foreground)}._row_w21mf_4[data-collapsed=true]{justify-content:center}._label_w21mf_66{flex:1 1 auto;min-width:0;overflow:hidden;color:var(--color-foreground-secondary);font-size:var(--text-nav-label);font-weight:var(--font-weight-regular);text-overflow:ellipsis;white-space:nowrap}._row_w21mf_4[data-active=true] ._label_w21mf_66{color:var(--color-foreground);font-weight:var(--font-weight-semibold)}._row_w21mf_4[data-collapsed=true] ._label_w21mf_66{position:absolute;overflow:hidden;clip-path:inset(50%);width:1px;height:1px;white-space:nowrap}._trailingSlot_w21mf_92{display:flex;flex:0 0 var(--spacing-5_5);align-items:center;justify-content:center;width:var(--spacing-5_5);font-family:var(--font-sans);font-size:var(--text-nav-label)}._row_w21mf_4[data-collapsed=true] ._trailingSlot_w21mf_92{display:none}._count_w21mf_106{color:var(--color-foreground-muted)}._countUrgent_w21mf_110{color:var(--color-primary)}@media(prefers-reduced-motion:reduce){._row_w21mf_4{transition:none}}._chip_1th8y_37{--chip-lift: 0px;position:relative;box-sizing:border-box;width:100%;padding:calc(var(--spacing-3) + var(--chip-lift));border:1px solid var(--color-border);border-radius:var(--radius-2xl);background:transparent;font-family:var(--font-sans)}._chip_1th8y_37:not([data-unknown]):hover,._chip_1th8y_37:not([data-unknown]):focus-within,._chip_1th8y_37[data-threshold]{--chip-lift: var(--spacing-0_5);box-shadow:var(--shadow-overlay)}._chip_1th8y_37:not([data-threshold]):not([data-unknown]):hover,._chip_1th8y_37:not([data-threshold]):not([data-unknown]):focus-within{background:var(--color-surface)}._chip_1th8y_37:focus-visible{outline:2px solid var(--color-primary);outline-offset:2px}._header_1th8y_74{display:flex;align-items:center;gap:var(--spacing-2)}._chip_1th8y_37:not([data-threshold]):not([data-unknown]) ._header_1th8y_74{padding-right:var(--spacing-topup-reserve)}._name_1th8y_83{min-width:0;overflow:hidden;color:var(--color-foreground-muted);font-size:var(--text-xs);font-weight:var(--font-weight-medium);line-height:var(--leading-tight);text-overflow:ellipsis;white-space:nowrap}._pill_1th8y_162{padding:var(--spacing-0_5) var(--spacing-2);border-radius:var(--radius-full);background:var(--color-warning-subtle);color:var(--color-warning);font-size:var(--text-2xs);font-weight:var(--font-weight-semibold)}._figure_1th8y_171{display:flex;align-items:baseline;gap:var(--spacing-1_5);margin-top:calc(var(--spacing-2_5) + var(--chip-lift))}._amount_1th8y_186{color:var(--color-foreground);font-family:var(--font-mono);font-size:var(--text-2xl);font-weight:var(--font-weight-bold);letter-spacing:var(--tracking-tight);line-height:var(--leading-none)}._unit_1th8y_195{color:var(--color-foreground-muted);font-size:var(--text-xs)}._retry_1th8y_200{width:var(--spacing-3);height:var(--spacing-3);color:var(--color-foreground-muted)}._bar_1th8y_209{display:flex;overflow:hidden;width:100%;height:calc(var(--spacing-1_25) + var(--chip-lift));margin-top:calc(var(--spacing-2_5) + var(--chip-lift));border-radius:var(--radius-full)}._bar_1th8y_209[data-tone=rest]{background:var(--color-surface-muted)}._bar_1th8y_209[data-tone=threshold]{background:var(--color-warning-subtle)}._bar_1th8y_209[data-tone=unknown]{background:var(--color-surface-muted)}._segmentGranted_1th8y_230,._segmentPurchased_1th8y_231{height:100%;transition:width var(--dur-fast) var(--ease-out)}._segmentGranted_1th8y_230{background:var(--color-grant)}._segmentPurchased_1th8y_231{background:var(--color-purchased)}._details_1th8y_251{display:none}._chip_1th8y_37:hover ._details_1th8y_251,._chip_1th8y_37:focus-within ._details_1th8y_251{display:block}._split_1th8y_260{display:flex;flex-direction:column;gap:var(--spacing-1);margin:var(--spacing-2_5) 0 0}._splitRow_1th8y_267{display:flex;align-items:center;justify-content:space-between}._splitLabel_1th8y_273{display:flex;align-items:center;gap:var(--spacing-1_5);color:var(--color-foreground);font-size:var(--text-xs)}._splitAmount_1th8y_281{margin:0;color:var(--color-foreground);font-family:var(--font-mono);font-size:var(--text-xs);font-variant-numeric:tabular-nums}._dotGranted_1th8y_289,._dotPurchased_1th8y_290{width:var(--spacing-1_5);height:var(--spacing-1_5);border-radius:var(--radius-full)}._dotGranted_1th8y_289{background:var(--color-grant)}._dotPurchased_1th8y_290{background:var(--color-purchased)}._footer_1th8y_304{margin:var(--spacing-3) 0 0;padding-top:var(--spacing-1);border-top:1px solid var(--color-border);color:var(--color-foreground-muted);font-size:var(--text-2xs)}._burnNote_1th8y_312{margin:var(--spacing-2_5) 0 0;color:var(--color-foreground-secondary);font-size:var(--text-2xs);line-height:var(--leading-snug)}._topUp_1th8y_87{position:absolute;top:calc(var(--spacing-3) + var(--chip-lift));right:calc(var(--spacing-3) + var(--chip-lift));padding:0;border:none;background:transparent;color:var(--color-primary);font-family:var(--font-sans);font-size:var(--text-xs);font-weight:var(--font-weight-semibold);opacity:0;cursor:pointer;transition:opacity var(--dur-fast) var(--ease-out)}._chip_1th8y_37:hover ._topUp_1th8y_87,._chip_1th8y_37:focus-within ._topUp_1th8y_87,._topUp_1th8y_87:focus-visible{opacity:1}._topUp_1th8y_87:focus-visible{outline:2px solid var(--color-primary);outline-offset:2px}._topUp_1th8y_87[data-grown=true]{position:static;width:100%;margin-top:var(--spacing-2_5);padding:var(--spacing-2);border-radius:var(--radius-lg);background:var(--color-primary);color:var(--color-primary-foreground);font-size:var(--text-sm);opacity:1}._topUp_1th8y_87[data-grown=true]:hover{background:var(--color-primary-hover)}._rail_1th8y_378{--chip-lift: 0px;position:relative;display:flex;box-sizing:border-box;width:100%;flex-direction:column;align-items:center;gap:var(--spacing-2);padding:var(--spacing-2);font-family:var(--font-sans)}._railIcon_1th8y_392{width:var(--spacing-6);height:var(--spacing-6);color:var(--color-foreground-secondary)}._railFigure_1th8y_398{color:var(--color-foreground);font-family:var(--font-mono);font-size:var(--text-sm);font-weight:var(--font-weight-semibold);font-variant-numeric:tabular-nums}._rail_1th8y_378 ._bar_1th8y_209{height:var(--spacing-0_5);margin-top:0}._srOnly_1th8y_411{position:absolute;overflow:hidden;clip-path:inset(50%);width:1px;height:1px;white-space:nowrap}@media(prefers-reduced-motion:reduce){._segmentGranted_1th8y_230,._segmentPurchased_1th8y_231,._topUp_1th8y_87{transition:none}}._bar_194bx_7{display:flex;align-items:center;justify-content:space-between;gap:var(--spacing-4);box-sizing:border-box;height:var(--spacing-12);padding:0 var(--spacing-8);border-bottom:1px solid var(--color-border);background:var(--color-surface);font-family:var(--font-sans)}._breadcrumb_194bx_20{display:flex;min-width:0;align-items:center;gap:var(--spacing-2);overflow:hidden;color:var(--color-foreground);font-size:var(--text-sm);font-weight:var(--font-weight-medium);text-overflow:ellipsis;white-space:nowrap}._actions_194bx_33{display:flex;flex:0 0 auto;align-items:center;gap:var(--spacing-2)}@supports (animation-timeline: --a) and (timeline-scope: --a){@container style(--boost33-shell-timeline: on){._bar_194bx_7{position:relative;z-index:1;animation:_scrolled_194bx_1 linear both;animation-timeline:--boost33-shell-scroll;animation-range:0 var(--spacing-10)}}}@keyframes _scrolled_194bx_1{to{box-shadow:var(--shadow-scrolled)}}._header_i17td_42{display:flex;align-items:center;justify-content:space-between;gap:var(--spacing-8);padding:var(--spacing-4_5) var(--spacing-8) var(--spacing-4);background:var(--color-surface);font-family:var(--font-sans)}._headings_i17td_52{display:flex;min-width:0;flex-direction:column;gap:var(--spacing-1)}._title_i17td_33{margin:0;color:var(--color-foreground);font-family:var(--font-display);font-size:var(--text-2xl);font-weight:var(--font-weight-semibold);letter-spacing:var(--tracking-tight);line-height:var(--leading-tight)}._subtitle_i17td_74{margin:0;color:var(--color-foreground-secondary);font-size:var(--text-base);font-weight:var(--font-weight-regular);line-height:var(--leading-snug)}._actions_i17td_82{display:flex;flex:0 0 auto;align-items:center;gap:var(--spacing-2)}._shell_1iynb_10{display:flex;height:100%;background:var(--color-surface);font-family:var(--font-sans)}._wordmark_1iynb_20{padding:var(--spacing-3) var(--spacing-2_5) var(--spacing-5)}._switcher_1iynb_26{padding-bottom:var(--spacing-4)}._foot_1iynb_33{display:flex;flex-direction:column;gap:var(--spacing-4);margin-block-start:auto;padding-block-start:var(--spacing-4)}._nav_1iynb_45{display:flex;flex-direction:column;gap:var(--spacing-4)}._main_1iynb_54{display:flex;min-width:0;flex:1;flex-direction:column}._content_1iynb_65{flex:1;min-height:0;overflow-y:auto;background:var(--color-surface)}@supports (animation-timeline: --a) and (timeline-scope: --a){._main_1iynb_54{timeline-scope:--boost33-shell-scroll;--boost33-shell-timeline: on}._content_1iynb_65{scroll-timeline:--boost33-shell-scroll block}}:root{--color-neutral-0: #FFFFFF;--color-neutral-50: #F7F7F8;--color-neutral-100: #EFEFF1;--color-neutral-200: #E5E5EA;--color-neutral-300: #D9D9D9;--color-neutral-400: #C1C1C2;--color-neutral-500: #8E8E93;--color-neutral-600: #6B6B6B;--color-neutral-700: #525252;--color-neutral-800: #3A3A3C;--color-neutral-900: #292929;--color-neutral-950: #1D1D1B;--color-neutral-1000: #000000;--color-orange-50: #FDF3EE;--color-orange-100: #FBE2D5;--color-orange-200: #F7C3A8;--color-orange-300: #F0A173;--color-orange-400: #EB7147;--color-orange-500: #E9521E;--color-orange-600: #C2410C;--color-orange-700: #9A3412;--color-sand-100: #FBF3E4;--color-sand-300: #F3D6AC;--color-sand-600: #8A6510;--color-green-100: #DDF0E4;--color-green-600: #157F3D;--color-crimson-100: #F7DEE1;--color-crimson-600: #A3182B;--color-blue-100: #DEE6FA;--color-blue-600: #2A5BD7;--color-background: var(--color-neutral-50);--color-surface: var(--color-neutral-0);--color-surface-muted: var(--color-neutral-100);--color-border: var(--color-neutral-200);--color-border-strong: var(--color-neutral-300);--color-foreground: var(--color-neutral-950);--color-foreground-secondary: var(--color-neutral-700);--color-foreground-muted: var(--color-neutral-600);--color-foreground-disabled: var(--color-neutral-400);--color-primary: var(--color-orange-500);--color-primary-hover: var(--color-orange-600);--color-primary-foreground: var(--color-neutral-0);--color-primary-subtle: var(--color-orange-50);--color-primary-border: var(--color-orange-200);--color-success: var(--color-green-600);--color-success-subtle: var(--color-green-100);--color-warning: var(--color-sand-600);--color-warning-subtle: var(--color-sand-300);--color-danger: var(--color-crimson-600);--color-danger-subtle: var(--color-crimson-100);--color-info: var(--color-blue-600);--color-info-subtle: var(--color-blue-100);--color-grant: var(--color-sand-600);--color-purchased: var(--color-neutral-950);--shadow-overlay-color: rgb(29 29 27 / 20%);--font-display: var(--boost33-font-display, Outfit), ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;--font-sans: var(--boost33-font-sans, Inter), ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;--font-mono: var(--boost33-font-mono, "Geist Mono"), ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;--text-3xs: 9px;--text-2xs: 11px;--text-xs: 12px;--text-sm: 13px;--text-base: 15px;--text-md: 16px;--text-lg: 18px;--text-xl: 20px;--text-2xl: 24px;--text-3xl: 30px;--text-4xl: 36px;--text-5xl: 46px;--text-6xl: 60px;--text-7xl: 78px;--text-nav-label: 14px;--font-weight-light: 300;--font-weight-regular: 400;--font-weight-medium: 500;--font-weight-semibold: 600;--font-weight-bold: 700;--tracking-tighter: -.03em;--tracking-tight: -.02em;--tracking-normal: 0em;--tracking-wide: .04em;--tracking-wider: .09em;--tracking-caps: .05em;--leading-none: 100%;--leading-tight: 120%;--leading-snug: 135%;--leading-normal: 150%;--leading-relaxed: 170%;--breakpoint-mobile: 390px;--breakpoint-desktop: 1440px;--container-sidebar: 264px;--container-sidebar-rail: 56px;--container-content: 1440px;--spacing-1: 4px;--spacing-2: 8px;--spacing-3: 12px;--spacing-4: 16px;--spacing-4_5: 18px;--spacing-5: 20px;--spacing-6: 24px;--spacing-7: 28px;--spacing-8: 32px;--spacing-10: 40px;--spacing-12: 48px;--spacing-16: 64px;--spacing-20: 80px;--spacing-2_5: 10px;--spacing-5_5: 22px;--spacing-1_5: 6px;--spacing-1_25: 5px;--spacing-0_5: 2px;--spacing-2_25: 9px;--spacing-topup-reserve: 68px;--radius-none: 0px;--radius-xs: 2px;--radius-sm: 3px;--radius-md: 4px;--radius-lg: 6px;--radius-xl: 8px;--radius-2xl: 12px;--radius-full: 9999px;--radius-product-tile: 25%}:root{--dur-instant: 80ms;--dur-fast: .15s;--dur-base: .22s;--dur-slow: .32s;--dur-bold: .7s;--dur-hero: .9s;--dur-spin: .7s;--dur-pulse: 1.6s;--ease-out: cubic-bezier(.22, 1, .36, 1);--ease-in: cubic-bezier(.4, 0, 1, 1);--ease-spring: cubic-bezier(.34, 1.56, .64, 1);--lift-sm: -1px;--lift-md: -2px;--rise: 8px;--stagger: 40ms;--duration-fast: var(--dur-fast);--easing-standard: var(--ease-out)}:root{--shadow-overlay: 0px 16px 40px -10px var(--shadow-overlay-color);--shadow-lift: 0px 10px 24px -14px color-mix(in srgb, var(--color-neutral-950) 28%, transparent);--shadow-scrolled: 0px 6px 16px -12px color-mix(in srgb, var(--color-neutral-950) 35%, transparent)}._root_24c25_8{position:relative;font-family:var(--font-sans)}._trigger_24c25_17{display:flex;box-sizing:border-box;width:100%;align-items:center;gap:var(--spacing-2);padding:var(--spacing-2_25) var(--spacing-2);border:1px solid var(--color-border-strong);border-radius:var(--radius-lg);background:transparent;font-family:var(--font-sans);cursor:pointer}._trigger_24c25_17[aria-expanded=true]{background:var(--color-surface-muted)}._trigger_24c25_17:focus-visible,._railTrigger_24c25_46:focus-visible{outline:2px solid var(--color-primary);outline-offset:2px}._triggerName_24c25_56{overflow:hidden;flex:1;color:var(--color-foreground);font-size:var(--text-nav-label);font-weight:var(--font-weight-semibold);text-align:left;text-overflow:ellipsis;white-space:nowrap}._chevron_24c25_67{width:var(--spacing-3);height:var(--spacing-3);flex-shrink:0;color:var(--color-foreground-muted);transition:transform var(--dur-fast) var(--ease-out)}._trigger_24c25_17[aria-expanded=true] ._chevron_24c25_67{transform:rotate(180deg)}._railTrigger_24c25_46{display:flex;box-sizing:border-box;width:100%;min-height:var(--spacing-6);align-items:flex-start;justify-content:center;padding:0;border:none;background:transparent;cursor:pointer}._panel_24c25_109{position:absolute;z-index:1;top:100%;left:0;width:var(--container-sidebar);box-sizing:border-box;margin-top:var(--spacing-0_5);padding:var(--spacing-2);border:1px solid var(--color-border);border-radius:var(--radius-2xl);background:var(--color-surface);box-shadow:var(--shadow-overlay)}._hubRow_24c25_135,._productRow_24c25_136,._accountRow_24c25_137{display:flex;box-sizing:border-box;width:100%;align-items:center;gap:var(--spacing-2_5);padding:var(--spacing-2);border:none;border-radius:var(--radius-lg);background:transparent;color:var(--color-foreground);font-family:var(--font-sans);text-align:left;text-decoration:none;cursor:pointer}._hubRow_24c25_135:hover,._productRow_24c25_136:hover,._accountRow_24c25_137:hover{background:var(--color-surface-muted)}._hubRow_24c25_135:focus-visible,._productRow_24c25_136:focus-visible,._accountRow_24c25_137:focus-visible{outline:2px solid var(--color-primary);outline-offset:-2px}._products_24c25_144{display:flex;flex-direction:column;gap:var(--spacing-0_5);margin:0;padding:0;list-style:none}._productRow_24c25_136[data-current=true]{background:var(--color-surface-muted)}._productRow_24c25_136[data-entitled=false] ._rowName_24c25_193{color:var(--color-foreground-muted)}._accountRow_24c25_137{padding-inline:var(--spacing-3)}._accountRow_24c25_137 ._rowName_24c25_193{font-size:var(--text-sm)}._accountRow_24c25_137 ._rowSub_24c25_213{font-size:var(--text-2xs)}._rowText_24c25_217{display:flex;min-width:0;flex-direction:column}._hubRow_24c25_135 ._rowText_24c25_217{gap:var(--spacing-0_5)}._rowName_24c25_193{overflow:hidden;color:var(--color-foreground);font-size:var(--text-nav-label);font-weight:var(--font-weight-regular);text-overflow:ellipsis;white-space:nowrap}._productRow_24c25_136 ._rowName_24c25_193{flex:1}._rowSub_24c25_213{overflow:hidden;color:var(--color-foreground-muted);font-size:var(--text-xs);text-overflow:ellipsis;white-space:nowrap}._hubMark_24c25_260{width:var(--spacing-5);height:var(--spacing-5);flex-shrink:0;color:var(--color-foreground)}._tag_24c25_276{flex-shrink:0;margin-left:auto;padding:var(--spacing-0_5) var(--spacing-1_5);border-radius:var(--radius-full);background:var(--color-surface-muted);color:var(--color-foreground-muted);font-size:var(--text-2xs);font-weight:var(--font-weight-medium)}._check_24c25_287{width:var(--spacing-4);height:var(--spacing-4);flex-shrink:0;margin-left:auto;color:var(--color-foreground)}._avatar_24c25_300{display:grid;width:var(--spacing-6);height:var(--spacing-6);flex-shrink:0;border-radius:var(--radius-lg);background:var(--color-surface-muted);color:var(--color-foreground-secondary);font-size:var(--text-3xs);font-weight:var(--font-weight-semibold);place-items:center}._chevronRight_24c25_313{width:var(--spacing-4);height:var(--spacing-4);flex-shrink:0;margin-left:auto;color:var(--color-foreground-muted)}._divider_24c25_334{margin:var(--spacing-2_25) 0 var(--spacing-2);border:none;border-top:1px solid var(--color-border)}._products_24c25_144+._divider_24c25_334{margin:var(--spacing-1_5) 0 var(--spacing-1)}@media(prefers-reduced-motion:reduce){._chevron_24c25_67{transition:none}}
1
+ .btn-primary,.btn-quiet,.btn-danger{cursor:pointer;transition:background-color var(--dur-fast) var(--ease-out),border-color var(--dur-fast) var(--ease-out),transform var(--dur-instant) var(--ease-out)}.btn-primary{border:none;background:var(--color-primary);color:var(--color-primary-foreground)}.btn-primary:hover:not(:disabled){background:var(--color-primary-hover)}.btn-quiet{background:transparent;color:var(--color-foreground-secondary)}.btn-quiet:hover:not(:disabled){background:var(--color-surface-muted)}.btn-danger{border:1px solid var(--color-danger);background:var(--color-danger);color:var(--color-neutral-0)}.btn-danger:hover:not(:disabled){border-color:color-mix(in srgb,var(--color-danger) 85%,var(--color-neutral-950));background:color-mix(in srgb,var(--color-danger) 85%,var(--color-neutral-950))}.btn-primary:disabled,.btn-quiet:disabled,.btn-danger:disabled{opacity:.55;cursor:not-allowed}.spinner{box-sizing:border-box;width:var(--spacing-4_5);height:var(--spacing-4_5);border:2px solid var(--color-neutral-0);border-right-color:transparent;border-radius:var(--radius-full);animation:boost33-spin var(--dur-spin) linear infinite}.spinner--sm{width:var(--spacing-3_5);height:var(--spacing-3_5)}.spinner--primary{border-color:var(--color-primary);border-right-color:transparent}@keyframes boost33-spin{to{transform:rotate(360deg)}}.sk{display:block;border-radius:var(--radius-md);background:var(--color-surface-muted);animation:boost33-pulse var(--dur-pulse) ease-in-out infinite}.sk-group{animation:boost33-appear var(--dur-base) var(--ease-out) var(--dur-fast) both}.reveal{animation:boost33-appear var(--dur-base) var(--ease-out) both}@keyframes boost33-pulse{50%{opacity:.55}}@keyframes boost33-appear{0%{opacity:0}}@media(prefers-reduced-motion:no-preference){.btn-primary:active:not(:disabled),.btn-quiet:active:not(:disabled),.btn-danger:active:not(:disabled){transform:scale(.98)}}@media(prefers-reduced-motion:reduce){.sk,.sk-group,.reveal,.spinner{animation:none}*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:0s!important;scroll-behavior:auto!important}}._sidebar_17kel_7{display:flex;flex-direction:column;box-sizing:border-box;width:var(--container-sidebar);height:100%;padding-inline:var(--spacing-3);border-right:1px solid var(--color-border);background:var(--color-background);font-family:var(--font-sans)}._sidebar_17kel_7[data-collapsed=true]{width:var(--container-sidebar-rail)}._sidebar_17kel_7>nav{flex-shrink:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;margin:calc(-1 * var(--spacing-1));padding:var(--spacing-1);scroll-padding:var(--spacing-1)}@media(prefers-reduced-motion:reduce){._sidebar_17kel_7>nav{scroll-behavior:auto}}._section_3sxsg_5{display:flex;flex-direction:column}._header_3sxsg_13{margin:0;padding:var(--spacing-2) var(--spacing-2_5);color:var(--color-foreground-muted);font-family:var(--font-sans);font-size:var(--text-2xs);font-weight:var(--font-weight-semibold);letter-spacing:var(--tracking-caps);text-transform:uppercase}._wordmark_1k88r_4{display:flex;align-items:center;gap:var(--spacing-2_5);font-family:var(--font-display)}._mark_1k88r_17{display:inline-flex;flex:0 0 var(--spacing-6);align-items:center;justify-content:center;width:var(--spacing-6);height:var(--spacing-6)}._mark_1k88r_17 svg{width:100%;height:100%}._name_1k88r_35{color:var(--color-foreground);font-family:var(--font-display);font-size:var(--text-lg);font-weight:var(--font-weight-semibold);letter-spacing:var(--tracking-tight)}._wordmark_1k88r_4[data-collapsed=true] ._name_1k88r_35{position:absolute;overflow:hidden;clip-path:inset(50%);width:1px;height:1px;white-space:nowrap}._wordmark_1k88r_4[data-collapsed=true]{justify-content:center}._tile_5vhrh_12{fill:var(--color-neutral-950)}._ring_5vhrh_16{fill:var(--color-primary)}._mark_5vhrh_24{display:block}._mark_1jt5g_6{display:block}._ground_1jt5g_17{rx:var(--radius-product-tile)}._row_w21mf_4{display:flex;align-items:center;width:100%;gap:var(--spacing-2_5);padding:var(--spacing-2) var(--spacing-2_5);border:none;border-radius:var(--radius-lg);background:transparent;font-family:var(--font-sans);text-align:left;text-decoration:none;cursor:pointer;transition:background-color var(--dur-fast) var(--ease-out)}._row_w21mf_4:hover{background:var(--color-surface-muted)}._row_w21mf_4[data-active=true]{background:var(--color-surface)}._row_w21mf_4:focus-visible{outline:2px solid var(--color-primary);outline-offset:2px}._icon_w21mf_41{display:inline-flex;flex:0 0 var(--spacing-4);align-items:center;justify-content:center;width:var(--spacing-4);height:var(--spacing-4);color:var(--color-foreground-secondary)}._icon_w21mf_41 svg{width:100%;height:100%}._row_w21mf_4[data-active=true] ._icon_w21mf_41{color:var(--color-foreground)}._row_w21mf_4[data-collapsed=true]{justify-content:center}._label_w21mf_66{flex:1 1 auto;min-width:0;overflow:hidden;color:var(--color-foreground-secondary);font-size:var(--text-nav-label);font-weight:var(--font-weight-regular);text-overflow:ellipsis;white-space:nowrap}._row_w21mf_4[data-active=true] ._label_w21mf_66{color:var(--color-foreground);font-weight:var(--font-weight-semibold)}._row_w21mf_4[data-collapsed=true] ._label_w21mf_66{position:absolute;overflow:hidden;clip-path:inset(50%);width:1px;height:1px;white-space:nowrap}._trailingSlot_w21mf_92{display:flex;flex:0 0 var(--spacing-5_5);align-items:center;justify-content:center;width:var(--spacing-5_5);font-family:var(--font-sans);font-size:var(--text-nav-label)}._row_w21mf_4[data-collapsed=true] ._trailingSlot_w21mf_92{display:none}._count_w21mf_106{color:var(--color-foreground-muted)}._countUrgent_w21mf_110{color:var(--color-primary)}@media(prefers-reduced-motion:reduce){._row_w21mf_4{transition:none}}._chip_xxga2_37{--chip-lift: 0px;position:relative;box-sizing:border-box;width:100%;padding:calc(var(--spacing-3) + var(--chip-lift));border:1px solid var(--color-border);border-radius:var(--radius-2xl);background:transparent;font-family:var(--font-sans)}._chip_xxga2_37:not([data-unknown]):hover,._chip_xxga2_37:not([data-unknown]):focus-within,._chip_xxga2_37[data-threshold]{--chip-lift: var(--spacing-0_5);box-shadow:var(--shadow-overlay)}._chip_xxga2_37:not([data-threshold]):not([data-unknown]):hover,._chip_xxga2_37:not([data-threshold]):not([data-unknown]):focus-within{background:var(--color-surface)}._chip_xxga2_37:focus-visible{outline:2px solid var(--color-primary);outline-offset:2px}._header_xxga2_74{display:flex;align-items:center;gap:var(--spacing-2)}._chip_xxga2_37:not([data-threshold]):not([data-unknown]) ._header_xxga2_74{padding-right:var(--spacing-topup-reserve)}._name_xxga2_83{min-width:0;overflow:hidden;color:var(--color-foreground-muted);font-size:var(--text-xs);font-weight:var(--font-weight-medium);line-height:var(--leading-tight);text-overflow:ellipsis;white-space:nowrap}._pill_xxga2_162{padding:var(--spacing-0_5) var(--spacing-2);border-radius:var(--radius-full);background:var(--color-warning-subtle);color:var(--color-warning);font-size:var(--text-2xs);font-weight:var(--font-weight-semibold)}._figure_xxga2_171{display:flex;align-items:baseline;gap:var(--spacing-1_5);margin-top:calc(var(--spacing-2_5) + var(--chip-lift))}._amount_xxga2_186{color:var(--color-foreground);font-family:var(--font-mono);font-size:var(--text-2xl);font-weight:var(--font-weight-bold);letter-spacing:var(--tracking-tight);line-height:var(--leading-none)}._unit_xxga2_195{color:var(--color-foreground-muted);font-size:var(--text-xs)}._retry_xxga2_200{width:var(--spacing-3);height:var(--spacing-3);color:var(--color-foreground-muted)}._bar_xxga2_209{display:flex;overflow:hidden;width:100%;height:calc(var(--spacing-1_25) + var(--chip-lift));margin-top:calc(var(--spacing-2_5) + var(--chip-lift));border-radius:var(--radius-full)}._bar_xxga2_209[data-tone=rest]{background:var(--color-surface-muted)}._bar_xxga2_209[data-tone=threshold]{background:var(--color-warning-subtle)}._bar_xxga2_209[data-tone=unknown]{background:var(--color-surface-muted)}._segmentGranted_xxga2_230,._segmentPurchased_xxga2_231{height:100%;transition:width var(--dur-fast) var(--ease-out)}._segmentGranted_xxga2_230{background:var(--color-grant)}._segmentPurchased_xxga2_231{background:var(--color-purchased)}._details_xxga2_275{display:none}._chip_xxga2_37:hover ._details_xxga2_275,._chip_xxga2_37:focus-within ._details_xxga2_275{display:block}._split_xxga2_284{display:flex;flex-direction:column;gap:var(--spacing-1);margin:var(--spacing-2_5) 0 0}._splitRow_xxga2_291{display:flex;align-items:center;justify-content:space-between}._splitLabel_xxga2_297{display:flex;align-items:center;gap:var(--spacing-1_5);color:var(--color-foreground);font-size:var(--text-xs)}._splitAmount_xxga2_305{margin:0;color:var(--color-foreground);font-family:var(--font-mono);font-size:var(--text-xs);font-variant-numeric:tabular-nums}._dotGranted_xxga2_313,._dotPurchased_xxga2_314{width:var(--spacing-1_5);height:var(--spacing-1_5);border-radius:var(--radius-full)}._dotGranted_xxga2_313{background:var(--color-grant)}._dotPurchased_xxga2_314{background:var(--color-purchased)}._footer_xxga2_328{margin:var(--spacing-3) 0 0;padding-top:var(--spacing-1);border-top:1px solid var(--color-border);color:var(--color-foreground-muted);font-size:var(--text-2xs)}._burnNote_xxga2_336{margin:var(--spacing-2_5) 0 0;color:var(--color-foreground-secondary);font-size:var(--text-2xs);line-height:var(--leading-snug)}._topUp_xxga2_87{position:absolute;top:calc(var(--spacing-3) + var(--chip-lift));right:calc(var(--spacing-3) + var(--chip-lift));padding:0;border:none;background:transparent;color:var(--color-primary);font-family:var(--font-sans);font-size:var(--text-xs);font-weight:var(--font-weight-semibold);opacity:0;cursor:pointer;transition:opacity var(--dur-fast) var(--ease-out)}._chip_xxga2_37:hover ._topUp_xxga2_87,._chip_xxga2_37:focus-within ._topUp_xxga2_87,._topUp_xxga2_87:focus-visible{opacity:1}._topUp_xxga2_87:focus-visible{outline:2px solid var(--color-primary);outline-offset:2px}._topUp_xxga2_87[data-grown=true]{position:static;width:100%;margin-top:var(--spacing-2_5);padding:var(--spacing-2);border-radius:var(--radius-lg);background:var(--color-primary);color:var(--color-primary-foreground);font-size:var(--text-sm);opacity:1}._topUp_xxga2_87[data-grown=true]:hover{background:var(--color-primary-hover)}._rail_xxga2_402{--chip-lift: 0px;position:relative;display:flex;box-sizing:border-box;width:100%;flex-direction:column;align-items:center;gap:var(--spacing-2);padding:var(--spacing-2);font-family:var(--font-sans)}._railIcon_xxga2_416{width:var(--spacing-6);height:var(--spacing-6);color:var(--color-foreground-secondary)}._railFigure_xxga2_422{color:var(--color-foreground);font-family:var(--font-mono);font-size:var(--text-sm);font-weight:var(--font-weight-semibold);font-variant-numeric:tabular-nums}._rail_xxga2_402 ._bar_xxga2_209{height:var(--spacing-0_5);margin-top:0}._srOnly_xxga2_435{position:absolute;overflow:hidden;clip-path:inset(50%);width:1px;height:1px;white-space:nowrap}@media(prefers-reduced-motion:reduce){._segmentGranted_xxga2_230,._segmentPurchased_xxga2_231,._topUp_xxga2_87{transition:none}}._bar_194bx_7{display:flex;align-items:center;justify-content:space-between;gap:var(--spacing-4);box-sizing:border-box;height:var(--spacing-12);padding:0 var(--spacing-8);border-bottom:1px solid var(--color-border);background:var(--color-surface);font-family:var(--font-sans)}._breadcrumb_194bx_20{display:flex;min-width:0;align-items:center;gap:var(--spacing-2);overflow:hidden;color:var(--color-foreground);font-size:var(--text-sm);font-weight:var(--font-weight-medium);text-overflow:ellipsis;white-space:nowrap}._actions_194bx_33{display:flex;flex:0 0 auto;align-items:center;gap:var(--spacing-2)}@supports (animation-timeline: --a) and (timeline-scope: --a){@container style(--boost33-shell-timeline: on){._bar_194bx_7{position:relative;z-index:1;animation:_scrolled_194bx_1 linear both;animation-timeline:--boost33-shell-scroll;animation-range:0 var(--spacing-10)}}}@keyframes _scrolled_194bx_1{to{box-shadow:var(--shadow-scrolled)}}._header_i17td_42{display:flex;align-items:center;justify-content:space-between;gap:var(--spacing-8);padding:var(--spacing-4_5) var(--spacing-8) var(--spacing-4);background:var(--color-surface);font-family:var(--font-sans)}._headings_i17td_52{display:flex;min-width:0;flex-direction:column;gap:var(--spacing-1)}._title_i17td_33{margin:0;color:var(--color-foreground);font-family:var(--font-display);font-size:var(--text-2xl);font-weight:var(--font-weight-semibold);letter-spacing:var(--tracking-tight);line-height:var(--leading-tight)}._subtitle_i17td_74{margin:0;color:var(--color-foreground-secondary);font-size:var(--text-base);font-weight:var(--font-weight-regular);line-height:var(--leading-snug)}._actions_i17td_82{display:flex;flex:0 0 auto;align-items:center;gap:var(--spacing-2)}._shell_10chf_10{display:flex;height:100%;background:var(--color-surface);font-family:var(--font-sans)}._wordmark_10chf_20{padding:var(--spacing-3) var(--spacing-2_5) var(--spacing-5)}._switcher_10chf_26{padding-bottom:var(--spacing-4)}._foot_10chf_33{display:flex;flex-direction:column;gap:var(--spacing-4);margin-block-start:auto;padding-block-start:var(--spacing-4)}._nav_10chf_45{display:flex;flex-direction:column;gap:var(--spacing-4)}._main_10chf_54{display:flex;min-width:0;flex:1;flex-direction:column}._content_10chf_75{flex:1;min-height:0;overflow-y:auto;background:var(--color-surface)}@supports (animation-timeline: --a) and (timeline-scope: --a){._main_10chf_54{timeline-scope:--boost33-shell-scroll;--boost33-shell-timeline: on}._content_10chf_75{scroll-timeline:--boost33-shell-scroll block}}:root{--shadow-overlay: 0px 16px 40px -10px var(--shadow-overlay-color);--shadow-lift: 0px 10px 24px -14px color-mix(in srgb, var(--color-neutral-950) 28%, transparent);--shadow-scrolled: 0px 6px 16px -12px color-mix(in srgb, var(--color-neutral-950) 35%, transparent)}._root_15f7d_8{position:relative;font-family:var(--font-sans)}._trigger_15f7d_17{display:flex;box-sizing:border-box;width:100%;align-items:center;gap:var(--spacing-2);padding:var(--spacing-2_25) var(--spacing-2);border:1px solid var(--color-border-strong);border-radius:var(--radius-lg);background:transparent;font-family:var(--font-sans);cursor:pointer}._trigger_15f7d_17[aria-expanded=true]{background:var(--color-surface-muted)}._trigger_15f7d_17:focus-visible,._railTrigger_15f7d_46:focus-visible{outline:2px solid var(--color-primary);outline-offset:2px}._triggerName_15f7d_56{overflow:hidden;flex:1;color:var(--color-foreground);font-size:var(--text-nav-label);font-weight:var(--font-weight-semibold);text-align:left;text-overflow:ellipsis;white-space:nowrap}._chevron_15f7d_67{width:var(--spacing-3);height:var(--spacing-3);flex-shrink:0;color:var(--color-foreground-muted);transition:transform var(--dur-fast) var(--ease-out)}._trigger_15f7d_17[aria-expanded=true] ._chevron_15f7d_67{transform:rotate(180deg)}._railTrigger_15f7d_46{display:flex;box-sizing:border-box;width:100%;min-height:var(--spacing-6);align-items:flex-start;justify-content:center;padding:0;border:none;background:transparent;cursor:pointer}._panel_15f7d_109{position:absolute;z-index:1;top:100%;left:0;width:var(--container-sidebar);box-sizing:border-box;margin-top:var(--spacing-0_5);padding:var(--spacing-2);border:1px solid var(--color-border);border-radius:var(--radius-2xl);background:var(--color-surface);box-shadow:var(--shadow-overlay)}._hubRow_15f7d_135,._productRow_15f7d_136,._accountRow_15f7d_137{display:flex;box-sizing:border-box;width:100%;align-items:center;gap:var(--spacing-2_5);padding:var(--spacing-2);border:none;border-radius:var(--radius-lg);background:transparent;color:var(--color-foreground);font-family:var(--font-sans);text-align:left;text-decoration:none;cursor:pointer}._hubRow_15f7d_135:hover,._productRow_15f7d_136:hover,._accountRow_15f7d_137:hover{background:var(--color-surface-muted)}._hubRow_15f7d_135:focus-visible,._productRow_15f7d_136:focus-visible,._accountRow_15f7d_137:focus-visible{outline:2px solid var(--color-primary);outline-offset:-2px}._products_15f7d_144{display:flex;flex-direction:column;gap:var(--spacing-0_5);margin:0;padding:0;list-style:none}._productRow_15f7d_136[data-current=true]{background:var(--color-surface-muted)}._productRow_15f7d_136[data-entitled=false] ._rowName_15f7d_193{color:var(--color-foreground-muted)}._accountRow_15f7d_137{padding-inline:var(--spacing-3)}._accountRow_15f7d_137 ._rowName_15f7d_193{font-size:var(--text-sm)}._accountRow_15f7d_137 ._rowSub_15f7d_213{font-size:var(--text-2xs)}._accountRow_15f7d_137[data-actionable=false]{cursor:default}._accountRow_15f7d_137[data-actionable=false]:hover{background:transparent}._rowText_15f7d_238{display:flex;min-width:0;flex-direction:column}._hubRow_15f7d_135 ._rowText_15f7d_238{gap:var(--spacing-0_5)}._rowName_15f7d_193{overflow:hidden;color:var(--color-foreground);font-size:var(--text-nav-label);font-weight:var(--font-weight-regular);text-overflow:ellipsis;white-space:nowrap}._productRow_15f7d_136 ._rowName_15f7d_193{flex:1}._rowSub_15f7d_213{overflow:hidden;color:var(--color-foreground-muted);font-size:var(--text-xs);text-overflow:ellipsis;white-space:nowrap}._hubMark_15f7d_281{width:var(--spacing-5);height:var(--spacing-5);flex-shrink:0;color:var(--color-foreground)}._tag_15f7d_297{flex-shrink:0;margin-left:auto;padding:var(--spacing-0_5) var(--spacing-1_5);border-radius:var(--radius-full);background:var(--color-surface-muted);color:var(--color-foreground-muted);font-size:var(--text-2xs);font-weight:var(--font-weight-medium)}._check_15f7d_308{width:var(--spacing-4);height:var(--spacing-4);flex-shrink:0;margin-left:auto;color:var(--color-foreground)}._avatar_15f7d_321{display:grid;width:var(--spacing-6);height:var(--spacing-6);flex-shrink:0;border-radius:var(--radius-lg);background:var(--color-surface-muted);color:var(--color-foreground-secondary);font-size:var(--text-3xs);font-weight:var(--font-weight-semibold);place-items:center}._chevronRight_15f7d_334{width:var(--spacing-4);height:var(--spacing-4);flex-shrink:0;margin-left:auto;color:var(--color-foreground-muted)}._divider_15f7d_355{margin:var(--spacing-2_25) 0 var(--spacing-2);border:none;border-top:1px solid var(--color-border)}._products_15f7d_144+._divider_15f7d_355{margin:var(--spacing-1_5) 0 var(--spacing-1)}@media(prefers-reduced-motion:reduce){._chevron_15f7d_67{transition:none}}._root_g1823_11{--dots-size: var(--spacing-8);--dots-speed: var(--dur-dots);--dots-ease: var(--ease-in-out-quint);--dots-gap: calc(var(--dots-size) * .14);--dots-dot: calc((var(--dots-size) - var(--dots-gap) * 2) / 3);display:inline-flex;align-items:center;justify-content:center}._grid_g1823_25{display:grid;gap:var(--dots-gap);grid-template-columns:repeat(3,var(--dots-dot))}._dot_g1823_31{width:var(--dots-dot);height:var(--dots-dot);border-radius:var(--radius-full);background:currentColor;opacity:1;animation:_dots-pulse_g1823_1 var(--dots-speed) var(--dots-ease) infinite backwards;animation-delay:calc(var(--dots-step, 0) * var(--dots-speed) / 4)}._dot_g1823_31:nth-child(1){--dots-step: 0}._dot_g1823_31:nth-child(2),._dot_g1823_31:nth-child(4){--dots-step: 1}._dot_g1823_31:nth-child(3),._dot_g1823_31:nth-child(5),._dot_g1823_31:nth-child(7){--dots-step: 2}._dot_g1823_31:nth-child(6),._dot_g1823_31:nth-child(8){--dots-step: 3}._dot_g1823_31:nth-child(9){--dots-step: 4}@keyframes _dots-pulse_g1823_1{0%,to{opacity:.2;transform:scale(.7)}50%{opacity:1;transform:scale(1)}}@media(prefers-reduced-motion:reduce){._dot_g1823_31{animation:none}}:root{--color-neutral-0: #FFFFFF;--color-neutral-50: #F7F7F8;--color-neutral-100: #EFEFF1;--color-neutral-200: #E5E5EA;--color-neutral-300: #D9D9D9;--color-neutral-400: #C1C1C2;--color-neutral-500: #8E8E93;--color-neutral-600: #6B6B6B;--color-neutral-700: #525252;--color-neutral-800: #3A3A3C;--color-neutral-900: #292929;--color-neutral-950: #1D1D1B;--color-neutral-1000: #000000;--color-orange-50: #FDF3EE;--color-orange-100: #FBE2D5;--color-orange-200: #F7C3A8;--color-orange-300: #F0A173;--color-orange-400: #EB7147;--color-orange-500: #E9521E;--color-orange-600: #C2410C;--color-orange-700: #9A3412;--color-sand-100: #FBF3E4;--color-sand-300: #F3D6AC;--color-sand-600: #8A6510;--color-green-100: #DDF0E4;--color-green-600: #157F3D;--color-crimson-100: #F7DEE1;--color-crimson-600: #A3182B;--color-blue-100: #DEE6FA;--color-blue-600: #2A5BD7;--color-background: var(--color-neutral-50);--color-surface: var(--color-neutral-0);--color-surface-muted: var(--color-neutral-100);--color-border: var(--color-neutral-200);--color-border-strong: var(--color-neutral-300);--color-foreground: var(--color-neutral-950);--color-foreground-secondary: var(--color-neutral-700);--color-foreground-muted: var(--color-neutral-600);--color-foreground-disabled: var(--color-neutral-400);--color-primary: var(--color-orange-500);--color-primary-hover: var(--color-orange-600);--color-primary-foreground: var(--color-neutral-0);--color-primary-subtle: var(--color-orange-50);--color-primary-border: var(--color-orange-200);--color-success: var(--color-green-600);--color-success-subtle: var(--color-green-100);--color-warning: var(--color-sand-600);--color-warning-subtle: var(--color-sand-300);--color-danger: var(--color-crimson-600);--color-danger-subtle: var(--color-crimson-100);--color-info: var(--color-blue-600);--color-info-subtle: var(--color-blue-100);--color-grant: var(--color-sand-600);--color-purchased: var(--color-neutral-950);--shadow-overlay-color: rgb(29 29 27 / 20%);--font-display: var(--boost33-font-display, Outfit), ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;--font-sans: var(--boost33-font-sans, Inter), ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;--font-mono: var(--boost33-font-mono, "Geist Mono"), ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;--text-3xs: 9px;--text-2xs: 11px;--text-xs: 12px;--text-sm: 13px;--text-base: 15px;--text-md: 16px;--text-lg: 18px;--text-xl: 20px;--text-2xl: 24px;--text-3xl: 30px;--text-4xl: 36px;--text-5xl: 46px;--text-6xl: 60px;--text-7xl: 78px;--text-nav-label: 14px;--font-weight-light: 300;--font-weight-regular: 400;--font-weight-medium: 500;--font-weight-semibold: 600;--font-weight-bold: 700;--tracking-tighter: -.03em;--tracking-tight: -.02em;--tracking-normal: 0em;--tracking-wide: .04em;--tracking-wider: .09em;--tracking-caps: .05em;--leading-none: 100%;--leading-tight: 120%;--leading-snug: 135%;--leading-normal: 150%;--leading-relaxed: 170%;--breakpoint-mobile: 390px;--breakpoint-desktop: 1440px;--container-sidebar: 264px;--container-sidebar-rail: 56px;--container-command-palette: 560px;--container-content: 1440px;--container-account-menu: 312px;--container-notification-panel: 360px;--spacing-1: 4px;--spacing-2: 8px;--spacing-3: 12px;--spacing-4: 16px;--spacing-4_5: 18px;--spacing-5: 20px;--spacing-6: 24px;--spacing-7: 28px;--spacing-8: 32px;--spacing-10: 40px;--spacing-11: 44px;--spacing-12: 48px;--spacing-16: 64px;--spacing-20: 80px;--spacing-2_5: 10px;--spacing-5_5: 22px;--spacing-1_5: 6px;--spacing-1_25: 5px;--spacing-0_5: 2px;--spacing-2_25: 9px;--spacing-3_5: 14px;--spacing-0_25: 1px;--spacing-topup-reserve: 68px;--spacing-wallet-segment-min: 3px;--spacing-9_5: 38px;--radius-none: 0px;--radius-xs: 2px;--radius-sm: 3px;--radius-md: 4px;--radius-lg: 6px;--radius-xl: 8px;--radius-2xl: 12px;--radius-full: 9999px;--radius-product-tile: 25%}:root{--dur-instant: 80ms;--dur-fast: .15s;--dur-base: .22s;--dur-slow: .32s;--dur-bold: .7s;--dur-hero: .9s;--dur-spin: .7s;--dur-pulse: 1.6s;--ease-out: cubic-bezier(.22, 1, .36, 1);--ease-in: cubic-bezier(.4, 0, 1, 1);--ease-spring: cubic-bezier(.34, 1.56, .64, 1);--lift-sm: -1px;--lift-md: -2px;--rise: 8px;--stagger: 40ms;--duration-fast: var(--dur-fast);--easing-standard: var(--ease-out);--dur-dots: 1s;--ease-in-out-quint: cubic-bezier(.83, 0, .17, 1);--dur-drift: 5s}._root_1xhf4_6{position:fixed;top:0;right:0;bottom:0;left:0;display:grid;place-items:center;padding:var(--spacing-4);overflow:hidden;background-color:var(--launch-ground);color:var(--color-neutral-0);--launch-accent: var(--launch-accent-input, var(--color-neutral-0));isolation:isolate}._mesh_1xhf4_8{position:absolute;top:-12%;right:-12%;bottom:-12%;left:-12%;z-index:0;overflow:hidden;background-color:var(--launch-ground);filter:blur(5.5vmax);animation:_launch-drift_1xhf4_1 var(--dur-drift) ease-in-out 1 both}._shape_1xhf4_81{position:absolute}@keyframes _launch-drift_1xhf4_1{0%,to{transform:none}50%{transform:translate(-1.5%,1%) scale(1.03) rotate(-1deg)}}._backdrop_1xhf4_90{position:absolute;top:0;right:0;bottom:0;left:0;z-index:0;overflow:hidden}._content_1xhf4_13{position:relative;display:flex;flex-direction:column;align-items:center;gap:var(--spacing-5);inline-size:min(280px,100%)}._dots_1xhf4_127{--dots-size: var(--spacing-6);color:var(--launch-accent);mix-blend-mode:soft-light;opacity:1}._bar_1xhf4_160{position:relative;inline-size:100%;block-size:var(--spacing-1);overflow:hidden;border-radius:var(--radius-full)}._track_1xhf4_168{position:absolute;top:0;right:0;bottom:0;left:0;background:color-mix(in srgb,var(--launch-accent) 35%,transparent);mix-blend-mode:soft-light;opacity:.75}._fill_1xhf4_176{position:absolute;inset-block:0;inset-inline-start:0;inline-size:calc(var(--launch-progress, 0) * 1%);border-radius:inherit;background:var(--color-neutral-0);transition:inline-size var(--dur-fast) var(--ease-out)}._bar_1xhf4_160[data-indeterminate=true] ._fill_1xhf4_176{inline-size:33.333%;animation:_launch-indeterminate_1xhf4_1 calc(var(--dur-dots) * 1.5) var(--ease-out) infinite}@keyframes _launch-indeterminate_1xhf4_1{0%{inset-inline-start:-33.333%}to{inset-inline-start:100%}}._caption_1xhf4_202{margin:0;opacity:.8;font-family:var(--font-sans);font-size:var(--text-sm);font-weight:var(--font-weight-medium);line-height:var(--leading-normal);text-align:center}@media(prefers-reduced-motion:reduce){._mesh_1xhf4_8{animation:none}._fill_1xhf4_176{transition:none}._bar_1xhf4_160[data-indeterminate=true] ._fill_1xhf4_176{inset-inline-start:33.333%;animation:none}}@media(prefers-contrast){._root_1xhf4_6{background-color:var(--launch-solid);--launch-accent: var(--color-neutral-0)}._mesh_1xhf4_8,._backdrop_1xhf4_90{display:none}._dots_1xhf4_127,._track_1xhf4_168{mix-blend-mode:normal;opacity:1}}