lostcoords-ui 0.4.1 → 0.6.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 (117) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +364 -96
  3. data/app/assets/tailwind/lostcoords/ui/avatar.css +6 -6
  4. data/app/assets/tailwind/lostcoords/ui/base.css +45 -0
  5. data/app/assets/tailwind/lostcoords/ui/card.css +23 -22
  6. data/app/assets/tailwind/lostcoords/ui/choice.css +39 -0
  7. data/app/assets/tailwind/lostcoords/ui/code-block.css +10 -0
  8. data/app/assets/tailwind/lostcoords/ui/{disclosure.css → collapsible.css} +6 -6
  9. data/app/assets/tailwind/lostcoords/ui/components/code-block.css +5 -0
  10. data/app/assets/tailwind/lostcoords/ui/components/collapsible.css +5 -0
  11. data/app/assets/tailwind/lostcoords/ui/components/dialog.css +9 -0
  12. data/app/assets/tailwind/lostcoords/ui/components/emoji-picker.css +5 -0
  13. data/app/assets/tailwind/lostcoords/ui/components/menu.css +5 -0
  14. data/app/assets/tailwind/lostcoords/ui/components/prose.css +3 -0
  15. data/app/assets/tailwind/lostcoords/ui/components/table.css +3 -0
  16. data/app/assets/tailwind/lostcoords/ui/components/toast.css +5 -0
  17. data/app/assets/tailwind/lostcoords/ui/components/tooltip.css +4 -0
  18. data/app/assets/tailwind/lostcoords/ui/components.css +12 -0
  19. data/app/assets/tailwind/lostcoords/ui/copy-button.css +9 -9
  20. data/app/assets/tailwind/lostcoords/ui/core.css +21 -0
  21. data/app/assets/tailwind/lostcoords/ui/dialog.css +17 -17
  22. data/app/assets/tailwind/lostcoords/ui/emoji-picker.css +86 -88
  23. data/app/assets/tailwind/lostcoords/ui/field.css +2 -2
  24. data/app/assets/tailwind/lostcoords/ui/glass.css +9 -8
  25. data/app/assets/tailwind/lostcoords/ui/layout.css +2 -2
  26. data/app/assets/tailwind/lostcoords/ui/popover.css +14 -14
  27. data/app/assets/tailwind/lostcoords/ui/prose.css +66 -0
  28. data/app/assets/tailwind/lostcoords/ui/sheet.css +62 -63
  29. data/app/assets/tailwind/lostcoords/ui/squircle.css +7 -27
  30. data/app/assets/tailwind/lostcoords/ui/table.css +51 -0
  31. data/app/assets/tailwind/lostcoords/ui/theme.css +209 -225
  32. data/app/assets/tailwind/lostcoords/ui/toast.css +23 -0
  33. data/app/assets/tailwind/lostcoords/ui/tooltip.css +51 -0
  34. data/app/assets/tailwind/lostcoords/ui/typography.css +31 -61
  35. data/app/assets/tailwind/lostcoords/ui/ui.css +2 -21
  36. data/app/assets/tailwind/lostcoords/ui/utilities.css +14 -22
  37. data/app/helpers/lostcoords/ui/badge_helper.rb +67 -110
  38. data/app/helpers/lostcoords/ui/button_helper.rb +44 -35
  39. data/app/helpers/lostcoords/ui/card_helper.rb +9 -52
  40. data/app/helpers/lostcoords/ui/choice_helper.rb +87 -0
  41. data/app/helpers/lostcoords/ui/code_block_helper.rb +22 -0
  42. data/app/helpers/lostcoords/ui/collapsible_helper.rb +9 -0
  43. data/app/helpers/lostcoords/ui/copy_button_helper.rb +7 -7
  44. data/app/helpers/lostcoords/ui/description_list_helper.rb +12 -0
  45. data/app/helpers/lostcoords/ui/dialog_helper.rb +15 -17
  46. data/app/helpers/lostcoords/ui/emoji_picker_helper.rb +6 -0
  47. data/app/helpers/lostcoords/ui/feedback_helper.rb +25 -6
  48. data/app/helpers/lostcoords/ui/form_helper.rb +36 -59
  49. data/app/helpers/lostcoords/ui/glass_helper.rb +7 -7
  50. data/app/helpers/lostcoords/ui/layout_helper.rb +13 -11
  51. data/app/helpers/lostcoords/ui/menu_helper.rb +45 -10
  52. data/app/helpers/lostcoords/ui/navigation_helper.rb +144 -0
  53. data/app/helpers/lostcoords/ui/primitives_helper.rb +39 -61
  54. data/app/helpers/lostcoords/ui/reading_helper.rb +2 -2
  55. data/app/helpers/lostcoords/ui/render_attributes_helper.rb +17 -1
  56. data/app/helpers/lostcoords/ui/sheet_helper.rb +7 -45
  57. data/app/helpers/lostcoords/ui/structures_helper.rb +26 -125
  58. data/app/helpers/lostcoords/ui/table_helper.rb +11 -0
  59. data/app/helpers/lostcoords/ui/tailwind_helper.rb +13 -4
  60. data/app/helpers/lostcoords/ui/toast_helper.rb +22 -0
  61. data/app/helpers/lostcoords/ui/tooltip_helper.rb +17 -0
  62. data/app/helpers/lostcoords/ui/typography_helper.rb +41 -11
  63. data/app/javascript/lostcoords/ui/controllers/lc_code_block_controller.js +23 -0
  64. data/app/javascript/lostcoords/ui/controllers/{disclosure_controller.js → lc_collapsible_controller.js} +4 -4
  65. data/app/javascript/lostcoords/ui/controllers/{confirm_dialog_controller.js → lc_confirm_dialog_controller.js} +4 -4
  66. data/app/javascript/lostcoords/ui/controllers/{modal_controller.js → lc_dialog_controller.js} +1 -1
  67. data/app/javascript/lostcoords/ui/controllers/{emoji_picker_controller.js → lc_emoji_picker_controller.js} +38 -10
  68. data/app/javascript/lostcoords/ui/controllers/lc_kbd_controller.js +12 -0
  69. data/app/javascript/lostcoords/ui/controllers/{row_menu_controller.js → lc_menu_controller.js} +25 -3
  70. data/app/javascript/lostcoords/ui/controllers/{ui_popover_controller.js → lc_popover_controller.js} +1 -1
  71. data/app/javascript/lostcoords/ui/controllers/{ui_selection_controller.js → lc_selection_controller.js} +7 -15
  72. data/app/javascript/lostcoords/ui/controllers/{sheet_controller.js → lc_sheet_controller.js} +17 -17
  73. data/app/javascript/lostcoords/ui/controllers/lc_shortcut_controller.js +45 -0
  74. data/app/javascript/lostcoords/ui/controllers/lc_tabs_controller.js +42 -0
  75. data/app/javascript/lostcoords/ui/controllers/{toast_controller.js → lc_toast_controller.js} +2 -2
  76. data/app/javascript/lostcoords/ui/controllers/lc_toast_region_controller.js +62 -0
  77. data/app/javascript/lostcoords/ui/controllers/lc_tooltip_controller.js +54 -0
  78. data/app/javascript/lostcoords/ui/helpers/catalog_picker.js +77 -25
  79. data/app/javascript/lostcoords/ui/helpers/context_dialog.js +6 -6
  80. data/app/javascript/lostcoords/ui/helpers/dialog.js +2 -2
  81. data/app/javascript/lostcoords/ui/helpers/dismiss_stack.js +30 -7
  82. data/app/javascript/lostcoords/ui/helpers/floating_panel.js +7 -8
  83. data/app/javascript/lostcoords/ui/helpers/menu_keys.js +19 -18
  84. data/app/javascript/lostcoords/ui/helpers/roving_focus.js +175 -0
  85. data/app/javascript/lostcoords/ui/helpers/sheet_motion.js +4 -4
  86. data/app/javascript/lostcoords/ui/helpers/shortcuts.js +162 -0
  87. data/app/javascript/lostcoords/ui/helpers/top_layer.js +48 -10
  88. data/app/javascript/lostcoords/ui/index.js +57 -0
  89. data/app/views/lostcoords/ui/components/_emoji_picker.html.erb +46 -46
  90. data/app/views/lostcoords/ui/components/_emoji_picker_grid.html.erb +3 -3
  91. data/app/views/lostcoords/ui/components/_lc_code_block.html.erb +18 -0
  92. data/app/views/lostcoords/ui/components/_lc_collapsible.html.erb +5 -0
  93. data/app/views/lostcoords/ui/components/_lc_dialog.html.erb +11 -8
  94. data/app/views/lostcoords/ui/components/_lc_floating_panel.html.erb +8 -0
  95. data/config/importmap.rb +10 -2
  96. data/config/locales/en.yml +23 -0
  97. data/config/locales/es.yml +23 -0
  98. data/config/locales/ja.yml +23 -0
  99. data/lib/lostcoords/ui/action_view.rb +6 -3
  100. data/lib/lostcoords/ui/builders.rb +2 -2
  101. data/lib/lostcoords/ui/form_builder.rb +126 -0
  102. data/lib/lostcoords/ui/parts.rb +154 -0
  103. data/lib/lostcoords/ui/stylesheets.rb +25 -0
  104. data/lib/lostcoords/ui/tokens.rb +24 -1
  105. data/lib/lostcoords/ui/version.rb +1 -1
  106. data/lib/tasks/lostcoords_ui.rake +11 -0
  107. metadata +54 -21
  108. data/app/assets/tailwind/lostcoords/ui/reading.css +0 -60
  109. data/app/views/lostcoords/ui/components/_form_alert.html.erb +0 -4
  110. data/app/views/lostcoords/ui/components/_lc_disclosure.html.erb +0 -5
  111. data/app/views/lostcoords/ui/components/_skeleton.html.erb +0 -4
  112. data/app/views/lostcoords/ui/components/_spinner.html.erb +0 -10
  113. data/app/views/lostcoords/ui/components/_ui_floating_panel.html.erb +0 -8
  114. data/config/helper_renames.json +0 -113
  115. data/script/migrate_helpers.rb +0 -136
  116. /data/app/javascript/lostcoords/ui/controllers/{ui_copy_controller.js → lc_copy_controller.js} +0 -0
  117. /data/app/javascript/lostcoords/ui/controllers/{ui_toggle_controller.js → lc_toggle_controller.js} +0 -0
