@godxjp/ui 28.7.0 → 28.8.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 (85) hide show
  1. package/dist/components/data-display/index.d.ts +2 -0
  2. package/dist/components/data-display/index.js +2 -0
  3. package/dist/components/data-display/marquee.d.ts +16 -0
  4. package/dist/components/data-display/marquee.js +155 -0
  5. package/dist/components/general/reveal.d.ts +23 -2
  6. package/dist/components/general/reveal.js +37 -7
  7. package/dist/components/general/typography.d.ts +4 -1
  8. package/dist/components/general/typography.js +14 -1
  9. package/dist/components/layout/affix.d.ts +86 -0
  10. package/dist/components/layout/affix.js +187 -0
  11. package/dist/components/layout/index.d.ts +4 -0
  12. package/dist/components/layout/index.js +4 -0
  13. package/dist/components/layout/legal-document-shell.js +4 -3
  14. package/dist/components/layout/masonry.d.ts +74 -0
  15. package/dist/components/layout/masonry.js +214 -0
  16. package/dist/components/layout/page-container.js +5 -20
  17. package/dist/components/navigation/anchor.d.ts +64 -0
  18. package/dist/components/navigation/anchor.js +284 -0
  19. package/dist/components/navigation/index.d.ts +4 -0
  20. package/dist/components/navigation/index.js +4 -0
  21. package/dist/components/navigation/mega-menu.d.ts +21 -0
  22. package/dist/components/navigation/mega-menu.js +526 -0
  23. package/dist/contracts/measurement.json +1 -1
  24. package/dist/i18n/messages/en.json +517 -0
  25. package/dist/i18n/messages/ja.json +513 -0
  26. package/dist/i18n/messages/vi.json +513 -0
  27. package/dist/lib/hooks.d.ts +68 -0
  28. package/dist/lib/hooks.js +52 -0
  29. package/dist/lib/platform.d.ts +14 -0
  30. package/dist/lib/platform.js +10 -1
  31. package/dist/lib/utils.d.ts +1 -1
  32. package/dist/lib/utils.js +3 -2
  33. package/dist/props/components/data-display.prop.d.ts +95 -1
  34. package/dist/props/components/general.prop.d.ts +47 -3
  35. package/dist/props/components/layout.prop.d.ts +194 -0
  36. package/dist/props/components/navigation.prop.d.ts +263 -0
  37. package/dist/props/registry.d.ts +359 -4
  38. package/dist/props/registry.js +472 -3
  39. package/dist/props/vocabulary/index.d.ts +1 -1
  40. package/dist/props/vocabulary/interaction.prop.d.ts +39 -2
  41. package/dist/styles/control.css +5 -6
  42. package/dist/styles/data-display-layout.css +2 -1
  43. package/dist/styles/density.css +4 -0
  44. package/dist/styles/layout.css +79 -0
  45. package/dist/styles/motion.css +121 -1
  46. package/dist/styles/navigation-layout.css +397 -1
  47. package/dist/styles/shell-layout.css +3 -0
  48. package/dist/styles/text-layout.css +52 -4
  49. package/dist/tokens/base.css +5 -0
  50. package/dist/tokens/components/affix.css +7 -0
  51. package/dist/tokens/components/anchor.css +17 -0
  52. package/dist/tokens/components/control.css +3 -3
  53. package/dist/tokens/components/form.css +1 -1
  54. package/dist/tokens/components/marquee.css +7 -0
  55. package/dist/tokens/components/masonry.css +6 -0
  56. package/dist/tokens/components/mega-menu.css +62 -0
  57. package/dist/tokens/components/shell.css +3 -0
  58. package/dist/tokens/foundation.css +11 -0
  59. package/dist/tokens/semantic/layout.css +7 -0
  60. package/docs/COMPOSITION-VS-COMPONENT.md +19 -1
  61. package/docs/DESIGN-AUTHORITY.md +99 -18
  62. package/docs/FRAME-COVERAGE-REPORT.md +7 -2
  63. package/docs/data-display/marquee.tsx +254 -0
  64. package/docs/foundation/_theme-editor-scope.ts +222 -0
  65. package/docs/foundation/density.tsx +12 -2
  66. package/docs/foundation/spacing.tsx +5 -0
  67. package/docs/foundation/theme-editor.tsx +645 -0
  68. package/docs/general/activity.tsx +65 -0
  69. package/docs/general/reveal.tsx +290 -22
  70. package/docs/general/typography.tsx +91 -1
  71. package/docs/layout/affix.tsx +209 -0
  72. package/docs/layout/masonry.tsx +291 -0
  73. package/docs/navigation/anchor.tsx +285 -0
  74. package/docs/navigation/mega-menu-panel.tsx +86 -0
  75. package/docs/navigation/mega-menu.tsx +254 -0
  76. package/docs/roadmap/website-components.md +779 -0
  77. package/docs/showcase/acme-website.tsx +75 -39
  78. package/docs/showcase/futurelastic-web.tsx +91 -49
  79. package/docs/showcase/marketing-page.tsx +885 -0
  80. package/docs/showcase/table-footer-totals.tsx +12 -2
  81. package/docs/showcase/theme-customization.tsx +1259 -0
  82. package/package.json +5 -3
  83. package/scripts/brand-accent.generated.mjs +27 -0
  84. package/scripts/ui-audit.mjs +66 -0
  85. package/scripts/visual-audit-rules.mjs +46 -2
