@filipgutica/ui 1.16.1 → 1.18.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.
package/README.md CHANGED
@@ -186,9 +186,9 @@ import read from './captures/read.html?raw'
186
186
 
187
187
  `UiSiteNav` renders a labelled list of route rows in optional `groups`. Links to
188
188
  `#id` sections track the reader's position with `useActiveSection` and get
189
- `aria-current="location"`, an accent bar without a fill. An item with
189
+ `aria-current="location"`, an accent pip without a fill. An item with
190
190
  `current: true` gets
191
- `aria-current="page"`, the selected fill and accent bar; `code: true` shows the
191
+ `aria-current="page"`, a quiet filled row with a slight lift and accent text; `code: true` shows the
192
192
  label in the monospace font. The page grid places the rail, which stays in view
193
193
  on desktop. At 800px and below it is a native disclosure until the page is
194
194
  interactive, then a drawer. The disclosure, the drawer button, and the drawer
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as e, i as t, n, o as r, r as i, s as a, t as o } from "./use-active-section-DMbpGE2f.js";
1
+ import { a as e, i as t, n, o as r, r as i, s as a, t as o } from "./use-active-section-DpcSUhA9.js";
2
2
  import { Fragment as s, computed as c, createBlock as l, createCommentVNode as u, createElementBlock as d, createElementVNode as f, createTextVNode as p, createVNode as m, defineComponent as h, inject as g, mergeModels as _, mergeProps as v, normalizeStyle as y, openBlock as b, provide as x, ref as S, renderList as C, renderSlot as w, resolveDynamicComponent as T, shallowRef as ee, toDisplayString as E, unref as D, useAttrs as te, useId as O, useModel as ne, useSlots as k, watch as re, withCtx as A } from "vue";
3
3
  import { CheckboxIndicator as j, CheckboxRoot as M, Primitive as N, ProgressIndicator as P, ProgressRoot as F, RadioGroupItem as I, RadioGroupRoot as L, SliderRange as R, SliderRoot as z, SliderThumb as B, SliderTrack as V, Toggle as H } from "reka-ui";
4
4
  //#region src/components/UiCard.vue?vue&type=script&setup=true&lang.ts
package/dist/site.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as e, i as t, n, o as r, r as i, s as a, t as o } from "./use-active-section-DMbpGE2f.js";
1
+ import { a as e, i as t, n, o as r, r as i, s as a, t as o } from "./use-active-section-DpcSUhA9.js";
2
2
  import { Fragment as s, computed as c, createBlock as l, createCommentVNode as u, createElementBlock as d, createElementVNode as f, createTextVNode as p, createVNode as m, defineComponent as h, mergeModels as g, nextTick as _, normalizeClass as v, normalizeStyle as y, onBeforeUnmount as b, onMounted as x, onUnmounted as S, openBlock as C, ref as w, renderList as T, renderSlot as E, shallowRef as D, toDisplayString as O, unref as k, useId as A, useModel as j, useSlots as M, useTemplateRef as N, watch as P, withCtx as F } from "vue";
3
3
  import { tryOnMounted as I, usePreferredDark as L, useStorage as R } from "@vueuse/core";
4
4
  //#region src/site/appearance.ts
