@godxjp/ui 18.4.0 → 18.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 (131) hide show
  1. package/README.md +16 -1
  2. package/dist/app/theme-axes.d.ts +11 -3
  3. package/dist/app/theme-axes.js +2 -1
  4. package/dist/components/charts/chart-frame.d.ts +17 -3
  5. package/dist/components/charts/chart-frame.js +7 -1
  6. package/dist/components/charts/chart-summary.d.ts +21 -0
  7. package/dist/components/charts/chart-summary.js +32 -5
  8. package/dist/components/charts/compact-bar-trend.d.ts +29 -0
  9. package/dist/components/charts/compact-bar-trend.js +74 -0
  10. package/dist/components/charts/index.d.ts +3 -0
  11. package/dist/components/charts/index.js +2 -0
  12. package/dist/components/data-display/badge.d.ts +8 -0
  13. package/dist/components/data-display/badge.js +7 -1
  14. package/dist/components/data-display/data-table.d.ts +16 -1
  15. package/dist/components/data-display/data-table.js +56 -0
  16. package/dist/components/data-display/index.d.ts +3 -1
  17. package/dist/components/data-display/index.js +10 -1
  18. package/dist/components/data-display/list-row.d.ts +22 -4
  19. package/dist/components/data-display/list-row.js +21 -3
  20. package/dist/components/data-display/service-launcher-card.d.ts +40 -0
  21. package/dist/components/data-display/service-launcher-card.js +97 -0
  22. package/dist/components/data-entry/command-palette.d.ts +37 -0
  23. package/dist/components/data-entry/command-palette.js +125 -0
  24. package/dist/components/data-entry/index.d.ts +2 -0
  25. package/dist/components/data-entry/index.js +2 -0
  26. package/dist/components/feedback/dialog.d.ts +2 -0
  27. package/dist/components/feedback/dialog.js +42 -33
  28. package/dist/components/feedback/index.d.ts +4 -1
  29. package/dist/components/feedback/index.js +6 -2
  30. package/dist/components/feedback/sheet.d.ts +26 -4
  31. package/dist/components/feedback/sheet.js +54 -7
  32. package/dist/components/feedback/two-factor-setup.d.ts +31 -0
  33. package/dist/components/feedback/two-factor-setup.js +97 -0
  34. package/dist/components/general/index.d.ts +1 -1
  35. package/dist/components/general/logo.d.ts +42 -8
  36. package/dist/components/general/logo.js +67 -14
  37. package/dist/components/layout/app-shell.js +21 -5
  38. package/dist/components/layout/auth-divider.d.ts +7 -0
  39. package/dist/components/layout/auth-divider.js +21 -0
  40. package/dist/components/layout/auth-footer.d.ts +5 -0
  41. package/dist/components/layout/auth-footer.js +24 -0
  42. package/dist/components/layout/auth-identity.d.ts +5 -0
  43. package/dist/components/layout/auth-identity.js +17 -0
  44. package/dist/components/layout/auth-shell.d.ts +8 -1
  45. package/dist/components/layout/auth-shell.js +25 -6
  46. package/dist/components/layout/auth-stack.d.ts +4 -0
  47. package/dist/components/layout/auth-stack.js +8 -0
  48. package/dist/components/layout/centered-shell.d.ts +5 -2
  49. package/dist/components/layout/centered-shell.js +2 -1
  50. package/dist/components/layout/index.d.ts +18 -3
  51. package/dist/components/layout/index.js +17 -2
  52. package/dist/components/layout/legal-document-shell.d.ts +34 -0
  53. package/dist/components/layout/legal-document-shell.js +186 -0
  54. package/dist/components/layout/master-detail.d.ts +24 -0
  55. package/dist/components/layout/master-detail.js +48 -0
  56. package/dist/components/layout/org-switcher.d.ts +9 -0
  57. package/dist/components/layout/org-switcher.js +218 -0
  58. package/dist/components/layout/page-container.d.ts +1 -1
  59. package/dist/components/layout/page-container.js +3 -2
  60. package/dist/components/layout/sidebar-link.d.ts +43 -0
  61. package/dist/components/layout/sidebar-link.js +13 -0
  62. package/dist/components/layout/sidebar.d.ts +42 -5
  63. package/dist/components/layout/sidebar.js +170 -85
  64. package/dist/components/navigation/app-setting-picker.js +17 -6
  65. package/dist/components/navigation/filter-bar.d.ts +12 -3
  66. package/dist/components/navigation/filter-bar.js +8 -2
  67. package/dist/components/navigation/index.d.ts +2 -2
  68. package/dist/components/navigation/index.js +3 -1
  69. package/dist/components/navigation/tabs-scroll.d.ts +70 -0
  70. package/dist/components/navigation/tabs-scroll.js +76 -0
  71. package/dist/components/navigation/tabs.js +35 -20
  72. package/dist/email/brand-mark.d.ts +92 -0
  73. package/dist/email/brand-mark.js +83 -0
  74. package/dist/email/color.d.ts +52 -0
  75. package/dist/email/color.js +37 -0
  76. package/dist/email/geometry.d.ts +112 -0
  77. package/dist/email/geometry.js +89 -0
  78. package/dist/email/index.d.ts +78 -0
  79. package/dist/email/index.js +76 -0
  80. package/dist/email/inline-style.d.ts +25 -0
  81. package/dist/email/inline-style.js +21 -0
  82. package/dist/email/tokens.generated.d.ts +147 -0
  83. package/dist/email/tokens.generated.js +142 -0
  84. package/dist/i18n/messages/en.json +21 -2
  85. package/dist/i18n/messages/ja.json +21 -2
  86. package/dist/i18n/messages/vi.json +21 -2
  87. package/dist/inertia/index.d.ts +50 -0
  88. package/dist/inertia/index.js +5 -0
  89. package/dist/props/components/app.prop.d.ts +11 -1
  90. package/dist/props/components/charts.prop.d.ts +40 -1
  91. package/dist/props/components/data-display.prop.d.ts +13 -1
  92. package/dist/props/components/feedback.prop.d.ts +10 -0
  93. package/dist/props/components/index.d.ts +2 -2
  94. package/dist/props/components/layout.prop.d.ts +373 -10
  95. package/dist/props/components/layout.prop.js +1 -0
  96. package/dist/props/components/navigation.prop.d.ts +19 -1
  97. package/dist/props/registry.d.ts +291 -7
  98. package/dist/props/registry.js +341 -7
  99. package/dist/props/vocabulary/index.d.ts +1 -1
  100. package/dist/props/vocabulary/interaction.prop.d.ts +1 -1
  101. package/dist/props/vocabulary/layout.prop.d.ts +18 -0
  102. package/dist/styles/base.css +8 -2
  103. package/dist/styles/card-layout.css +161 -7
  104. package/dist/styles/chart-layout.css +97 -0
  105. package/dist/styles/control.css +48 -2
  106. package/dist/styles/data-display-layout.css +52 -6
  107. package/dist/styles/dialog-layout.css +105 -4
  108. package/dist/styles/fonts.css +15 -4
  109. package/dist/styles/index.css +11 -3
  110. package/dist/styles/layout.css +367 -1
  111. package/dist/styles/logo-layout.css +62 -0
  112. package/dist/styles/navigation-layout.css +43 -0
  113. package/dist/styles/shell-layout.css +488 -40
  114. package/dist/theme/dxs.canonical.css +76 -0
  115. package/dist/tokens/axes.css +39 -0
  116. package/dist/tokens/base.css +5 -0
  117. package/dist/tokens/components/card.css +48 -0
  118. package/dist/tokens/components/chart.css +47 -0
  119. package/dist/tokens/components/control.css +16 -0
  120. package/dist/tokens/components/email.css +66 -0
  121. package/dist/tokens/components/feedback.css +17 -0
  122. package/dist/tokens/components/legal-document.css +67 -0
  123. package/dist/tokens/components/list-row.css +23 -2
  124. package/dist/tokens/components/logo.css +32 -0
  125. package/dist/tokens/components/navigation.css +13 -0
  126. package/dist/tokens/components/sheet.css +19 -0
  127. package/dist/tokens/components/shell.css +138 -0
  128. package/dist/tokens/components/sidebar.css +33 -0
  129. package/dist/tokens/foundation.css +5 -2
  130. package/dist/tokens/semantic/layout.css +44 -2
  131. package/package.json +16 -5