@@ -1,49 +1,47 @@
1
1
  @layer components {
2
- .emoji-picker {
2
+ .lc-emoji-picker {
3
3
  display: flex;
4
4
  gap: 6px;
5
5
  flex-wrap: wrap;
6
6
  align-items: center;
7
7
 
8
- /* Sit above the mobile app nav (z-100 + 25) so the sheet covers it. */
9
- --emoji-sheet-z: calc(100 + 30);
10
- --emoji-cell: 2.25rem;
8
+ --lc-emoji-cell: 2.25rem;
11
9
  }
12
10
 
13
11
  /* ---- Trigger + quick choices -------------------------------------- */
14
- .emoji-picker__choice,
15
- .emoji-picker__trigger {
12
+ .lc-emoji-picker__choice,
13
+ .lc-emoji-picker__trigger {
16
14
  display: inline-grid;
17
15
  place-items: center;
18
16
  inline-size: 44px;
19
17
  block-size: 44px;
20
18
  border-radius: var(--radius-2xl, 1rem);
21
- border: 1px solid var(--hairline);
22
- background: var(--paper);
23
- color: var(--ink);
19
+ border: 1px solid var(--border);
20
+ background: var(--background);
21
+ color: var(--foreground);
24
22
  cursor: pointer;
25
23
  font-size: var(--text-xl, 1.25rem);
26
24
  line-height: 1;
27
25
  transition:
28
26
  border-color 0.15s,
29
27
  background 0.15s,
30
- transform 0.15s var(--ease-out),
28
+ transform 0.15s var(--lc-ease-out),
31
29
  box-shadow 0.15s;
32
30
 
33
- &:hover { border-color: var(--ink-3); }
31
+ &:hover { border-color: var(--muted-foreground); }
34
32
 
35
33
  &[aria-current="true"] {
36
- border-color: var(--sky);
37
- background: var(--sky-soft);
34
+ border-color: var(--primary);
35
+ background: color-mix(in oklab, var(--primary) 12%, var(--background));
38
36
  }
39
37
  }
40
38
 
41
- .emoji-picker__trigger {
42
- color: var(--ink-3);
39
+ .lc-emoji-picker__trigger {
40
+ color: var(--muted-foreground);
43
41
  font-weight: var(--font-weight-normal, 400);
44
42
  }
45
43
 
46
- .emoji-picker__more {
44
+ .lc-emoji-picker__more {
47
45
  position: relative;
48
46
  display: inline-flex;
49
47
  }
@@ -58,11 +56,11 @@
58
56
  dims everything beneath it — including the app nav. The allow-discrete
59
57
  transitions keep it in the top layer while it eases out, so it fades
60
58
  both ways instead of snapping away on close. */
61
- .emoji-picker__popover::backdrop {
62
- background: var(--scrim);
59
+ .lc-emoji-picker__popover::backdrop {
60
+ background: var(--lc-scrim);
63
61
  opacity: 0;
64
62
  transition:
65
- opacity 220ms var(--ease-out),
63
+ opacity 220ms var(--lc-ease-out),
66
64
  display 220ms allow-discrete,
67
65
  overlay 220ms allow-discrete;
68
66
 
@@ -72,7 +70,7 @@
72
70
  /* Only the phone sheet dims the page; the desktop popover rides the top
73
71
  layer too (so nothing paints over it) but floats over an undimmed page. */
74
72
  @media (width < 40rem) {
75
- .emoji-picker__popover:popover-open::backdrop {
73
+ .lc-emoji-picker__popover:popover-open::backdrop {
76
74
  opacity: 1;
77
75
 
78
76
  @starting-style { opacity: 0; }
@@ -82,22 +80,22 @@
82
80
  /* Phones get a solid sheet; on desktop the host paints the popover as a
83
81
  floating surface (glass in wariwari), so the gem leaves it unpainted. */
84
82
  @media (width < 40rem) {
85
- .emoji-picker__popover {
86
- border: 1px solid var(--glass-edge, var(--hairline));
83
+ .lc-emoji-picker__popover {
84
+ border: 1px solid var(--lc-glass-edge, var(--border));
87
85
  border-block-end: 0;
88
- background: var(--paper);
89
- box-shadow: 0 -12px 40px -12px color-mix(in oklab, var(--ink) 45%, transparent);
86
+ background: var(--background);
87
+ box-shadow: 0 -12px 40px -12px color-mix(in oklab, var(--foreground) 45%, transparent);
90
88
  }
91
89
  }
92
90
 
93
- .emoji-picker__popover {
91
+ .lc-emoji-picker__popover {
94
92
  position: fixed;
95
93
  inset: auto 0 0;
96
94
  margin: 0;
97
- z-index: var(--emoji-sheet-z);
95
+ z-index: var(--lc-z-overlay);
98
96
  display: none;
99
97
  max-inline-size: none;
100
- color: var(--ink);
98
+ color: var(--foreground);
101
99
  flex-direction: column;
102
100
  inline-size: 100%;
103
101
  max-block-size: min(72svh, 34rem);
@@ -107,7 +105,7 @@
107
105
  overscroll-behavior: contain;
108
106
  transform: translateY(100%);
109
107
  transition:
110
- transform 260ms var(--ease-out-expo),
108
+ transform 260ms var(--lc-ease-out-expo),
111
109
  display 260ms allow-discrete,
112
110
  overlay 260ms allow-discrete;
113
111
 
@@ -125,7 +123,7 @@
125
123
 
126
124
  /* Fallback for browsers without the Popover API: class-driven display. */
127
125
  @supports not selector(:popover-open) {
128
- .emoji-picker__popover[data-open] {
126
+ .lc-emoji-picker__popover[data-open] {
129
127
  display: flex;
130
128
  transform: translateY(0);
131
129
  }
@@ -135,7 +133,7 @@
135
133
  is large; the host's sheet draws the pill.
136
134
  touch-action: none lets a vertical drag pull the sheet down rather than
137
135
  scroll. Drag-to-dismiss is wired by the sheet controller. */
138
- .emoji-picker__handle {
136
+ .lc-emoji-picker__handle {
139
137
  flex: 0 0 auto;
140
138
  display: grid;
141
139
  place-items: center;
@@ -145,10 +143,10 @@
145
143
  cursor: grab;
146
144
  }
147
145
 
148
- .emoji-picker__handle:active { cursor: grabbing; }
146
+ .lc-emoji-picker__handle:active { cursor: grabbing; }
149
147
 
150
148
  /* ---- Search row --------------------------------------------------- */
151
- .emoji-picker__search {
149
+ .lc-emoji-picker__search {
152
150
  flex: 0 0 auto;
153
151
  display: flex;
154
152
  gap: 0.5rem;
@@ -156,35 +154,35 @@
156
154
  padding: 0.75rem 0.75rem 0.5rem;
157
155
  }
158
156
 
159
- .emoji-picker__search-field {
157
+ .lc-emoji-picker__search-field {
160
158
  position: relative;
161
159
  flex: 1 1 auto;
162
160
  display: flex;
163
161
  align-items: center;
164
162
  }
165
163
 
166
- .emoji-picker__search-icon {
164
+ .lc-emoji-picker__search-icon {
167
165
  position: absolute;
168
166
  inset-inline-start: 14px;
169
167
  inline-size: 18px;
170
168
  block-size: 18px;
171
- color: var(--ink-3);
169
+ color: var(--muted-foreground);
172
170
  pointer-events: none;
173
171
  }
174
172
 
175
- .emoji-picker__search-input {
173
+ .lc-emoji-picker__search-input {
176
174
  inline-size: 100%;
177
175
  min-block-size: 44px;
178
176
  padding-inline: 40px 1rem;
179
177
  border: 1px solid transparent;
180
178
  border-radius: 999px;
181
- background: var(--paper-2);
182
- color: var(--ink);
179
+ background: var(--muted);
180
+ color: var(--foreground);
183
181
  font: inherit;
184
182
  font-size: 1rem;
185
183
  transition: border-color 150ms ease-out, box-shadow 150ms ease-out;
186
184
 
187
- &::placeholder { color: var(--ink-3); }
185
+ &::placeholder { color: var(--muted-foreground); }
188
186
  &::-webkit-search-cancel-button {
189
187
  /* stylelint-disable-next-line property-no-vendor-prefix -- WebKit search decoration */
190
188
  -webkit-appearance: none;
@@ -193,35 +191,35 @@
193
191
 
194
192
  &:focus {
195
193
  outline: none;
196
- box-shadow: var(--focus-halo, 0 0 0 2px color-mix(in oklab, var(--sky) 50%, transparent));
194
+ box-shadow: var(--focus-halo, 0 0 0 2px color-mix(in oklab, var(--primary) 50%, transparent));
197
195
  }
198
196
  }
199
197
 
200
198
  /* ---- Skin-tone control ------------------------------------------- */
201
- .emoji-picker__tones {
199
+ .lc-emoji-picker__tones {
202
200
  position: relative;
203
201
  flex: 0 0 auto;
204
202
  }
205
203
 
206
- .emoji-picker__tone-toggle {
204
+ .lc-emoji-picker__tone-toggle {
207
205
  display: inline-grid;
208
206
  place-items: center;
209
207
  inline-size: 44px;
210
208
  block-size: 44px;
211
209
  border: none;
212
210
  border-radius: 999px;
213
- background: var(--paper-2);
211
+ background: var(--muted);
214
212
  cursor: pointer;
215
213
  font-size: var(--text-lg, 1.125rem);
216
214
  line-height: 1;
217
215
  transition: background 150ms ease-out;
218
216
 
219
- &:hover { background: var(--paper-3); }
217
+ &:hover { background: var(--accent); }
220
218
 
221
- &[aria-expanded="true"] { background: var(--sky-soft); }
219
+ &[aria-expanded="true"] { background: color-mix(in oklab, var(--primary) 12%, var(--background)); }
222
220
  }
223
221
 
224
- .emoji-picker__tone-menu {
222
+ .lc-emoji-picker__tone-menu {
225
223
  position: absolute;
226
224
  inset-block-end: calc(100% + 6px);
227
225
  inset-inline-end: 0;
@@ -234,7 +232,7 @@
234
232
  &[hidden] { display: none; }
235
233
  }
236
234
 
237
- .emoji-picker__tone {
235
+ .lc-emoji-picker__tone {
238
236
  display: inline-grid;
239
237
  place-items: center;
240
238
  inline-size: 36px;
@@ -246,16 +244,16 @@
246
244
  font-size: var(--text-lg, 1.125rem);
247
245
  line-height: 1;
248
246
 
249
- &:hover { background: var(--paper-2); }
247
+ &:hover { background: var(--muted); }
250
248
 
251
- &[aria-current="true"] { background: var(--sky-soft); }
249
+ &[aria-current="true"] { background: color-mix(in oklab, var(--primary) 12%, var(--background)); }
252
250
  }
253
251
 
254
252
  /* ---- Category tab navigation ------------------------------------- */
255
253
 
256
254
  /* Category tabs: a segmented track, like the app's tab strips — a paper-2
257
255
  track with the active tab raised on a paper thumb. */
258
- .emoji-picker__tabs {
256
+ .lc-emoji-picker__tabs {
259
257
  flex: 0 0 auto;
260
258
  display: flex;
261
259
  gap: 2px;
@@ -264,12 +262,12 @@
264
262
  overflow-x: auto;
265
263
  scrollbar-width: none;
266
264
  border-radius: var(--radius-2xl, 1rem);
267
- background: var(--paper-2);
265
+ background: var(--muted);
268
266
 
269
267
  &::-webkit-scrollbar { display: none; }
270
268
  }
271
269
 
272
- .emoji-picker__tab {
270
+ .lc-emoji-picker__tab {
273
271
  flex: 0 0 auto;
274
272
  display: inline-grid;
275
273
  place-items: center;
@@ -288,13 +286,13 @@
288
286
 
289
287
  &[aria-current="true"] {
290
288
  opacity: 1;
291
- background: var(--paper);
292
- box-shadow: var(--shadow-sm);
289
+ background: var(--background);
290
+ box-shadow: var(--lc-shadow-sm);
293
291
  }
294
292
  }
295
293
 
296
294
  /* ---- Scrollable body --------------------------------------------- */
297
- .emoji-picker__body {
295
+ .lc-emoji-picker__body {
298
296
  position: relative;
299
297
  flex: 1 1 auto;
300
298
  overflow-y: auto;
@@ -309,15 +307,15 @@
309
307
  @media (prefers-reduced-motion: reduce) { scroll-behavior: auto; }
310
308
  }
311
309
 
312
- .emoji-picker__category + .emoji-picker__category,
313
- .emoji-picker__recent + .emoji-picker__catalog,
314
- .emoji-picker__results + * {
310
+ .lc-emoji-picker__category + .lc-emoji-picker__category,
311
+ .lc-emoji-picker__recent + .lc-emoji-picker__catalog,
312
+ .lc-emoji-picker__results + * {
315
313
  margin-block-start: 0.5rem;
316
314
  }
317
315
 
318
316
  /* Heads scroll with their section: the tab track already marks the current
319
317
  category, and a sticky band would sit as a stripe across the panel. */
320
- .emoji-picker__category-head {
318
+ .lc-emoji-picker__category-head {
321
319
  display: flex;
322
320
  align-items: baseline;
323
321
  justify-content: space-between;
@@ -328,19 +326,19 @@
328
326
  margin: 0;
329
327
  font-size: var(--text-sm, 0.875rem);
330
328
  font-weight: var(--font-weight-semibold, 600);
331
- color: var(--ink-2);
329
+ color: var(--muted-foreground);
332
330
  }
333
331
 
334
332
  .count {
335
- font-family: var(--font-body, system-ui);
333
+ font-family: var(--font-sans, system-ui);
336
334
  font-size: var(--text-xs, 0.75rem);
337
- color: var(--ink-3);
335
+ color: var(--muted-foreground);
338
336
  }
339
337
  }
340
338
 
341
- .emoji-picker__grid {
339
+ .lc-emoji-picker__grid {
342
340
  display: grid;
343
- grid-template-columns: repeat(auto-fill, minmax(var(--emoji-cell), 1fr));
341
+ grid-template-columns: repeat(auto-fill, minmax(var(--lc-emoji-cell), 1fr));
344
342
  gap: 2px;
345
343
  padding-block-start: 4px;
346
344
 
@@ -356,45 +354,45 @@
356
354
  font-size: var(--text-xl, 1.25rem);
357
355
  line-height: 1;
358
356
  padding: 0;
359
- transition: background 0.1s, transform 0.1s var(--ease-out);
357
+ transition: background 0.1s, transform 0.1s var(--lc-ease-out);
360
358
 
361
359
  &:hover,
362
360
  &:focus-visible {
363
- background: var(--paper-2);
361
+ background: var(--muted);
364
362
  transform: scale(1.12);
365
363
  outline: none;
366
364
  }
367
365
 
368
- &[aria-current="true"] { background: var(--sky-soft); }
366
+ &[aria-current="true"] { background: color-mix(in oklab, var(--primary) 12%, var(--background)); }
369
367
  }
370
368
  }
371
369
 
372
370
  /* Section that owns the sticky header needs the scroll anchor, not the grid */
373
- .emoji-picker__category,
374
- .emoji-picker__recent,
375
- .emoji-picker__results {
371
+ .lc-emoji-picker__category,
372
+ .lc-emoji-picker__recent,
373
+ .lc-emoji-picker__results {
376
374
  scroll-margin-block-start: 0;
377
375
  }
378
376
 
379
- .emoji-picker__empty {
377
+ .lc-emoji-picker__empty {
380
378
  margin: 0;
381
379
  padding: 1rem 0.5rem;
382
- color: var(--ink-3);
380
+ color: var(--muted-foreground);
383
381
  font-size: 1rem;
384
382
  text-align: center;
385
383
  }
386
384
 
387
385
  /* ---- Search mode: swap catalog for a single ranked result list ---- */
388
- .emoji-picker__popover:not([data-searching]) .emoji-picker__results { display: none; }
386
+ .lc-emoji-picker__popover:not([data-searching]) .lc-emoji-picker__results { display: none; }
389
387
 
390
- .emoji-picker__popover[data-searching] {
391
- .emoji-picker__tabs,
392
- .emoji-picker__recent,
393
- .emoji-picker__catalog { display: none; }
388
+ .lc-emoji-picker__popover[data-searching] {
389
+ .lc-emoji-picker__tabs,
390
+ .lc-emoji-picker__recent,
391
+ .lc-emoji-picker__catalog { display: none; }
394
392
  }
395
393
 
396
394
  /* ---- Preview footer ---------------------------------------------- */
397
- .emoji-picker__preview {
395
+ .lc-emoji-picker__preview {
398
396
  flex: 0 0 auto;
399
397
  display: flex;
400
398
  gap: 0.5rem;
@@ -403,15 +401,15 @@
403
401
  padding: 0.25rem 1rem 0.75rem;
404
402
  }
405
403
 
406
- .emoji-picker__preview-emoji {
404
+ .lc-emoji-picker__preview-emoji {
407
405
  font-size: 1.5rem;
408
406
  line-height: 1;
409
407
  }
410
408
 
411
- .emoji-picker__preview-name {
409
+ .lc-emoji-picker__preview-name {
412
410
  overflow: hidden;
413
411
  font-size: 1rem;
414
- color: var(--ink-3);
412
+ color: var(--muted-foreground);
415
413
  text-overflow: ellipsis;
416
414
  white-space: nowrap;
417
415
  }
@@ -422,7 +420,7 @@
422
420
 
423
421
  /* ==================================================================== */
424
422
  @media (width >= 40rem) {
425
- .emoji-picker__popover {
423
+ .lc-emoji-picker__popover {
426
424
  position: absolute;
427
425
  inset-block: calc(100% + 8px) auto;
428
426
  inset-inline: 0 auto;
@@ -433,8 +431,8 @@
433
431
  transform: translateY(-4px) scale(0.98);
434
432
  opacity: 0;
435
433
  transition:
436
- opacity 160ms var(--ease-out-expo),
437
- transform 160ms var(--ease-out-expo),
434
+ opacity 160ms var(--lc-ease-out-expo),
435
+ transform 160ms var(--lc-ease-out-expo),
438
436
  display 160ms allow-discrete,
439
437
  overlay 160ms allow-discrete;
440
438
 
@@ -452,11 +450,11 @@
452
450
  @media (prefers-reduced-motion: reduce) { transition: none; }
453
451
  }
454
452
 
455
- .emoji-picker__handle { display: none; }
453
+ .lc-emoji-picker__handle { display: none; }
456
454
 
457
- main:has(.emoji-picker__popover[data-open]),
458
- .emoji-picker:has(.emoji-picker__popover[data-open]) {
459
- z-index: 60;
455
+ main:has(.lc-emoji-picker__popover[data-open]),
456
+ .lc-emoji-picker:has(.lc-emoji-picker__popover[data-open]) {
457
+ z-index: var(--lc-z-overlay);
460
458
  }
461
459
  }
462
460
  }
@@ -1,6 +1,6 @@
1
1
  @layer components {
2
2
  /* A legend sits outside the fieldset's internal grid. */
3
- :where(fieldset.ui-field > legend) {
4
- margin-block-end: var(--ui-space-related, 0.5rem);
3
+ :where(fieldset.lc-field > legend) {
4
+ margin-block-end: var(--spacing-related);
5
5
  }
6
6
  }
@@ -1,17 +1,18 @@
1
1
  /* Glass — the floating control layer, used the way iOS uses it: only on
2
2
  chrome that floats over content (the nav pills, the phone CTA dock, menus
3
3
  and popovers), never on cards or sections. Menus lift further, on
4
- --shadow-lg. Written once here because Safari
4
+ --lc-shadow-lg. Written once here because Safari
5
5
  drops custom properties inside -webkit-backdrop-filter, so the blur needs
6
6
  literal values, and because the solid fallbacks need @supports and
7
7
  prefers-reduced-transparency. glass_class is the only writer of the name. */
8
8
  @layer components {
9
9
  /* The emoji and currency pickers are glass only as desktop popovers; on
10
10
  phones they're bottom sheets, whose rules come later in the cascade and repaint them solid. */
11
- .glass {
12
- background: var(--glass-fill);
13
- border: 1px solid var(--glass-edge);
14
- box-shadow: var(--glass-shadow);
11
+ .lc-glass {
12
+ --_lc-glass-fill: var(--lc-glass-fill);
13
+ background: var(--_lc-glass-fill);
14
+ border: 1px solid var(--lc-glass-edge);
15
+ box-shadow: var(--lc-glass-shadow);
15
16
  -webkit-backdrop-filter: blur(24px) saturate(160%);
16
17
  backdrop-filter: blur(24px) saturate(160%);
17
18
 
@@ -23,13 +24,13 @@
23
24
  backdrop-filter: blur(12px) saturate(180%);
24
25
  }
25
26
 
26
- /* --glass-solid lets inverted (HUD) glass fall back to its own colour. */
27
+ /* --lc-glass-solid lets inverted (HUD) glass fall back to its own colour. */
27
28
  @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
28
- background: var(--glass-solid, var(--paper));
29
+ --_lc-glass-fill: var(--lc-glass-solid, var(--background));
29
30
  }
30
31
 
31
32
  @media (prefers-reduced-transparency: reduce) {
32
- background: var(--glass-solid, var(--paper));
33
+ --_lc-glass-fill: var(--lc-glass-solid, var(--background));
33
34
  -webkit-backdrop-filter: none;
34
35
  backdrop-filter: none;
35
36
  }
@@ -1,5 +1,5 @@
1
1
  @layer components {
2
- .ui-container-page {
3
- padding-inline: var(--ui-gutter, clamp(1rem, 3vw, 1.5rem));
2
+ .lc-container-page {
3
+ padding-inline: var(--spacing-gutter);
4
4
  }
5
5
  }
@@ -1,12 +1,12 @@
1
1
  /* Closed is the exit pose, so closing animates too; allow-discrete holds
2
2
  display/overlay until it ends. FloatingPanel sets [data-open]. */
3
- @utility popover-shell {
3
+ @utility lc-popover-shell {
4
4
  opacity: 0;
5
5
  transform: translateY(--spacing(-1)) scale(0.98);
6
6
  pointer-events: none;
7
7
  transition:
8
- opacity 160ms var(--ease-out-expo),
9
- transform 160ms var(--ease-out-expo),
8
+ opacity 160ms var(--lc-ease-out-expo),
9
+ transform 160ms var(--lc-ease-out-expo),
10
10
  display 160ms allow-discrete,
11
11
  overlay 160ms allow-discrete;
12
12
 
@@ -23,10 +23,10 @@
23
23
  }
24
24
 
25
25
  @layer components {
26
- /* Desktop: the .sheet becomes an anchored popover instead of a bottom sheet. */
26
+ /* Desktop: the .lc-sheet becomes an anchored popover instead of a bottom sheet. */
27
27
  @media (width >= 40rem) {
28
- .sheet--anchored {
29
- @apply popover-shell;
28
+ .lc-sheet--anchored {
29
+ @apply lc-popover-shell;
30
30
 
31
31
  display: none;
32
32
  flex-direction: column;
@@ -48,27 +48,27 @@
48
48
  /* Enter/exit for small overlays, whichever way they open: a [hidden] toggle
49
49
  (amount calculator, colour picker), the Popover API (phone nav menus) or a
50
50
  modal <dialog>. The closed state is the exit pose, so close animates too;
51
- allow-discrete holds display/overlay until it ends. --pop-y sets the
51
+ allow-discrete holds display/overlay until it ends. --lc-pop-y sets the
52
52
  direction: popovers drop from their trigger, dialogs rise. */
53
- .popover-pop {
54
- --pop-from: translateY(var(--pop-y, -0.25rem)) scale(0.96);
53
+ .lc-popover-pop {
54
+ --lc-pop-from: translateY(var(--lc-pop-y, -0.25rem)) scale(0.96);
55
55
 
56
- transform-origin: var(--pop-origin, top);
56
+ transform-origin: var(--lc-pop-origin, top);
57
57
  transition:
58
- opacity 160ms var(--ease-out-expo),
59
- transform 240ms var(--ease-out-expo),
58
+ opacity 160ms var(--lc-ease-out-expo),
59
+ transform 240ms var(--lc-ease-out-expo),
60
60
  display 240ms allow-discrete,
61
61
  overlay 240ms allow-discrete;
62
62
 
63
63
  &:is([hidden], [popover]:not(:popover-open), dialog:not([open])) {
64
64
  opacity: 0;
65
- transform: var(--pop-from);
65
+ transform: var(--lc-pop-from);
66
66
  }
67
67
 
68
68
  &:is(:not([hidden], [popover], dialog), :popover-open, dialog[open]) {
69
69
  @starting-style {
70
70
  opacity: 0;
71
- transform: var(--pop-from);
71
+ transform: var(--lc-pop-from);
72
72
  }
73
73
  }
74
74
  }
@@ -0,0 +1,66 @@
1
+ @layer utilities {
2
+ .lc-prose {
3
+ --tw-prose-body: var(--muted-foreground);
4
+ --tw-prose-headings: var(--foreground);
5
+ --tw-prose-lead: var(--muted-foreground);
6
+ --tw-prose-links: var(--primary);
7
+ --tw-prose-bold: var(--foreground);
8
+ --tw-prose-counters: var(--muted-foreground);
9
+ --tw-prose-bullets: var(--muted-foreground);
10
+ --tw-prose-hr: var(--border);
11
+ --tw-prose-quotes: var(--foreground);
12
+ --tw-prose-quote-borders: var(--border);
13
+ --tw-prose-captions: var(--muted-foreground);
14
+ --tw-prose-code: var(--foreground);
15
+ --tw-prose-pre-code: var(--foreground);
16
+ --tw-prose-pre-bg: var(--muted);
17
+ --tw-prose-th-borders: var(--border);
18
+ --tw-prose-td-borders: var(--border);
19
+
20
+ & :where(p, ul, ol, dl, blockquote, pre, table, figure, hr):not(:where(.not-prose, .not-prose *)) {
21
+ margin-block: var(--lc-reading-flow, 1.25em) 0;
22
+ }
23
+
24
+ & :where(h1, h2, h3, h4):not(:where(.not-prose, .not-prose *)) {
25
+ margin-block: calc(var(--lc-reading-flow, 1.25em) * 1.6) 0;
26
+ font-family: var(--font-display, inherit);
27
+ line-height: var(--lc-reading-heading-leading, 1.25);
28
+ text-wrap: balance;
29
+ }
30
+
31
+ & :where(h1, h2, h3, h4) + :where(p, ul, ol):not(:where(.not-prose, .not-prose *)) {
32
+ margin-block-start: calc(var(--lc-reading-flow, 1.25em) * 0.5);
33
+ }
34
+
35
+ & :where(li > p, li > ul, li > ol):not(:where(.not-prose, .not-prose *)) {
36
+ margin-block-start: calc(var(--lc-reading-flow, 1.25em) * 0.5);
37
+ }
38
+
39
+ & > :where(:first-child):not(:where(.not-prose, .not-prose *)) {
40
+ margin-block-start: 0;
41
+ }
42
+
43
+ &:lang(ja) {
44
+ line-break: strict;
45
+ word-break: normal;
46
+ overflow-wrap: anywhere;
47
+ }
48
+
49
+ &:lang(ja) :where(h1, h2, h3, h4):not(:where(.not-prose, .not-prose *)) {
50
+ line-height: var(--lc-reading-heading-leading, 1.4);
51
+ letter-spacing: normal;
52
+ }
53
+ }
54
+
55
+ @supports (word-break: auto-phrase) {
56
+ .lc-prose:lang(ja) :where(h1, h2, h3, h4):not(:where(.not-prose, .not-prose *)) {
57
+ word-break: auto-phrase;
58
+ }
59
+ }
60
+ }
61
+
62
+ @layer utilities {
63
+ .lc-prose :where(code):not(:where(pre code)) {
64
+ @apply lc-inline-code;
65
+ }
66
+ }