@@ -185,13 +185,32 @@ var N = /* @__PURE__ */ c({
185
185
  },
186
186
  emits: ["update:modelValue"],
187
187
  setup(t, { emit: n }) {
188
- let a = n;
189
- return (n, o) => (f(), i("div", {
188
+ let a = n, o = b("root"), s = !1;
189
+ u(() => requestAnimationFrame(() => {
190
+ s = !0;
191
+ }));
192
+ let c = () => {
193
+ o.value && (delete o.value.dataset.slide, o.value.style.removeProperty("--fg-segmented-slide-left"), o.value.style.removeProperty("--fg-segmented-slide-right"));
194
+ };
195
+ return x(() => t.modelValue, (e, n) => {
196
+ let r = o.value;
197
+ if (!r) return;
198
+ let i = Array.from(r.children).filter((e) => e instanceof HTMLElement), a = i[t.items.findIndex((e) => e.value === n)], l = i[t.items.findIndex((t) => t.value === e)];
199
+ if (!s || !a || !l || matchMedia("(prefers-reduced-motion: reduce)").matches) {
200
+ c();
201
+ return;
202
+ }
203
+ let u = getComputedStyle(a, "::before"), d = a.getBoundingClientRect(), f = d.left + parseFloat(u.left), p = d.right - parseFloat(u.right), m = l.getBoundingClientRect();
204
+ c(), r.style.setProperty("--fg-segmented-slide-left", `${f - m.left}px`), r.style.setProperty("--fg-segmented-slide-right", `${m.right - p}px`), r.dataset.slide = "";
205
+ }, { flush: "pre" }), (n, s) => (f(), i("div", {
206
+ ref_key: "root",
207
+ ref: o,
190
208
  class: "fg-segmented",
191
209
  role: "group",
192
210
  "aria-label": t.label,
193
211
  "data-size": t.size,
194
- "data-icon-only": t.iconOnly ? "true" : void 0
212
+ "data-icon-only": t.iconOnly ? "true" : void 0,
213
+ onAnimationend: s[0] ||= (e) => e.animationName === "fg-segmented-slide" && c()
195
214
  }, [(f(!0), i(e, null, h(t.items, (e) => (f(), i("button", {
196
215
  key: e.value,
197
216
  type: "button",
@@ -204,7 +223,7 @@ var N = /* @__PURE__ */ c({
204
223
  }, [g(n.$slots, "icon", {
205
224
  item: e,
206
225
  pressed: t.modelValue === e.value
207
- }), t.iconOnly ? r("", !0) : (f(), i("span", W, _(e.label), 1))], 8, U))), 128))], 8, H));
226
+ }), t.iconOnly ? r("", !0) : (f(), i("span", W, _(e.label), 1))], 8, U))), 128))], 40, H));
208
227
  }
209
228
  }), K = { class: "fg-tabs" }, q = ["aria-label", "data-clipped"], J = [
210
229
  "id",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@filipgutica/ui",
3
- "version": "1.16.1",
3
+ "version": "1.18.0",
4
4
  "description": "A compact Vue design system with Tailwind CSS and VS Code theme support",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -16,7 +16,8 @@
16
16
  min-width: 0;
17
17
  max-width: 100%;
18
18
  overflow: hidden;
19
- border: 1px solid var(--color-border-subtle);
19
+ border: 1px solid transparent;
20
+ box-shadow: var(--shadow-raise);
20
21
  border-radius: var(--radius-panel);
21
22
  background: var(--color-code);
22
23
  color: var(--color-code-text);
@@ -146,13 +147,15 @@
146
147
  }
147
148
 
148
149
  .fg-button {
150
+ --fg-button-shadow: var(--shadow-raise);
149
151
  display: inline-flex;
150
152
  align-items: center;
151
153
  justify-content: center;
152
154
  gap: var(--space-2);
153
155
  color: var(--color-button-text);
154
156
  background: var(--color-button-bg);
155
- box-shadow: none;
157
+ box-shadow: var(--fg-button-shadow);
158
+ transition: background-color var(--duration-fast) var(--ease-out), box-shadow var(--duration-fast) var(--ease-out);
156
159
  font-weight: 500;
157
160
  letter-spacing: -0.01em;
158
161
  line-height: 1.25;
@@ -163,8 +166,8 @@
163
166
  background: var(--color-button-hover);
164
167
  }
165
168
 
166
- .fg-button:active:not(:disabled) {
167
- box-shadow: none;
169
+ .fg-button:where(:not([data-variant]), [data-variant="primary"]):is(:active, [aria-pressed="true"]):not(:disabled) {
170
+ background: color-mix(in srgb, var(--color-button-bg) 80%, var(--color-bg));
168
171
  }
169
172
 
170
173
  .fg-button:disabled,
@@ -239,24 +242,29 @@
239
242
  }
240
243
 
241
244
  .fg-button[data-variant="secondary"] {
242
- border-color: var(--color-border);
243
245
  color: var(--color-text);
244
- background: transparent;
246
+ background: var(--color-control-thumb);
247
+ }
248
+
249
+ .fg-button[data-variant="secondary"]:hover:not(:disabled) {
250
+ color: var(--color-text);
251
+ background: color-mix(in srgb, var(--color-text) 8%, var(--color-control-thumb));
245
252
  }