@@ -195,6 +195,102 @@
195
195
  min-width: 0;
196
196
  }
197
197
 
198
+ /* MasterDetail owns its tracks AND its collapse threshold — both token-driven (gh#223).
199
+ *
200
+ * WHY FLEX AND NOT `@container (min-width: …)`: a media/container-query CONDITION cannot read a
201
+ * `var()` — it is resolved before custom properties, so a tokenised breakpoint is impossible
202
+ * there (the reason the first cut hard-coded `40rem`, violating rule #45). The flex threshold
203
+ * below ("Holy Albatross") puts the breakpoint in a `calc()`, where a `var()` DOES resolve, so
204
+ * `--master-detail-collapse-below` is a genuine knob a service theme can retune once globally.
205
+ *
206
+ * How it reads: the fluid region's flex-basis is `(threshold - 100%) * 999`, where 100% is the
207
+ * composition's own inline size (so, like the old container query, the decision comes from the
208
+ * space available to the layout — never the viewport).
209
+ * · width < threshold → a huge positive basis → each region takes a whole flex line and
210
+ * shrinks to it: master stacked above detail, both full width.
211
+ * · width ≥ threshold → the calc goes negative and is clamped to 0 → one line: the fluid
212
+ * region absorbs the free space while the rail keeps its token width (`max()` keeps the
213
+ * rail's basis at the preset because a negative operand can never win a max).
214
+ *
215
+ * `data-rail` picks WHICH region is the fixed rail. Default `detail` = the canonical
216
+ * "fluid list + fixed detail rail" (1fr / 320px) composition; `master` puts the fixed rail on
217
+ * the leading edge for a category/navigator list. DOM order is always master → detail, so the
218
+ * stacked order is list-then-detail either way, and the flex row follows the writing direction
219
+ * (RTL-safe without a single physical property). */
220
+ .ui-master-detail {
221
+ display: flex;
222
+ flex-wrap: wrap;
223
+ gap: var(--master-detail-gap);
224
+ --master-detail-rail-size: var(--master-detail-rail-standard);
225
+ }
226
+
227
+ .ui-master-detail[data-rail-width="compact"] {
228
+ --master-detail-rail-size: var(--master-detail-rail-compact);
229
+ }
230
+
231
+ /* Per-instance override of the token threshold (theme sets it globally, prop overrides here). */
232
+ .ui-master-detail[data-collapse-below="false"] {
233
+ --master-detail-collapse-below: 0px;
234
+ }
235
+ .ui-master-detail[data-collapse-below="sm"] {
236
+ --master-detail-collapse-below: 40rem;
237
+ }
238
+ .ui-master-detail[data-collapse-below="md"] {
239
+ --master-detail-collapse-below: 48rem;
240
+ }
241
+ .ui-master-detail[data-collapse-below="lg"] {
242
+ --master-detail-collapse-below: 64rem;
243
+ }
244
+ .ui-master-detail[data-collapse-below="xl"] {
245
+ --master-detail-collapse-below: 80rem;
246
+ }
247
+
248
+ .ui-master-detail-master,
249
+ .ui-master-detail-detail {
250
+ min-inline-size: 0;
251
+ }
252
+
253
+ /* The fluid region. */
254
+ .ui-master-detail[data-rail="detail"] > .ui-master-detail-master,
255
+ .ui-master-detail[data-rail="master"] > .ui-master-detail-detail {
256
+ flex: 1 1 calc((var(--master-detail-collapse-below) - 100%) * 999);
257
+ }
258
+
259
+ /* The fixed rail. */
260
+ .ui-master-detail[data-rail="detail"] > .ui-master-detail-detail,
261
+ .ui-master-detail[data-rail="master"] > .ui-master-detail-master {
262
+ flex: 0 1
263
+ max(var(--master-detail-rail-size), calc((var(--master-detail-collapse-below) - 100%) * 999));
264
+ }
265
+
266
+ /* Bounded master viewport (gh#231) — OPT-IN via `masterViewport`. `auto` (the default) emits no
267
+ * bound at all, so an existing composition renders byte-identically; the two presets cap the
268
+ * collection's BLOCK size with a token and scroll it INSIDE the region, which is what keeps a
269
+ * long real collection from pushing the detail thousands of pixels below the fold once the
270
+ * layout stacks. Block axis only — the inline axis stays owned by the flex threshold above, so
271
+ * the bound is writing-direction agnostic (RTL-safe).
272
+ *
273
+ * A11y: the region carries tabIndex={0} in this mode (see master-detail.tsx) so the scroll
274
+ * container is reachable and scrollable by keyboard alone, and the inset below reserves room
275
+ * for a focused row's focus ring — an overflow container clips BOTH axes, so a ring drawn at
276
+ * the region's edge would otherwise be cut. Focus is never trapped: the region is a plain tab
277
+ * stop that Tab/Shift+Tab leave normally. */
278
+ .ui-master-detail[data-master-viewport="compact"] > .ui-master-detail-master,
279
+ .ui-master-detail[data-master-viewport="standard"] > .ui-master-detail-master {
280
+ overflow-y: auto;
281
+ overscroll-behavior: contain;
282
+ padding: var(--master-detail-master-viewport-inset);
283
+ scroll-padding-block: var(--master-detail-master-viewport-inset);
284
+ }
285
+
286
+ .ui-master-detail[data-master-viewport="compact"] > .ui-master-detail-master {
287
+ max-block-size: var(--master-detail-master-viewport-compact);
288
+ }
289
+
290
+ .ui-master-detail[data-master-viewport="standard"] > .ui-master-detail-master {
291
+ max-block-size: var(--master-detail-master-viewport-standard);
292
+ }
293
+
198
294
  @container responsive-grid (min-width: 40rem) {
199
295
  .ui-responsive-grid {
200
296
  grid-template-columns: repeat(var(--responsive-grid-sm, 2), minmax(0, 1fr));
@@ -435,6 +531,34 @@
435
531
  }
436
532
  }