@@ -32,7 +32,7 @@ import {
32
32
  Warehouse,
33
33
  } from "lucide-react";
34
34
 
35
- import { Button, Text } from "@godxjp/ui/general";
35
+ import { Button, Heading, Icon, Text } from "@godxjp/ui/general";
36
36
  import { Avatar, AvatarFallback, Card, CardContent, CardCover } from "@godxjp/ui/data-display";
37
37
  import { Flex, ResponsiveGrid } from "@godxjp/ui/layout";
38
38
 
@@ -52,6 +52,7 @@ const THEME = `
52
52
  3.18 on --muted. It was 216 16% 80% = 1.46:1. --border is decorative chrome; it stays. */
53
53
  --border: 214 27% 90%; --input: 216 16% 55%;
54
54
  --success: 152 100% 25%; --warning: 41 73% 53%; --destructive: 11 82% 56%; --info: 203 100% 37%;
55
+ --letter-spacing-tight: -0.02em; /* the display tracking .tx-display/.tx-h2 hand-wrote, once */
55
56
  --radius: 0.875rem; --radius-md: 10px; --radius-lg: 14px;
56
57
  --card-radius: var(--radius); --control-radius: var(--radius);
57
58
  --shadow-color: 38 79 145;
@@ -62,33 +63,44 @@ const THEME = `
62
63
  --font-family-body: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
63
64
  --font-family-sans: var(--font-family-body);
64
65
  --text-brand: 41 84% 26%; /* dark gold, eyebrows clear AA on light sections */
66
+ /* The glass bar, opted into once (gh#831). These two used to be a literal 0.85 alpha and a
67
+ literal 10px blur on .tx-navbar. futurelastic-web.tsx hand-wrote the identical pair. */
68
+ --topbar-background-alpha: 85%;
69
+ --topbar-backdrop-blur-size: 10px;
65
70
  }
66
71
  /* Marketing layout + type (a consumer's section stylesheet) */