246
253
 
247
- .fg-button[data-variant="secondary"]:hover:not(:disabled),
248
254
  .fg-button[data-variant="ghost"]:hover:not(:disabled) {
249
255
  color: var(--color-text);
250
256
  background: var(--color-row-hover);
251
257
  }
252
258
 
253
259
  .fg-button[data-variant="ghost"] {
260
+ --fg-button-shadow: 0 0 0 0 transparent;
254
261
  color: var(--color-muted);
255
262
  background: transparent;
256
263
  box-shadow: none;
257
264
  }
258
265
 
259
266
  .fg-button[data-variant="danger"] {
267
+ --fg-button-shadow: 0 0 0 0 transparent;
260
268
  color: var(--color-status-text, var(--color-error));
261
269
  background: var(--color-error-surface);
262
270
  box-shadow: none;
@@ -270,6 +278,7 @@
270
278
  }
271
279
 
272
280
  .fg-button[data-variant="text"] {
281
+ --fg-button-shadow: 0 0 0 0 transparent;
273
282
  height: auto;
274
283
  min-height: auto;
275
284
  padding: var(--space-1);
@@ -285,13 +294,20 @@
285
294
  background: transparent;
286
295
  }
287
296
 
297
+ /* Keep press specificity level with focus so its halo can wrap the sink shadow. */
298
+ .fg-button:where(:not([data-variant="danger"], [data-variant="text"])):is(:active, [aria-pressed="true"]):not(:disabled) {
299
+ --fg-button-shadow: var(--shadow-sink);
300
+ box-shadow: var(--fg-button-shadow);
301
+ }
302
+ .fg-button[data-variant="secondary"]:is(:active, [aria-pressed="true"]):not(:disabled) { background: var(--color-well); }
303
+ .fg-button:disabled { box-shadow: none; }
304
+
288
305
  .fg-input,
289
306
  .fg-select {
290
307
  width: 100%;
291
- border-color: var(--color-input-border);
292
308
  color: var(--color-input-text);
293
- background: var(--color-input-bg);
294
- box-shadow: none;
309
+ background: var(--color-well);
310
+ box-shadow: var(--shadow-sink);
295
311
  line-height: 1.35;
296
312
  }
297
313
 
@@ -348,7 +364,7 @@
348
364
  min-height: var(--control-sm);
349
365
  gap: var(--space-2);
350
366
  padding: var(--space-2) var(--space-3);
351
- border-radius: var(--radius-control);
367
+ border-radius: calc(var(--radius-panel) - var(--space-1));
352
368
  color: var(--color-text);
353
369
  background: transparent;
354
370
  cursor: pointer;
@@ -419,7 +435,7 @@
419
435
  margin-top: 0.1rem;
420
436
  place-items: center;
421
437
  border: 1px solid var(--color-input-border);
422
- border-radius: var(--radius-control);
438
+ border-radius: var(--radius-inline);
423
439
  color: var(--color-button-text);
424
440
  background: var(--color-input-bg);
425
441
  box-shadow: var(--shadow-control);
@@ -494,25 +510,24 @@
494
510
  min-width: 0;
495
511
  padding: var(--space-3);
496
512
  overflow: hidden;
497
- border: 1px solid var(--color-border-subtle);
513
+ border: 1px solid transparent;
498
514
  border-radius: var(--radius-panel);
499
515
  color: var(--color-text);
500
516
  background: var(--color-surface-raised);
501
- box-shadow: none;
517
+ box-shadow: var(--shadow-raise);
502
518
  text-align: left;
503
519
  cursor: pointer;
504
520
  transition: transform var(--duration-fast) var(--ease-out);
505
521
  }
506
522
 