437
533
 
534
+ /* `headerLayout="responsive-inline"` (gh#231): below the 640px step the DEFAULT arrangement
535
+ * drops `extra` onto its own full-width line under the subtitle, which pushes a compact header
536
+ * control (a member search, one primary action) off the title row on a 390px screen. This
537
+ * arrangement keeps it beside the title band at a token-owned measure instead, and lets the
538
+ * title/subtitle wrap into the space that is left. Scoped to the compact range only, so at
539
+ * >=640px both arrangements resolve to the identical row below — and a page that never sets
540
+ * `headerLayout` never matches these rules at all. */
541
+ @media (max-width: 639.98px) {
542
+ .ui-page-header[data-layout="responsive-inline"] .ui-page-header-row {
543
+ flex-direction: row;
544
+ align-items: flex-start;
545
+ justify-content: space-between;
546
+ gap: var(--space-inline-md);
547
+ }
548
+
549
+ .ui-page-header[data-layout="responsive-inline"] .ui-page-header-heading {
550
+ flex: 1 1 auto;
551
+ min-inline-size: 0;
552
+ }
553
+
554
+ .ui-page-header[data-layout="responsive-inline"] .ui-page-header-extra {
555
+ flex: 0 0 var(--page-header-extra-measure);
556
+ inline-size: var(--page-header-extra-measure);
557
+ max-inline-size: 100%;
558
+ justify-content: flex-end;
559
+ }
560
+ }
561
+
438
562
  @media (min-width: 640px) {
439
563
  .ui-page-header-row {
440
564
  flex-direction: row;
@@ -550,7 +674,8 @@
550
674
  .ui-empty-state-description {
551
675
  font-size: var(--font-size-base);
552
676
  color: hsl(var(--muted-foreground));
553
- max-width: 28rem;
677
+ /* Measure knob (rule #45) — default 28rem, retunable per service/locale. */
678
+ max-width: var(--empty-state-description-max-width);
554
679
  }
555
680
 
556
681
  .ui-empty-state-icon {
@@ -755,4 +880,245 @@
755
880
  color: hsl(var(--muted-foreground));
756
881
  font-size: var(--filter-label-font-size);
757
882
  }
883
+
884
+ /* Toolbar/FilterBar `overflow="scroll"` (#216) — one bounded row that scrolls INLINE instead of
885
+ * wrapping onto extra rows. Below 640px the strip still stacks (the `wrap` base rules above own
886
+ * that): a 390px viewport must never hide a filter behind a horizontal scroll it cannot see.
887
+ * `overflow-x` matches the rest of the package's scroll regions (.ui-data-table-scroll, tabs
888
+ * list) and the browser already mirrors its scroll origin under `dir="rtl"`; the INSET/margin
889
+ * properties below stay logical. No `tabindex` is needed — every group holds a focusable
890
+ * control, so the region is keyboard-reachable (axe scrollable-region-focusable / WCAG 2.1.1). */
891
+ @media (min-width: 640px) {
892
+ .ui-toolbar[data-overflow="scroll"] {
893
+ flex-wrap: nowrap;
894
+ overflow-x: auto;
895
+ overscroll-behavior-x: contain;
896
+ scrollbar-gutter: stable;
897
+ padding-block-end: var(--filter-bar-scroll-padding-y);
898
+ }
899
+
900
+ .ui-toolbar[data-overflow="scroll"] > .ui-toolbar-group {
901
+ flex: 0 0 auto;
902
+ }
903
+
904
+ /* The clear action stays parked at the inline end of the VIEWPORT of the scroll region, not at
905
+ * the end of its scroll width — `margin-inline-start: auto` would push it past the fold and
906
+ * make "clear all" unreachable without scrolling. */
907
+ .ui-toolbar[data-overflow="scroll"] > .ui-toolbar-clear {
908
+ position: sticky;
909
+ inset-inline-end: 0;
910
+ margin-inline-start: auto;
911
+ flex: 0 0 auto;
912
+ /* Above the scrolling groups, and opaque, so a filter never slides underneath it. */
913
+ z-index: 1;
914
+ background: hsl(var(--filter-bar-sticky-background, var(--background)));
915
+ }
916
+ }
917
+
918
+ /* ── LegalDocumentShell (#222) ─────────────────────────────────────────────────
919
+ * The long-form legal/policy document surface: a readable, measure-capped document
920
+ * column with a STICKY table-of-contents rail beside it. Every value reads a
921
+ * `--legal-document-*` component token, so a service re-tunes the measure, the rail
922
+ * and the section rhythm from theme.css with zero consumer `.legal-*` CSS.
923
+ *
924
+ * It OWNS its query container (`container: legal-document / inline-size`, the SplitPane
925
+ * precedent — gh#165), so the one-column ⇄ two-column decision comes from the SHELL's
926
+ * own available width, not the viewport: dropped into a narrow pane on a 1440px screen
927
+ * it correctly stays single-column.
928
+ *
929
+ * DOM order is header → rail (contents) → sections → footer, which is exactly the
930
+ * mobile reading order; the desktop branch only re-places those boxes into a grid.
931
+ */
932
+ .ui-legal-document-scope {
933
+ container: legal-document / inline-size;
934
+ }
935
+
936
+ .ui-legal-document {
937
+ display: grid;
938
+ grid-template-columns: minmax(0, 1fr);
939
+ row-gap: var(--legal-document-section-gap);
940
+ align-items: start;
941
+ min-width: 0;
942
+ }
943
+
944
+ .ui-legal-document-header {
945
+ display: flex;
946
+ flex-direction: column;
947
+ gap: var(--legal-document-header-gap);
948
+ min-width: 0;
949
+ max-width: var(--legal-document-measure-max-width);
950
+ border-block-end: var(--legal-document-header-border);
951
+ }
952
+
953
+ .ui-legal-document-meta {
954
+ display: flex;
955
+ flex-wrap: wrap;
956
+ align-items: baseline;
957
+ gap: var(--legal-document-meta-gap);
958
+ font-size: var(--legal-document-meta-font-size);
959
+ color: hsl(var(--legal-document-meta-foreground, var(--muted-foreground)));
960
+ }
961
+
962
+ /* Long-form prose wrapping that holds in JA (kinsoku via `line-break: strict`), in EN
963
+ * (long compounds / URLs via `overflow-wrap: break-word`) and in VI (diacritic-heavy
964
+ * words must NOT be broken mid-word — `word-break: normal` keeps that true). */
965
+ .ui-legal-document-summary,
966
+ .ui-legal-document-section-body {
967
+ min-width: 0;
968
+ line-height: var(--legal-document-body-line-height);
969
+ overflow-wrap: break-word;
970
+ word-break: normal;
971
+ line-break: strict;
972
+ }
973
+
974
+ .ui-legal-document-summary {
975
+ color: hsl(var(--legal-document-summary-foreground, var(--muted-foreground)));
976
+ }
977
+
978
+ .ui-legal-document-rail {
979
+ display: flex;
980
+ flex-direction: column;
981
+ gap: var(--legal-document-nav-gap);
982
+ min-width: 0;
983
+ }
984
+
985
+ .ui-legal-document-toc {
986
+ display: flex;
987
+ flex-direction: column;
988
+ gap: var(--legal-document-toc-gap);
989
+ min-width: 0;
990
+ min-height: 0;
991
+ border-inline-end: var(--legal-document-toc-border);
992
+ }
993
+
994
+ .ui-legal-document-toc-title {
995
+ font-size: var(--legal-document-toc-title-font-size);
996
+ color: hsl(var(--legal-document-toc-foreground, var(--muted-foreground)));
997
+ }
998
+
999
+ .ui-legal-document-toc-list {
1000
+ display: flex;
1001
+ flex-direction: column;
1002
+ gap: var(--legal-document-toc-gap);
1003
+ margin: 0;
1004
+ padding: 0;
1005
+ list-style: none;
1006
+ min-width: 0;
1007
+ min-height: 0;
1008
+ }
1009
+
1010
+ /* REAL anchors — `href="#<section id>"` — so the contents list is keyboard-reachable,
1011
+ * middle-clickable and deep-linkable even before JS boots. */
1012
+ .ui-legal-document-toc-link {
1013
+ display: block;
1014
+ padding: var(--legal-document-toc-item-padding);
1015
+ border-radius: var(--legal-document-toc-item-radius);
1016
+ border-inline-start: var(--legal-document-toc-marker-width) solid transparent;
1017
+ font-size: var(--legal-document-toc-font-size);
1018
+ line-height: 1.6;
1019
+ color: hsl(var(--legal-document-toc-foreground, var(--muted-foreground)));
1020
+ text-decoration: none;
1021
+ overflow-wrap: break-word;
1022
+ transition-property: color, background-color, border-color;
1023
+ transition-duration: var(--duration-fast);
1024
+ }
1025
+
1026
+ .ui-legal-document-toc-link:hover {
1027
+ color: hsl(var(--legal-document-toc-active-foreground, var(--foreground)));
1028
+ background: hsl(var(--legal-document-toc-active-background, var(--accent)) / 0.6);
1029
+ }
1030
+
1031
+ .ui-legal-document-toc-link:focus-visible {
1032
+ outline: none;
1033
+ box-shadow: 0 0 0 var(--focus-ring-width) hsl(var(--focus-ring-color, var(--ring)));
1034
+ }
1035
+
1036
+ /* Active entry: colour + a leading marker + a heavier weight, so the state survives a
1037
+ * monochrome / high-contrast rendering (never colour-only, WCAG 1.4.1). The anchor also
1038
+ * carries `aria-current="location"`. */
1039
+ .ui-legal-document-toc-link[data-active] {
1040
+ color: hsl(var(--legal-document-toc-active-foreground, var(--foreground)));
1041
+ background: hsl(var(--legal-document-toc-active-background, var(--accent)));
1042
+ border-inline-start-color: hsl(var(--legal-document-toc-marker-color, var(--primary)));
1043
+ font-weight: var(--font-weight-medium);
1044
+ }
1045
+
1046
+ .ui-legal-document-content {
1047
+ display: flex;
1048
+ flex-direction: column;
1049
+ gap: var(--legal-document-section-gap);
1050
+ min-width: 0;
1051
+ max-width: var(--legal-document-measure-max-width);
1052
+ }
1053
+
1054
+ /* `scroll-margin-block-start` (logical, so it survives vertical writing modes) is what
1055
+ * makes a hash jump AND a programmatic `scrollIntoView` land BELOW sticky chrome — no
1056
+ * JS offset arithmetic, no consumer CSS. */
1057
+ .ui-legal-document-section {
1058
+ display: flex;
1059
+ flex-direction: column;
1060
+ gap: var(--legal-document-section-title-gap);
1061
+ min-width: 0;
1062
+ scroll-margin-block-start: var(--legal-document-scroll-offset);
1063
+ }
1064
+
1065
+ /* The section is `tabIndex={-1}` and receives focus when its contents anchor is
1066
+ * activated, so the focus ring must be VISIBLE (WCAG 2.4.7 / 2.4.11). */
1067
+ .ui-legal-document-section:focus-visible {
1068
+ outline: var(--focus-ring-width) solid hsl(var(--focus-ring-color, var(--ring)));
1069
+ outline-offset: var(--space-1);
1070
+ }
1071
+
1072
+ .ui-legal-document-footer {
1073
+ display: flex;
1074
+ flex-wrap: wrap;
1075
+ align-items: center;
1076
+ gap: var(--legal-document-footer-gap);
1077
+ min-width: 0;
1078
+ max-width: var(--legal-document-measure-max-width);
1079
+ border-block-start: var(--legal-document-footer-border);
1080
+ }
1081
+
1082
+ /* Two-column mode (shell ≥56rem — a 1024px and a 1440px viewport both qualify): the
1083
+ * rail is PINNED beside a measure-capped document column and spans the header/body/
1084
+ * footer rows, so it stays in view for the whole scroll. Logical grid placement means
1085
+ * column 1 is the START side, i.e. it mirrors correctly under `dir="rtl"`.
1086
+ *
1087
+ * Below 56rem (the 390×844 case) NOTHING here applies: the shell stays a single column
1088
+ * and the rail is a STATIC compact contents block between the document header and the
1089
+ * first section — deliberately not sticky and not height-capped, so a phone never loses
1090
+ * vertical space to pinned chrome. */
1091
+ @container legal-document (min-width: 56rem) {
1092
+ .ui-legal-document {
1093
+ grid-template-columns:
1094
+ var(--legal-document-toc-width)
1095
+ minmax(0, var(--legal-document-measure-max-width));
1096
+ column-gap: var(--legal-document-column-gap);
1097
+ }
1098
+
1099
+ .ui-legal-document-header {
1100
+ grid-column: 2;
1101
+ grid-row: 1;
1102
+ }
1103
+
1104
+ .ui-legal-document-rail {
1105
+ grid-column: 1;
1106
+ grid-row: 1 / span 3;
1107
+ position: sticky;
1108
+ inset-block-start: var(--legal-document-toc-inset-block-start);
1109
+ align-self: start;
1110
+ max-height: var(--legal-document-toc-max-height);
1111
+ overflow-y: auto;
1112
+ }
1113
+
1114
+ .ui-legal-document-content {
1115
+ grid-column: 2;
1116
+ grid-row: 2;
1117
+ }
1118
+
1119
+ .ui-legal-document-footer {
1120
+ grid-column: 2;
1121
+ grid-row: 3;
1122
+ }
1123
+ }
758
1124
  }
@@ -38,4 +38,66 @@
38
38
  height: var(--logo-size-lg);
39
39
  font-size: var(--logo-font-size-lg);
40
40
  }