67
- [data-tenant="acme-web"] .tx-shell { margin-inline: auto; width: 100%; max-width: 1200px; padding-inline: 2rem;
72
+ [data-tenant="acme-web"] .tx-shell { margin-inline: auto; width: 100%; max-inline-size: var(--page-measure-wide);
73
+ padding-inline: var(--space-8);
68
74
  /* ResponsiveGrid uses @container queries, so establish the container here (PageContainer does this
69
75
  * in an app; a marketing composition provides it on its own shell). */
70
76
  container-type: inline-size; }
71
- [data-tenant="acme-web"] .tx-section { padding-block: 5rem; }
72
- [data-tenant="acme-web"] .tx-navbar { position: sticky; top: 0; z-index: 30; background: hsl(var(--background) / 0.85);
73
- backdrop-filter: blur(10px); border-bottom: 1px solid hsl(var(--border)); }
77
+ [data-tenant="acme-web"] .tx-section { padding-block: var(--space-section-band); }
78
+ /* The sticky header IS this page's bar region, so it reads the --topbar-* glass knobs. It is not a
79
+ <Topbar> for one reason: the glass must span the full bleed while the bar's CONTENT stays on the
80
+ .tx-shell measure, and Topbar is a single flex row with no inner shell. */
81
+ [data-tenant="acme-web"] .tx-navbar { position: sticky; inset-block-start: 0; z-index: 30;
82
+ background-color: hsl(var(--background) / var(--topbar-background-alpha));
83
+ backdrop-filter: blur(var(--topbar-backdrop-blur-size));
84
+ border-block-end: var(--stroke-hairline) solid hsl(var(--border)); }
74
85
  [data-tenant="acme-web"] .tx-navbar-inner { height: 72px; display: flex; align-items: center; gap: 2.25rem; }
75
86
  [data-tenant="acme-web"] .tx-brand { font-family: var(--font-family-display); font-weight: 800; font-size: 1.375rem; letter-spacing: -0.01em; color: hsl(var(--foreground)); }
76
87
  [data-tenant="acme-web"] .tx-eyebrow { font-family: var(--font-family-display); font-weight: 700; font-size: 0.8125rem;
77
88
  letter-spacing: 0.12em; text-transform: uppercase; color: hsl(var(--text-brand)); }
78
- [data-tenant="acme-web"] .tx-display { font-family: var(--font-family-display); font-weight: 900; font-size: 3.5rem;
79
- line-height: 1.05; letter-spacing: -0.02em; margin: 1rem 0; color: hsl(var(--foreground)); }
80
- [data-tenant="acme-web"] .tx-h2 { font-family: var(--font-family-display); font-weight: 800; font-size: 2.25rem;
81
- line-height: 1.15; letter-spacing: -0.02em; margin: 0.75rem 0 0.625rem; color: hsl(var(--foreground)); }
82
- [data-tenant="acme-web"] .tx-lead { font-size: 1.1875rem; line-height: 1.6; color: hsl(var(--muted-foreground)); max-width: 480px; }
89
+ /* .tx-display / .tx-h2 / .tx-lead are GONE (gh#826). They existed only because the display type
90
+ ramp had tokens and no prop, so all three were a raw font-size plus the leading that has to come
91
+ with it. They are now Heading size="5xl", Heading size="4xl" and Text size="xl". The
92
+ brand face is not lost with them: base.css already points every <h1>-<h6> at
93
+ --font-family-display, which the token block above sets. The one knob those classes carried that
94
+ is genuinely brand and not scale is the display tracking, and it IS a token — set once, below. */
83
95
  /* --text-brand, not --primary (gh#643): --primary is a FILL role, and as ink it measured
84
96
  2.01:1 here. The AA-safe text tier already exists and .tx-eyebrow above already reads it. */
85
97
  [data-tenant="acme-web"] .tx-gold { color: hsl(var(--text-brand)); }
86
98
  [data-tenant="acme-web"] .tx-stat { font-family: var(--font-family-display); font-weight: 800; font-size: 1.75rem; color: hsl(var(--primary)); }
87
- [data-tenant="acme-web"] .tx-hero-grid { display: grid; gap: 3rem; align-items: center; padding-block: 6rem; }
99
+ [data-tenant="acme-web"] .tx-hero-grid { display: grid; gap: var(--space-12); align-items: center; padding-block: var(--space-section-hero); }
88
100
  @media (min-width: 1024px) { [data-tenant="acme-web"] .tx-hero-grid { grid-template-columns: 1.1fr 0.9fr; } }
89
- [data-tenant="acme-web"] .tx-glow { position: absolute; inset: 0; pointer-events: none; }
90
- [data-tenant="acme-web"] .tx-glow-tr { background: radial-gradient(40% 50% at 88% -10%, hsl(41 73% 53% / 0.22), transparent 70%); }
91
- [data-tenant="acme-web"] .tx-glow-bl { background: radial-gradient(40% 50% at 12% 120%, hsl(41 73% 53% / 0.18), transparent 70%); }
101
+ /* .tx-glow* are GONE too: .ui-brand-glow shipped with exactly this gradient and these four
102
+ knobs, and nothing in the repo referenced it while four sections hand-wrote it (gh#826). The
103
+ halo's position/size/alpha are now per-instance token overrides at the call site. */
92
104
  [data-tenant="acme-web"] .tx-field { display: flex; align-items: center; gap: 0.75rem; padding: 0.75rem 1rem;
93
105
  background: hsl(0 0% 100% / 0.08); border-radius: var(--radius-md); }
94
106
  [data-tenant="acme-web"] .tx-quote-total { display: flex; align-items: center; justify-content: space-between;
@@ -98,10 +110,7 @@ const THEME = `
98
110
  [data-tenant="acme-web"] .tx-footer-grid { display: grid; gap: 2.25rem; }
99
111
  @media (min-width: 768px) { [data-tenant="acme-web"] .tx-footer-grid { grid-template-columns: 1.4fr 1fr 1fr 1fr; } }
100
112
  [data-tenant="acme-web"] .tx-footer-bottom { display: flex; align-items: center; justify-content: space-between;
101
- margin-top: 2.25rem; padding-top: 1.25rem; border-top: 1px solid hsl(0 0% 100% / 0.1); }
102
- [data-tenant="acme-web"] .tx-medallion svg { width: 1.25rem; height: 1.25rem; }
103
- [data-tenant="acme-web"] .tx-icon-18 { width: 1.125rem; height: 1.125rem; }
104
- [data-tenant="acme-web"] .tx-icon-22 { width: 1.375rem; height: 1.375rem; }
113
+ margin-block-start: 2.25rem; padding-block-start: var(--space-5); border-block-start: var(--stroke-hairline) solid hsl(0 0% 100% / 0.1); }
105
114
  /* Navy region: role scoping so descendants render on-dark (white text, glass cards, outline btns). */
106
115
  [data-tenant="acme-web"] .tx-navy {
107
116
  background: hsl(217 61% 12%); --foreground: 0 0% 100%;
@@ -156,19 +165,28 @@ function Hero() {
156
165
  ];
157
166
  return (
158
167
  <section className="tx-navy relative overflow-hidden">
159
- <div className="tx-glow tx-glow-tr" />
168
+ <div
169
+ className="ui-brand-glow absolute inset-0"
170
+ style={
171
+ {
172
+ "--brand-glow-size": "40% 50%",
173
+ "--brand-glow-position": "88% -10%",
174
+ "--brand-glow-alpha": "0.22",
175
+ } as React.CSSProperties
176
+ }
177
+ />
160
178
  <div className={`${SHELL} tx-hero-grid relative`}>
161
179
  <Flex direction="col" gap="xl">
162
180
  <div className="tx-eyebrow">Mua hộ &amp; vận chuyển quốc tế</div>
163
- <h1 className="tx-display">
181
+ <Heading level={1} size="5xl" weight="bold">
164
182
  Mua sắm toàn cầu,
165
183
  <br />
166
184
  <span className="tx-gold">ACME</span> lo phần còn lại.
167
- </h1>
168
- <p className="tx-lead">
185
+ </Heading>
186
+ <Text as="p" size="xl" tone="muted">
169
187
  Đặt mua, thanh toán và vận chuyển hàng từ Nhật, Hàn, Indonesia và Mỹ về tận nhà. Minh
170
188
  bạch chi phí, theo dõi từng bước.
171
- </p>
189
+ </Text>
172
190
  <Flex direction="row" gap="md">
173
191
  <Button size="lg">
174
192
  Nhận báo giá miễn phí
@@ -196,9 +214,9 @@ function Hero() {
196
214
  <Flex direction="col" gap="md">
197
215
  <div className="tx-eyebrow">Ước tính phí vận chuyển</div>
198
216
  <Flex direction="col" gap="sm">
199
- {fields.map(([Icon, label, value]) => (
217
+ {fields.map(([Glyph, label, value]) => (
200
218
  <div key={label} className="tx-field">
201
- <Icon aria-hidden="true" className="tx-gold tx-icon-18" />
219
+ <Icon as={Glyph} size="lg" className="tx-gold" />
202
220
  <div className="min-w-0 flex-1">
203
221
  <Text as="div" size="2xs" tone="muted">
204
222
  {label}
@@ -207,7 +225,7 @@ function Hero() {
207
225
  {value}
208
226
  </Text>
209
227
  </div>
210
- <ChevronDown aria-hidden="true" className="tx-icon-18" />
228
+ <Icon as={ChevronDown} size="lg" />
211
229
  </div>
212
230
  ))}
213
231
  <div className="tx-quote-total">
@@ -241,17 +259,23 @@ function SectionHead({
241
259
  return (
242
260
  <div className={center ? "mx-auto text-center" : undefined}>
243
261
  <div className="tx-eyebrow">{eyebrow}</div>
244
- <h2 className="tx-h2">{title}</h2>
245
- {sub ? <p className="tx-lead mx-auto">{sub}</p> : null}
262
+ <Heading level={2} size="4xl" weight="bold">
263
+ {title}
264
+ </Heading>
265
+ {sub ? (
266
+ <Text as="p" size="xl" tone="muted" className="mx-auto">
267
+ {sub}
268
+ </Text>
269
+ ) : null}
246
270
  </div>
247
271
  );
248
272
  }
249
273
 
250
- function Medallion({ icon: Icon }: { icon: typeof Globe }) {
274
+ function Medallion({ icon: Glyph }: { icon: typeof Globe }) {
251
275
  return (
252
276
  <Avatar className="rounded-md">
253
- <AvatarFallback className="bg-primary/10 text-primary tx-medallion rounded-md">
254
- <Icon aria-hidden="true" />
277
+ <AvatarFallback className="bg-primary/10 text-primary rounded-md">
278
+ <Icon as={Glyph} size="lg" />
255
279
  </AvatarFallback>
256
280
  </Avatar>
257
281
  );
@@ -315,7 +339,7 @@ function Steps() {
315
339
  <Flex direction="col" gap="xl" className={SHELL}>
316
340
  <SectionHead eyebrow="Quy trình" title="Mua hàng quốc tế trong 4 bước" />
317
341
  <ResponsiveGrid columns={{ sm: 1, md: 2, lg: 4 }}>
318
- {steps.map(([Icon, t, d], i) => (
342
+ {steps.map(([Glyph, t, d], i) => (
319
343
  <Flex direction="col" gap="md" key={t}>
320
344
  <Flex direction="row" gap="sm" align="center">
321
345
  <Avatar>
@@ -323,7 +347,7 @@ function Steps() {
323
347
  {i + 1}
324
348
  </AvatarFallback>
325
349
  </Avatar>
326
- <Icon aria-hidden="true" className="text-brand tx-icon-22" />
350
+ <Icon as={Glyph} size="xl" className="text-brand" />
327
351
  </Flex>
328
352
  <Text as="div" weight="bold" size="lg">
329
353
  {t}
@@ -380,10 +404,22 @@ function Routes() {
380
404
  function CtaBanner() {
381
405
  return (
382
406
  <section className="tx-navy tx-section relative overflow-hidden">
383
- <div className="tx-glow tx-glow-bl" />
407
+ <div
408
+ className="ui-brand-glow absolute inset-0"
409
+ style={
410
+ {
411
+ "--brand-glow-size": "40% 50%",
412
+ "--brand-glow-position": "12% 120%",
413
+ } as React.CSSProperties
414
+ }
415
+ />
384
416
  <div className={`${SHELL} relative text-center`}>
385
- <h2 className="tx-h2">Sẵn sàng cho đơn hàng đầu tiên?</h2>
386
- <p className="tx-lead mx-auto">Tạo tài khoản miễn phí và nhận báo giá trong 5 phút.</p>
417
+ <Heading level={2} size="4xl" weight="bold">
418
+ Sẵn sàng cho đơn hàng đầu tiên?
419
+ </Heading>
420
+ <Text as="p" size="xl" tone="muted" className="mx-auto">
421
+ Tạo tài khoản miễn phí và nhận báo giá trong 5 phút.
422
+ </Text>
387
423
  <Flex direction="row" gap="md" justify="center">
388
424
  <Button size="lg">
389
425
  <UserPlus aria-hidden="true" />
@@ -406,7 +442,7 @@ function Footer() {
406
442
  ];
407
443
  return (
408
444
  <footer className="tx-navy tx-navy-deep">
409
- <div className={`${SHELL} tx-footer-grid`} style={{ paddingBlock: "3.5rem 2rem" }}>
445
+ <div className={`${SHELL} tx-footer-grid`} style={{ paddingBlock: "3.5rem var(--space-8)" }}>
410
446
  <Flex direction="col" gap="md">
411
447
  <span className="tx-brand">
412
448
  TIXI<span className="tx-gold">MAX</span>
@@ -438,8 +474,8 @@ function Footer() {
438
474
  2026 ACME Logistics. All rights reserved.
439
475
  </Text>
440
476
  <Flex direction="row" gap="md">
441
- {[Mail, Phone, Globe].map((Icon, i) => (
442
- <Icon key={i} aria-hidden="true" className="text-muted-foreground tx-icon-18" />
477
+ {[Mail, Phone, Globe].map((Glyph, i) => (
478
+ <Icon key={i} as={Glyph} size="lg" tone="muted" />
443
479
  ))}
444
480
  </Flex>
445
481
  </div>
@@ -23,7 +23,7 @@ import {
23
23
  AtSign,
24
24
  } from "lucide-react";
25
25
 
26
- import { Button, Text } from "@godxjp/ui/general";
26
+ import { Button, Heading, Icon, Text } from "@godxjp/ui/general";
27
27
  import { Avatar, AvatarFallback, Card, CardContent } from "@godxjp/ui/data-display";
28
28
  import { Flex } from "@godxjp/ui/layout";
29
29
 
@@ -46,6 +46,7 @@ const THEME = `
46
46
  a card, 3.21 on --muted. It was 36 18% 22% = 1.52:1. --border is decorative chrome; it stays. */
47
47
  --border: 36 22% 14%; --input: 36 18% 40%;
48
48
  --success: 145 63% 49%; --warning: 33 90% 44%; --destructive: 0 84% 60%; --info: 213 94% 68%;
49
+ --letter-spacing-tight: -0.03em; /* the display tracking .fl-display/.fl-h2 hand-wrote, once */
49
50
  --radius: 1rem; --radius-md: 10px; --radius-lg: 14px; --radius-xl: 20px; --radius-2xl: 24px;
50
51
  --card-radius: var(--radius-2xl); --control-radius: var(--radius-lg);
51
52
  --shadow-color: 0 0 0;
@@ -54,15 +55,32 @@ const THEME = `
54
55
  --font-family-display: "Sora", system-ui, sans-serif;
55
56
  --font-family-body: "Be Vietnam Pro", system-ui, "Noto Sans JP", sans-serif;
56
57
  --font-family-sans: var(--font-family-body);
57
- --font-size-display: 5rem; /* 80px hero via text-5xl */
58
+ /* 80px hero. --font-size-display is the display ramp's BASE KNOB, but overriding it HERE does
59
+ not move the derived steps: --font-size-5xl is declared at :root as var(--font-size-display),
60
+ so it computes on :root and merely INHERITS down — the freeze docs/TOKENS.md names, and the
61
+ reason its role-mirror knobs are declared initial. The whole --font-size-* scale is built this way
62
+ (--font-size-lg freezes on --font-size-base identically), so it is a systemic token-tier
63
+ defect, not this ramp's, and not gh#826's to fix. Measured, not assumed: with only the line
64
+ below, <Heading size="5xl"> painted 54px here, the :root value. Setting the STEP works,
65
+ because this declaration is on an ancestor of the heading. */
66
+ --font-size-display: 5rem;
67
+ --font-size-5xl: 5rem;
68
+ /* The glass bar, opted into once (gh#831). acme-website.tsx hand-wrote the identical pair,
69
+ which is why these are framework knobs now and not two literals on .fl-navbar. */
70
+ --topbar-background-alpha: 80%;
71
+ --topbar-backdrop-blur-size: 12px;
58
72
  color-scheme: dark;
59
73
  }
60
74
  /* Marketing surface (consumer section stylesheet) */
61
75
  [data-tenant="futurelastic"] { background: hsl(var(--background)); color: hsl(var(--foreground)); min-height: 100vh; }
62
- [data-tenant="futurelastic"] .fl-shell { margin-inline: auto; width: 100%; max-width: 1140px; padding-inline: 2rem; container-type: inline-size; }
63
- [data-tenant="futurelastic"] .fl-section { padding-block: 5.5rem; }
64
- [data-tenant="futurelastic"] .fl-navbar { position: sticky; top: 0; z-index: 30; background: hsl(var(--background) / 0.8);
65
- backdrop-filter: blur(12px); border-bottom: 1px solid hsl(var(--border)); }
76
+ [data-tenant="futurelastic"] .fl-shell { margin-inline: auto; width: 100%; max-inline-size: var(--page-measure-wide); padding-inline: var(--space-8); container-type: inline-size; }
77
+ [data-tenant="futurelastic"] .fl-section { padding-block: var(--space-section-band); }
78
+ /* The sticky header IS this page's bar region, so it reads the --topbar-* glass knobs. See the
79
+ matching note in acme-website.tsx for why it is not a <Topbar>. */
80
+ [data-tenant="futurelastic"] .fl-navbar { position: sticky; inset-block-start: 0; z-index: 30;
81
+ background-color: hsl(var(--background) / var(--topbar-background-alpha));
82
+ backdrop-filter: blur(var(--topbar-backdrop-blur-size));
83
+ border-block-end: var(--stroke-hairline) solid hsl(var(--border)); }
66
84
  /* REFLOW, NOT DECORATION (gh#643). This row was a fixed 68px flex line with no wrap, so at 320 its
67
85
  content ran to x=569.6 inside a 320px nav: every link painted OUTSIDE the navbar's own
68
86
  translucent box, over the page behind it, and axe read the white labels against the light
@@ -71,53 +89,57 @@ const THEME = `
71
89
  element looks like from the outside. min-height rather than height, so a wrapped row may be
72
90
  taller than one line and the language cluster stops overflowing the bar vertically (measured at
73
91
  320: it was 148.3px tall, starting at y=-40.2). */
74
- [data-tenant="futurelastic"] .fl-navbar-inner { min-height: 68px; display: flex; align-items: center; flex-wrap: wrap; column-gap: 2rem; row-gap: 0.75rem; padding-block: 0.75rem; }
92
+ [data-tenant="futurelastic"] .fl-navbar-inner { min-block-size: 68px; display: flex; align-items: center; flex-wrap: wrap; column-gap: var(--space-8); row-gap: var(--space-3); padding-block: var(--space-3); }
75
93
  [data-tenant="futurelastic"] .fl-brand { font-family: var(--font-family-display); font-weight: 700; font-size: 1.25rem; color: hsl(var(--foreground)); }
76
94
  [data-tenant="futurelastic"] .fl-eyebrow { font-family: var(--font-family-body); font-weight: 600; font-size: 0.75rem;
77
95
  letter-spacing: 0.18em; text-transform: uppercase; color: hsl(var(--primary)); }
78
- [data-tenant="futurelastic"] .fl-badge { display: inline-flex; align-items: center; gap: 0.5rem; padding: 0.375rem 0.875rem;
79
- border: 1px solid hsl(var(--primary) / 0.3); background: hsl(var(--primary) / 0.08); border-radius: 999px;
96
+ [data-tenant="futurelastic"] .fl-badge { display: inline-flex; align-items: center; gap: var(--space-2); padding: 0.375rem 0.875rem;
97
+ border: var(--stroke-hairline) solid hsl(var(--primary) / 0.3); background: hsl(var(--primary) / 0.08); border-radius: var(--radius-pill);
80
98
  font-size: 0.8125rem; font-weight: 500; color: hsl(var(--foreground)); }
81
- [data-tenant="futurelastic"] .fl-badge .dot { width: 6px; height: 6px; border-radius: 999px; background: hsl(var(--primary)); }
82
- [data-tenant="futurelastic"] .fl-hero { position: relative; overflow: hidden; padding-block: clamp(5rem, 3rem + 9vw, 10rem); }
83
- [data-tenant="futurelastic"] .fl-hero-glow { position: absolute; inset: 0; pointer-events: none;
84
- background: radial-gradient(60% 50% at 70% 0%, hsl(42 54% 54% / 0.12), transparent 70%); }
99
+ [data-tenant="futurelastic"] .fl-badge .dot { inline-size: 6px; block-size: 6px; border-radius: var(--radius-pill); background: hsl(var(--primary)); }
100
+ /* The hero band still breathes with the viewport, but both ends of the clamp are now the band/hero
101
+ steps rather than an eyeballed 5rem…10rem (gh#831). */
102
+ [data-tenant="futurelastic"] .fl-hero { position: relative; overflow: hidden; padding-block: clamp(var(--space-section-band), 3rem + 9vw, var(--space-section-hero)); }
103
+ /* The halo is .ui-brand-glow (src/styles/layout.css), which ships and was going unused. This
104
+ class and .fl-cta-glow now only place it and set its documented knobs. */
105
+ [data-tenant="futurelastic"] .fl-hero-glow { position: absolute; inset: 0;
106
+ --brand-glow-color: 42 54% 54%; --brand-glow-size: 60% 50%; --brand-glow-position: 70% 0%; --brand-glow-alpha: 0.12; }
85
107
  [data-tenant="futurelastic"] .fl-hero-inner { position: relative; max-width: 860px; }
86
- [data-tenant="futurelastic"] .fl-display { font-family: var(--font-family-display); font-weight: 700; font-size: 5rem;
87
- line-height: 1.05; letter-spacing: -0.03em; color: hsl(var(--foreground)); margin: 1.25rem 0; }
88
- [data-tenant="futurelastic"] .fl-h2 { font-family: var(--font-family-display); font-weight: 700; font-size: 2.5rem;
89
- line-height: 1.1; letter-spacing: -0.02em; color: hsl(var(--foreground)); margin: 0.75rem 0; }
108
+ /* .fl-display / .fl-h2 / .fl-lead / .fl-hero-glow / .fl-cta-glow are GONE (gh#826) — and the fact
109
+ that this file and acme-website.tsx, two unrelated brands, had independently written the SAME
110
+ five is what made them a missing API rather than brand styling. They are now
111
+ Heading size="5xl", Heading size="4xl", Text size="xl" and two .ui-brand-glow
112
+ layers. The display face still arrives through --font-family-display (base.css points every
113
+ <h1>-<h6> at it); the tracking is a token, set once above. */
90
114
  [data-tenant="futurelastic"] .fl-gold { color: hsl(var(--primary)); }
91
- [data-tenant="futurelastic"] .fl-lead { font-size: 1.25rem; line-height: 1.65; color: hsl(var(--muted-foreground)); max-width: 620px; }
92
- [data-tenant="futurelastic"] .fl-note { font-size: 0.9375rem; color: hsl(var(--muted-foreground)); margin-top: 2.5rem; }
115
+ [data-tenant="futurelastic"] .fl-note { font-size: 0.9375rem; color: hsl(var(--muted-foreground)); margin-block-start: var(--space-10); }
93
116
  [data-tenant="futurelastic"] .fl-head { max-width: 640px; margin-inline: auto; text-align: center; }
94
- [data-tenant="futurelastic"] .fl-hero-actions { margin-top: 1.75rem; }
117
+ [data-tenant="futurelastic"] .fl-hero-actions { margin-block-start: 1.75rem; }
95
118
  [data-tenant="futurelastic"] .fl-cta-inner { position: relative; max-width: 680px; margin-inline: auto; }
96
119
  [data-tenant="futurelastic"] .fl-footer-tagline { max-width: 260px; }
97
- [data-tenant="futurelastic"] .fl-logocloud { display: flex; flex-wrap: wrap; justify-content: center; gap: 1rem 2.5rem; }
98
- [data-tenant="futurelastic"] .fl-logo-item { display: inline-flex; align-items: center; gap: 0.5rem; font-family: var(--font-family-display);
120
+ [data-tenant="futurelastic"] .fl-logocloud { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--space-4) var(--space-10); }
121
+ [data-tenant="futurelastic"] .fl-logo-item { display: inline-flex; align-items: center; gap: var(--space-2); font-family: var(--font-family-display);
99
122
  font-weight: 600; font-size: 1.0625rem; color: hsl(var(--foreground) / 0.85); }
100
123
  [data-tenant="futurelastic"] .fl-logo-item .tag { font-family: var(--font-family-body); font-weight: 500; font-size: 0.6875rem;
101
124
  letter-spacing: 0.06em; text-transform: uppercase; color: hsl(var(--primary)); }
102
- [data-tenant="futurelastic"] .fl-section-tint { background: hsl(var(--card) / 0.5); border-block: 1px solid hsl(var(--border)); padding-block: 3rem; }
103
- [data-tenant="futurelastic"] .fl-bento { display: grid; grid-template-columns: 1fr; gap: 1.25rem; }
125
+ [data-tenant="futurelastic"] .fl-section-tint { background: hsl(var(--card) / 0.5); border-block: var(--stroke-hairline) solid hsl(var(--border)); padding-block: var(--space-12); }
126
+ [data-tenant="futurelastic"] .fl-bento { display: grid; grid-template-columns: 1fr; gap: var(--space-5); }
104
127
  @container (min-width: 768px) { [data-tenant="futurelastic"] .fl-bento { grid-template-columns: repeat(6, 1fr); } }
105
128
  @container (min-width: 768px) { [data-tenant="futurelastic"] .fl-bento > .fl-cell { grid-column: span 2; } }
106
129
  @container (min-width: 768px) { [data-tenant="futurelastic"] .fl-bento > .fl-cell.wide { grid-column: span 4; } }
107
130
  @container (min-width: 768px) { [data-tenant="futurelastic"] .fl-bento > .fl-cell.half { grid-column: span 3; } }
108
- [data-tenant="futurelastic"] .fl-medallion svg { width: 1.5rem; height: 1.5rem; }
109
- [data-tenant="futurelastic"] .fl-stats { display: grid; grid-template-columns: repeat(2, 1fr); gap: 2rem; }
131
+ [data-tenant="futurelastic"] .fl-stats { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-8); }
110
132
  @container (min-width: 768px) { [data-tenant="futurelastic"] .fl-stats { grid-template-columns: repeat(4, 1fr); } }
111
133
  [data-tenant="futurelastic"] .fl-stat-num { font-family: var(--font-family-display); font-weight: 700; font-size: 3rem; color: hsl(var(--primary)); line-height: 1; }
112
- [data-tenant="futurelastic"] .fl-cta { position: relative; overflow: hidden; text-align: center; border: 1px solid hsl(var(--primary) / 0.2);
113
- border-radius: var(--radius-2xl); background: hsl(var(--card)); padding: 4rem 2rem; }
114
- [data-tenant="futurelastic"] .fl-cta-glow { position: absolute; inset: 0; pointer-events: none;
115
- background: radial-gradient(60% 80% at 50% 0%, hsl(42 54% 54% / 0.14), transparent 70%); }
116
- [data-tenant="futurelastic"] .fl-footer { border-top: 1px solid hsl(var(--border)); padding-block: 3.5rem 2rem; }
117
- [data-tenant="futurelastic"] .fl-footer-grid { display: grid; grid-template-columns: 1fr; gap: 2.5rem; }
134
+ [data-tenant="futurelastic"] .fl-cta { position: relative; overflow: hidden; text-align: center; border: var(--stroke-hairline) solid hsl(var(--primary) / 0.2);
135
+ border-radius: var(--radius-2xl); background: hsl(var(--card)); padding: 4rem var(--space-8); }
136
+ [data-tenant="futurelastic"] .fl-cta-glow { position: absolute; inset: 0;
137
+ --brand-glow-color: 42 54% 54%; --brand-glow-size: 60% 80%; --brand-glow-position: 50% 0%; --brand-glow-alpha: 0.14; }
138
+ [data-tenant="futurelastic"] .fl-footer { border-block-start: var(--stroke-hairline) solid hsl(var(--border)); padding-block: 3.5rem var(--space-8); }
139
+ [data-tenant="futurelastic"] .fl-footer-grid { display: grid; grid-template-columns: 1fr; gap: var(--space-10); }
118
140
  @container (min-width: 768px) { [data-tenant="futurelastic"] .fl-footer-grid { grid-template-columns: 1.6fr 1fr 1fr 1fr; } }
119
- [data-tenant="futurelastic"] .fl-footer-bottom { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 1rem;
120
- margin-top: 2.5rem; padding-top: 1.5rem; border-top: 1px solid hsl(var(--border)); }
141
+ [data-tenant="futurelastic"] .fl-footer-bottom { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: var(--space-4);
142
+ margin-block-start: var(--space-10); padding-block-start: var(--space-6); border-block-start: var(--stroke-hairline) solid hsl(var(--border)); }
121
143
  `;
122
144
 
123
145
  const SHELL = "fl-shell";
@@ -151,18 +173,26 @@ function Navbar() {
151
173
  function Hero() {
152
174
  return (
153
175
  <header className="fl-hero">
154
- <div className="fl-hero-glow" />
176
+ <div
177
+ className="ui-brand-glow absolute inset-0"
178
+ style={
179
+ {
180
+ "--brand-glow-alpha": "0.12",
181
+ "--brand-glow-position": "70% 0%",
182
+ } as React.CSSProperties
183
+ }
184
+ />
155
185
  <div className={`${SHELL} fl-hero-inner`}>
156
186
  <span className="fl-badge">
157
187
  <span className="dot" /> Tech · AI · Holdings
158
188
  </span>
159
- <h1 className="fl-display">
189
+ <Heading level={1} size="5xl" weight="bold">
160
190
  Building the <span className="fl-gold">elastic</span> infrastructure of the future.
161
- </h1>
162
- <p className="fl-lead">
191
+ </Heading>
192
+ <Text as="p" size="xl" tone="muted">
163
193
  Futurelastic là pháp nhân holding của nhóm công ty công nghệ &amp; AI vận hành xuyên Nhật
164
194
  Bản và Việt Nam, từ logistics, fintech đến F&amp;B và thương mại.
165
- </p>
195
+ </Text>
166
196
  <Flex direction="row" gap="md" className="fl-hero-actions">
167
197
  <Button size="lg">Khám phá hệ sinh thái</Button>
168
198
  <Button variant="outline" size="lg">
@@ -201,11 +231,11 @@ function LogoCloud() {
201
231
  );
202
232
  }
203
233
 
204
- function Medallion({ icon: Icon }: { icon: typeof Boxes }) {
234
+ function Medallion({ icon: Glyph }: { icon: typeof Boxes }) {
205
235
  return (
206
236
  <Avatar className="rounded-lg">
207
- <AvatarFallback className="bg-primary/10 text-primary fl-medallion rounded-lg">
208
- <Icon aria-hidden="true" />
237
+ <AvatarFallback className="bg-primary/10 text-primary rounded-lg">
238
+ <Icon as={Glyph} size="xl" />
209
239
  </AvatarFallback>
210
240
  </Avatar>
211
241
  );
@@ -247,10 +277,12 @@ function Bento() {
247
277
  <Flex direction="col" gap="xl" className={SHELL}>
248
278
  <div className="fl-head">
249
279
  <div className="fl-eyebrow">Hệ sinh thái</div>
250
- <h2 className="fl-h2">Một mái nhà, nhiều mũi nhọn</h2>
251
- <p className="fl-lead mx-auto">
280
+ <Heading level={2} size="4xl" weight="bold">
281
+ Một mái nhà, nhiều mũi nhọn
282
+ </Heading>
283
+ <Text as="p" size="xl" tone="muted" className="mx-auto">
252
284
  Mỗi entity tự chủ vận hành; Futurelastic cung cấp vốn, công nghệ nền và quản trị chung.
253
- </p>
285
+ </Text>
254
286
  </div>
255
287
  <div className="fl-bento">
256
288
  {cells.map((c) => (
@@ -304,13 +336,23 @@ function Cta() {
304
336
  <section className="fl-section">
305
337
  <div className={SHELL}>
306
338
  <div className="fl-cta">
307
- <div className="fl-cta-glow" />
339
+ <div
340
+ className="ui-brand-glow absolute inset-0"
341
+ style={
342
+ {
343
+ "--brand-glow-alpha": "0.14",
344
+ "--brand-glow-size": "60% 80%",
345
+ } as React.CSSProperties
346
+ }
347
+ />
308
348
  <Flex direction="col" gap="xl" className="fl-cta-inner">
309
- <h2 className="fl-h2">Cùng xây điều mới?</h2>
310
- <p className="fl-lead mx-auto">
349
+ <Heading level={2} size="4xl" weight="bold">
350
+ Cùng xây điều mới?
351
+ </Heading>
352
+ <Text as="p" size="xl" tone="muted" className="mx-auto">
311
353
  Chúng tôi tìm đối tác, nhân tài và cơ hội M&amp;A trong công nghệ, logistics và
312
354
  F&amp;B tại Nhật Bản &amp; Việt Nam.
313
- </p>
355
+ </Text>
314
356
  <Flex direction="row" gap="md" className="justify-center">
315
357
  <Button size="lg">Đặt lịch trao đổi</Button>
316
358
  <Button variant="ghost" size="lg">