507
- .fg-radio-card:hover:not([data-disabled]) {
508
- border-color: var(--color-border);
523
+ .fg-radio-card:hover:not([data-disabled], [data-state="checked"]) {
524
+ background: color-mix(in srgb, var(--color-surface-muted) 45%, var(--color-surface-raised));
509
525
  }
510
526
 
511
- /* Checked choices use the accent, as the checkbox does; blue stays for links and focus. */
527
+ /* Depth and a check badge distinguish the choice without an accent ring. */
512
528
  .fg-radio-card[data-state="checked"] {
513
- border-color: var(--color-accent);
514
- outline: 1px solid var(--color-accent);
515
- outline-offset: -2px;
529
+ background: var(--color-well);
530
+ box-shadow: var(--shadow-sink);
516
531
  }
517
532
 
518
533
  .fg-radio-card[data-disabled] {
@@ -541,14 +556,26 @@
541
556
  }
542
557
 
543
558
  .fg-radio-card[data-state="checked"] .fg-radio-card__indicator {
544
- border: 0.3rem solid var(--color-accent);
559
+ border-color: var(--color-accent);
560
+ color: var(--color-accent-text);
561
+ background: var(--color-accent);
562
+ }
563
+
564
+ .fg-radio-card[data-state="checked"] .fg-radio-card__indicator::after {
565
+ content: "";
566
+ position: absolute;
567
+ inset: 2px 4px 4px;
568
+ border-right: 2px solid currentColor;
569
+ border-bottom: 2px solid currentColor;
570
+ transform: rotate(45deg);
545
571
  }
546
572
 
547
573
  .fg-badge {
548
574
  display: inline-flex;
549
575
  align-items: center;
550
576
  width: fit-content;
551
- border: 1px solid var(--color-border-subtle);
577
+ gap: 6px;
578
+ border: 1px solid transparent;
552
579
  border-radius: var(--radius-pill);
553
580
  padding: 0.18rem 0.56rem;
554
581
  color: var(--color-muted);
@@ -558,41 +585,53 @@
558
585
  line-height: 1.25;
559
586
  }
560
587
 
561
- /* A tone badge is a tinted pill whose text carries the status colour, darkened toward the text
562
- * colour so it reads at small sizes on the tint. */
563
- .fg-badge[data-tone]:not([data-tone="neutral"]) { border-color: transparent; }
564
- .fg-badge[data-tone="info"] { color: color-mix(in srgb, var(--color-info) 80%, var(--color-text)); background: var(--color-info-surface); }
565
- .fg-badge[data-tone="success"] { color: color-mix(in srgb, var(--color-success) 80%, var(--color-text)); background: var(--color-success-surface); }
566
- .fg-badge[data-tone="warning"] { color: color-mix(in srgb, var(--color-warning) 80%, var(--color-text)); background: var(--color-warning-surface); }
588
+ /* Status tint and text use the same hue; neutral metadata has no status dot. */
589
+ .fg-badge[data-tone]:not([data-tone="neutral"]) {
590
+ color: color-mix(in srgb, var(--fg-badge-tone) 75%, var(--color-text));
591
+ background: color-mix(in srgb, var(--fg-badge-tone) 22%, var(--color-surface-raised));
592
+ }
593
+ :root.dark .fg-badge[data-tone]:not([data-tone="neutral"]) {
594
+ color: color-mix(in srgb, var(--fg-badge-tone) 85%, var(--color-text));
595
+ }
596
+ .fg-badge[data-tone]:not([data-tone="neutral"])::before {
597
+ content: "";
598
+ flex: none;
599
+ width: 6px;
600
+ height: 6px;
601
+ border-radius: 50%;
602
+ background: currentColor;
603
+ }
604
+ .fg-badge[data-tone="info"] { --fg-badge-tone: var(--color-info); }
605
+ .fg-badge[data-tone="success"] { --fg-badge-tone: var(--color-success); }
606
+ .fg-badge[data-tone="warning"] { --fg-badge-tone: var(--color-warning); }
567
607
  .fg-badge[data-tone="danger"],
568
- .fg-badge[data-tone="error"] { color: color-mix(in srgb, var(--color-error) 80%, var(--color-text)); background: var(--color-error-surface); }
608
+ .fg-badge[data-tone="error"] { --fg-badge-tone: var(--color-error); }
569
609
 
570
610
  .fg-alert {
571
611
  display: flex;
572
612
  align-items: flex-start;
573
613
  gap: var(--space-3);
574
614
  padding: var(--space-3) var(--space-4);
575
- border: 1px solid var(--color-border-subtle);
615
+ border: 1px solid transparent;
576
616
  border-radius: var(--radius-panel);
577
617
  color: var(--color-text);
578
618
  background: var(--color-surface);
579
619
  }
580
620
 
581
- .fg-alert[data-tone="info"] { border-color: color-mix(in srgb, var(--color-info) 35%, var(--color-border-subtle)); background: var(--color-info-surface); }
582
- .fg-alert[data-tone="success"] { border-color: color-mix(in srgb, var(--color-success) 35%, var(--color-border-subtle)); background: var(--color-success-surface); }
583
- .fg-alert[data-tone="warning"] { border-color: color-mix(in srgb, var(--color-warning) 35%, var(--color-border-subtle)); background: var(--color-warning-surface); }
621
+ .fg-alert[data-tone="info"] { background: var(--color-info-surface); }
622
+ .fg-alert[data-tone="success"] { background: var(--color-success-surface); }
623
+ .fg-alert[data-tone="warning"] { background: var(--color-warning-surface); }
584
624
  .fg-alert[data-tone="danger"],
585
625
  .fg-alert[data-tone="error"] {
586
- border-color: color-mix(in srgb, var(--color-error) 35%, var(--color-border-subtle));
587
626
  color: var(--color-status-text, var(--color-error));
588
627
  background: var(--color-error-surface);
589
628
  }
590
629
 
591
630
  .fg-surface {
592
- border: 1px solid var(--color-border-subtle);
631
+ border: 1px solid transparent;
593
632
  border-radius: var(--radius-panel);
594
633
  background: var(--color-surface-raised);
595
- box-shadow: none;
634
+ box-shadow: var(--shadow-raise);
596
635
  }
597
636
 
598
637
  .fg-surface[data-padding="compact"] { padding: var(--space-3); }
@@ -601,11 +640,11 @@
601
640
  .fg-card {
602
641
  min-width: 0;
603
642
  overflow: hidden;
604
- border: 1px solid var(--color-border-subtle);
643
+ border: 1px solid transparent;
605
644
  border-radius: var(--radius-panel);
606
645
  color: var(--color-text);
607
646
  background: var(--color-surface-raised);
608
- box-shadow: none;
647
+ box-shadow: var(--shadow-raise);
609
648
  }
610
649
 
611
650
  .fg-card__header {
@@ -725,20 +764,22 @@
725
764
  /* Segmented control: one pressed choice on a muted track, so it reads as a raised thumb in both themes. */
726
765
  .fg-segmented {
727
766
  --fg-segmented-item: var(--control-md);
728
- /* The thumb is lighter than the track in both themes; the dark raised surface is darker than the track. */
729
- --fg-segmented-thumb: light-dark(var(--color-row-selected), color-mix(in srgb, var(--color-text) 10%, var(--color-surface-muted)));
767
+ --fg-segmented-thumb: var(--color-surface-raised);
730
768
  display: inline-flex;
731
769
  flex: none;
732
770
  /* The fill alone marks the track. The transparent border keeps its size, and forced colors draw it. */
733
771
  border: 1px solid transparent;
734
772
  border-radius: var(--radius-control);
735
- background: var(--color-surface-muted);
773
+ background: var(--color-well);
774
+ box-shadow: var(--shadow-sink);
775
+ /* Thumbs stack on the track, under every label, so a sliding thumb never covers its neighbour's. */
776
+ isolation: isolate;
736
777
  }
778
+ :root.dark .fg-segmented { --fg-segmented-thumb: color-mix(in srgb, var(--color-surface-raised) 88%, white); }
737
779
  .fg-segmented[data-size="sm"] { --fg-segmented-item: var(--control-sm); font-size: var(--text-sm); }
738
780
  .fg-segmented[data-size="lg"] { --fg-segmented-item: var(--control-lg); font-size: var(--text-lg); }
739
781
  .fg-segmented__item {
740
782
  position: relative;
741
- isolation: isolate;
742
783
  display: inline-flex;
743
784
  gap: var(--space-2);
744
785
  min-height: var(--fg-segmented-item);
@@ -754,16 +795,27 @@
754
795
  }
755
796
  .fg-segmented[data-icon-only="true"] .fg-segmented__item { width: var(--fg-segmented-item); padding: 0; }
756
797
  /* The thumb is inset inside each full-size button, so it floats on the track and the hit target keeps its size. */
757
- .fg-segmented__item::before { content: ""; position: absolute; inset: 2px; z-index: -1; border-radius: var(--radius-inline); }
798
+ .fg-segmented__item::before { content: ""; position: absolute; inset: 2px; z-index: -2; border-radius: var(--radius-control); pointer-events: none; }
758
799
  .fg-segmented__item:hover:not(:disabled, [aria-pressed="true"]) { color: var(--color-text); }
759
800
  .fg-segmented__item:hover:not(:disabled, [aria-pressed="true"])::before { background: color-mix(in srgb, var(--fg-segmented-thumb) 45%, transparent); }
760
- /* The ring gives the pressed choice 3:1 against the track in both themes (WCAG 1.4.11). */
761
- .fg-segmented__item[aria-pressed="true"] { color: var(--color-text); }
801
+ /* The accent label or icon marks the pressed choice at 3:1 against its thumb. */
802
+ .fg-segmented__item[aria-pressed="true"] { color: var(--color-accent); }
762
803
  /* Drawn inside the button, the ring hugs the thumb instead of reaching the next choice. */
763
804
  .fg-segmented .fg-segmented__item:focus-visible { outline-offset: -2px; }
764
805
  .fg-segmented__item[aria-pressed="true"]::before {
806
+ z-index: -1;
765
807
  background: var(--fg-segmented-thumb);
766
- box-shadow: inset 0 0 0 1px var(--color-border);
808
+ box-shadow: var(--shadow-raise);
809
+ }
810
+ /* Motion answers only the reader: hover, and a change of choice, which the script marks
811
+ * with the thumb's start over the previous option. A theme change recolours at once. */
812
+ @media (prefers-reduced-motion: no-preference) {
813
+ .fg-segmented__item:hover:not(:disabled, [aria-pressed="true"]) { transition: color var(--duration-fast) var(--ease-out); }
814
+ .fg-segmented[data-slide] .fg-segmented__item { transition: color var(--duration) var(--ease-out); }
815
+ .fg-segmented[data-slide] .fg-segmented__item[aria-pressed="true"]::before { animation: fg-segmented-slide var(--duration) var(--ease-out); }
816
+ }
817
+ @keyframes fg-segmented-slide {
818
+ from { left: var(--fg-segmented-slide-left); right: var(--fg-segmented-slide-right); }
767
819
  }
768
820
 
769
821
  .fg-switch { display: inline-flex; align-items: center; gap: var(--space-2); min-width: 0; }
@@ -801,7 +853,8 @@
801
853
  height: 1rem;
802
854
  border: 1px solid var(--color-border);
803
855
  border-radius: var(--radius-pill);
804
- background: var(--color-surface-muted);
856
+ background: var(--color-well);
857
+ box-shadow: var(--shadow-sink);
805
858
  transition: background-color var(--duration) var(--ease-out), border-color var(--duration) var(--ease-out);
806
859
  }
807
860
 
@@ -813,7 +866,8 @@
813
866
  width: 0.625rem;
814
867
  height: 0.625rem;
815
868
  border-radius: var(--radius-pill);
816
- background: var(--color-border);
869
+ background: var(--color-surface-raised);
870
+ box-shadow: var(--shadow-raise);
817
871
  content: "";
818
872
  transition: transform var(--duration) var(--ease-out), background-color var(--duration) var(--ease-out);
819
873
  }