41
+
42
+ /* Identity fills — role defaults live HERE (call site), not on :root, so a scoped `.dark` /
43
+ * `[data-tenant]` override of --success re-resolves (docs/TOKENS.md · role-mirror knobs). */
44
+ .ui-logo[data-tone="success"] {
45
+ background: hsl(var(--logo-success-background, var(--success)));
46
+ color: hsl(var(--logo-success-foreground, var(--success-foreground)));
47
+ }
48
+
49
+ .ui-logo[data-mark="godx"] {
50
+ width: var(--logo-godx-size);
51
+ height: var(--logo-godx-size);
52
+ border-radius: 0;
53
+ background: transparent;
54
+ color: hsl(var(--logo-godx-color, var(--success)));
55
+ }
56
+
57
+ .ui-logo[data-mark="godx"] [data-slot="logo-artwork"] {
58
+ display: block;
59
+ width: 100%;
60
+ height: 100%;
61
+ }
62
+
63
+ /*
64
+ * LOCKUP — `<Logo wordmark="…" />`: the mark plus the readable product name as ONE element, so a
65
+ * shell header / auth brand bar never hand-rolls `inline-flex items-center gap-2` around a Logo
66
+ * and a `<Text>` (rule #46). Every knob is a token (rule #45); the wordmark colour defaults to
67
+ * the IDENTITY role and never reads --primary, so re-theming the action colour cannot recolour
68
+ * the brand — no consumer page CSS is required for the logo colour (gh#214).
69
+ */
70
+ .ui-logo-lockup {
71
+ display: inline-flex;
72
+ align-items: center;
73
+ gap: var(--logo-wordmark-gap);
74
+ }
75
+
76
+ .ui-logo-lockup .ui-logo-wordmark {
77
+ font-family: var(--logo-wordmark-font-family, var(--font-family-display));
78
+ font-size: var(--logo-wordmark-font-size-md);
79
+ font-weight: var(--logo-wordmark-font-weight);
80
+ letter-spacing: var(--logo-wordmark-letter-spacing);
81
+ line-height: 1;
82
+ color: hsl(var(--logo-wordmark-color, var(--foreground)));
83
+ white-space: nowrap;
84
+ }
85
+
86
+ .ui-logo-lockup[data-size="xs"] .ui-logo-wordmark {
87
+ font-size: var(--logo-wordmark-font-size-xs);
88
+ }
89
+
90
+ .ui-logo-lockup[data-size="sm"] .ui-logo-wordmark {
91
+ font-size: var(--logo-wordmark-font-size-sm);
92
+ }
93
+
94
+ .ui-logo-lockup[data-size="lg"] .ui-logo-wordmark {
95
+ font-size: var(--logo-wordmark-font-size-lg);
96
+ }
97
+
98
+ /* Canonical brand-green GoDX wordmark — the identity role, independent of --primary. */
99
+ .ui-logo-lockup[data-mark="godx"] .ui-logo-wordmark,
100
+ .ui-logo-lockup[data-tone="success"] .ui-logo-wordmark {
101
+ color: hsl(var(--logo-wordmark-color, var(--logo-godx-color, var(--success))));
102
+ }
41
103
  }
@@ -1,4 +1,47 @@
1
1
  @layer components {
2
+ .ui-app-setting-picker-inline {
3
+ width: auto;
4
+ min-height: 0;
5
+ height: auto;
6
+ border: 0;
7
+ border-radius: 0;
8
+ padding: 0;
9
+ background: transparent;
10
+ box-shadow: none;
11
+ color: inherit;
12
+ font: inherit;
13
+ }
14
+
15
+ [data-slot="auth-legal-footer"] .ui-app-setting-picker-inline {
16
+ width: auto;
17
+ min-height: 0;
18
+ height: auto;
19
+ border-width: 0;
20
+ border-radius: 0;
21
+ padding: 0;
22
+ background: transparent;
23
+ box-shadow: none;
24
+ }
25
+
26
+ .ui-app-setting-picker-inline:hover,
27
+ .ui-app-setting-picker-inline[data-state="open"] {
28
+ background: transparent;
29
+ color: hsl(var(--foreground));
30
+ text-decoration: underline;
31
+ }
32
+
33
+ /*
34
+ * AppSettingPicker `compact` (gh#217) — a small, content-hugging LABELLED trigger for auth /
35
+ * legal footers: it keeps the icon, the readable value and the real control chrome (so it is
36
+ * obviously a switcher), but re-tiers the box to --control-height-sm and drops the picker's owned
37
+ * per-kind width. Every value is a token; the height comes from the official control tier, never
38
+ * a literal or an ad-hoc calc.
39
+ */
40
+ .ui-app-setting-picker-compact {
41
+ --control-height: var(--app-setting-picker-compact-control-height);
42
+ --control-padding-x: var(--app-setting-picker-compact-padding-x);
43
+ --control-font-size: var(--app-setting-picker-compact-font-size);
44
+ }
2
45
  [data-slot="tabs-list"] {
3
46
  max-inline-size: var(--tabs-list-max-inline-size);
4
47
  overflow-x: var(--tabs-list-overflow);