@@ -990,8 +1044,26 @@
990
1044
  .fg-toggle-button:focus-visible,
991
1045
  .fg-segmented__item:focus-visible,
992
1046
  .fg-switch__control:focus-visible .fg-switch__track {
993
- outline: 2px solid var(--color-focus);
1047
+ outline: 2px solid color-mix(in srgb, var(--color-accent) 80%, transparent);
994
1048
  outline-offset: 2px;
1049
+ box-shadow: 0 0 0 6px color-mix(in srgb, var(--color-accent) 18%, transparent);
1050
+ }
1051
+
1052
+ .fg-button:focus-visible:not(:disabled) {
1053
+ box-shadow: var(--fg-button-shadow), 0 0 0 6px color-mix(in srgb, var(--color-accent) 18%, transparent);
1054
+ }
1055
+
1056
+ .fg-input:focus-visible,
1057
+ .fg-select:focus-visible,
1058
+ .fg-radio-card[data-state="checked"]:focus-visible,
1059
+ .fg-switch__control:focus-visible .fg-switch__track {
1060
+ box-shadow: var(--shadow-sink), 0 0 0 6px color-mix(in srgb, var(--color-accent) 18%, transparent);
1061
+ }
1062
+ .fg-dialog__content:focus-visible {
1063
+ box-shadow: var(--shadow-dialog), 0 0 0 6px color-mix(in srgb, var(--color-accent) 18%, transparent);
1064
+ }
1065
+ .fg-radio-card:not([data-state="checked"]):focus-visible {
1066
+ box-shadow: var(--shadow-raise), 0 0 0 6px color-mix(in srgb, var(--color-accent) 18%, transparent);
995
1067
  }
996
1068
 
997
1069
  /* The control is a larger transparent hit area, so the ring follows the visible track instead. */
@@ -1025,6 +1097,12 @@
1025
1097
  }
1026
1098
 
1027
1099
  @media (forced-colors: active) {
1100
+ .fg-radio-card[data-state="checked"] .fg-radio-card__indicator {
1101
+ forced-color-adjust: none;
1102
+ border-color: Highlight;
1103
+ color: HighlightText;
1104
+ background: Highlight;
1105
+ }
1028
1106
  /* Forced colors drop the fill and the ring, so the pressed choice takes the system highlight. */
1029
1107
  .fg-segmented__item[aria-pressed="true"] { forced-color-adjust: none; color: HighlightText; }
1030
1108
  .fg-segmented__item[aria-pressed="true"]::before { forced-color-adjust: none; background: Highlight; }
package/src/site.css CHANGED
@@ -201,30 +201,39 @@
201
201
 
202
202
  /* Route rows. Hover is neutral, and the current page keeps its selected fill on
203
203
  * hover. An in-page section marker (aria-current="location") must look different
204
- * from the current page, so it keeps the bar without the selected fill. */
204
+ * from the current page, so it uses an accent pip without the raised fill. */
205
205
  .fg-site-nav a {
206
206
  display: flex;
207
207
  min-height: var(--row);
208
208
  align-items: center;
209
209
  padding-inline: var(--space-3);
210
- border-radius: var(--radius-control);
210
+ border-radius: var(--radius-field);
211
211
  color: var(--color-muted);
212
212
  font-family: var(--font-mono);
213
213
  font-size: var(--text-sm);
214
214
  text-decoration: none;
215
- transition: background-color var(--duration-fast) ease, color var(--duration-fast) ease;
215
+ transition: background-color var(--duration-fast) ease, color var(--duration-fast) ease, box-shadow var(--duration-fast) ease;
216
216
  }
217
217
  .fg-site-nav a:hover { background: var(--color-row-hover); color: var(--color-text); }
218
218
  .fg-site-nav a[aria-current="page"] {
219
- background: var(--color-surface-raised);
220
- box-shadow: inset 2px 0 0 var(--color-project);
221
- color: var(--color-text);
219
+ background: var(--color-nav-current);
220
+ box-shadow: var(--shadow-nav-current);
221
+ color: var(--color-accent);
222
222
  font-weight: var(--weight-medium);
223
223
  }
224
- .fg-site-nav a[aria-current="location"] { box-shadow: inset 2px 0 0 var(--color-project); color: var(--color-text); font-weight: var(--weight-medium); }
224
+ .fg-site-nav a[aria-current="location"] { position: relative; color: var(--color-text); font-weight: var(--weight-medium); }
225
+ .fg-site-nav a[aria-current="location"]::before {
226
+ content: "";
227
+ position: absolute;
228
+ inset-inline-start: var(--space-1);
229
+ width: 3px;
230
+ height: 14px;
231
+ border-radius: var(--radius-pill);
232
+ background: var(--color-accent);
233
+ }
225
234
  .fg-site-nav a code { font-family: var(--font-mono); font-size: var(--text-sm); }
226
235
 
227
- /* Forced colors drop the inset marker, so the current link takes a system border. */
236
+ /* Forced colors drop shadows, so the current link takes a system border. */
228
237
  @media (forced-colors: active) {
229
238
  .fg-site-nav a[aria-current] { border-inline-start: 2px solid Highlight; }
230
239
  }
@@ -418,7 +427,7 @@
418
427
  .fg-capture-viewer .fg-capture__screen {
419
428
  max-height: calc(100dvh - var(--space-16) * 3);
420
429
  overflow: auto;
421
- border-radius: var(--radius-control);
430
+ border-radius: var(--radius-inline);
422
431
  }
423
432
 
424
433
  /* Command table (UiCommandList). Wide commands scroll inside it on larger
package/src/theme.css CHANGED
@@ -37,6 +37,10 @@
37
37
  --color-surface: #e9ebed;
38
38
  --color-surface-raised: #fbfbfc;
39
39
  --color-surface-muted: #e2e5e8;
40
+ --color-well: var(--color-surface-muted);
41
+ --color-control-thumb: var(--color-surface-raised);
42
+ --color-nav-current: var(--color-surface-raised);
43
+ --shadow-nav-current: 0 1px 3px rgb(130 142 158 / 40%);
40
44
  --color-text: #101315;
41
45
  --color-muted: #50575e;
42
46
  --color-link: #0b5f7c;
@@ -78,15 +82,18 @@
78
82
  --color-warning-surface: #e7e2de;
79
83
  --color-success-surface: #d9e5e1;
80
84
  --color-info-surface: #d9e3f2;
81
- --radius-inline: 3px;
82
- --radius-control: 4px;
83
- --radius-panel: 6px;
85
+ --radius-inline: 6px;
86
+ --radius-control: 999px;
87
+ --radius-panel: 16px;
88
+ --radius-field: 10px;
84
89
  --radius-pill: 999px;
85
90
  --duration-fast: 120ms;
86
91
  --duration: 180ms;
87
92
  --ease-out: cubic-bezier(0.2, 0.8, 0.2, 1);
88
93
  --shadow-control: none;
89
94
  --shadow-dialog: 0 8px 24px rgb(16 16 24 / 12%), 0 0 0 1px var(--color-border-subtle);
95
+ --shadow-raise: 2px 2px 6px rgb(130 142 158 / 35%), -2px -2px 6px rgb(255 255 255 / 80%);
96
+ --shadow-sink: inset 1.2px 1.2px 3.6px rgb(130 142 158 / 35%), inset -1.2px -1.2px 3.6px rgb(255 255 255 / 80%);
90
97
 
91
98
  --color-project: #4b45d1;
92
99
  --color-accent-text: var(--color-bg);
@@ -106,6 +113,10 @@
106
113
  --color-surface: #13171a;
107
114
  --color-surface-raised: #181d21;
108
115
  --color-surface-muted: #20262b;
116
+ --color-well: color-mix(in srgb, var(--color-bg) 87.5%, black);
117
+ --color-control-thumb: color-mix(in srgb, var(--color-surface-raised) 88%, white);
118
+ --color-nav-current: color-mix(in srgb, var(--color-text) 7%, transparent);
119
+ --shadow-nav-current: 0 1px 2px rgb(0 0 0 / 25%);
109
120
  --color-text: #e6e9eb;
110
121
  --color-muted: #9aa3ab;
111
122
  --color-link: #67c1df;
@@ -144,6 +155,8 @@
144
155
  --color-shadow: rgb(0 0 0 / 32%);
145
156
  --color-overlay: rgb(0 0 0 / 62%);
146
157
  --shadow-dialog: 0 8px 24px rgb(0 0 0 / 45%), 0 0 0 1px var(--color-border-subtle);
158
+ --shadow-raise: 2px 2px 6px rgb(0 0 0 / 50%), -2px -2px 6px rgb(255 255 255 / 5%);
159
+ --shadow-sink: inset 1.2px 1.2px 3.6px rgb(0 0 0 / 55%), inset -1.2px -1.2px 3.6px rgb(255 255 255 / 5%);
147
160
 
148
161
  --color-project: #a5a1ff;
149
162
  --color-accent-text: var(--color-bg);