@stridge/noctis 1.0.0-beta.4 → 1.0.0-beta.6

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 (53) hide show
  1. package/dist/components/breadcrumb/breadcrumb.d.ts +163 -0
  2. package/dist/components/breadcrumb/breadcrumb.js +152 -0
  3. package/dist/components/breadcrumb/breadcrumb.props.d.ts +59 -0
  4. package/dist/components/breadcrumb/breadcrumb.props.js +68 -0
  5. package/dist/components/breadcrumb/breadcrumb.slots.d.ts +16 -0
  6. package/dist/components/breadcrumb/breadcrumb.slots.js +32 -0
  7. package/dist/components/breadcrumb/breadcrumb.types.d.ts +9 -0
  8. package/dist/components/breadcrumb/index.d.ts +3 -0
  9. package/dist/components/command/command-listbox.js +174 -0
  10. package/dist/components/command/command-rank.d.ts +40 -0
  11. package/dist/components/command/command-rank.js +61 -0
  12. package/dist/components/command/command-score.d.ts +25 -0
  13. package/dist/components/command/command-score.js +85 -0
  14. package/dist/components/command/command.context.d.ts +17 -0
  15. package/dist/components/command/command.context.js +13 -0
  16. package/dist/components/command/command.d.ts +396 -0
  17. package/dist/components/command/command.js +471 -0
  18. package/dist/components/command/command.props.d.ts +91 -0
  19. package/dist/components/command/command.props.js +94 -0
  20. package/dist/components/command/command.slots.d.ts +23 -0
  21. package/dist/components/command/command.slots.js +60 -0
  22. package/dist/components/command/index.d.ts +6 -0
  23. package/dist/components/command/use-command-ranking.d.ts +37 -0
  24. package/dist/components/command/use-command-ranking.js +127 -0
  25. package/dist/components/pagination/index.d.ts +3 -0
  26. package/dist/components/pagination/pagination.context.js +16 -0
  27. package/dist/components/pagination/pagination.d.ts +217 -0
  28. package/dist/components/pagination/pagination.js +333 -0
  29. package/dist/components/pagination/pagination.props.d.ts +51 -0
  30. package/dist/components/pagination/pagination.props.js +49 -0
  31. package/dist/components/pagination/pagination.slots.d.ts +16 -0
  32. package/dist/components/pagination/pagination.slots.js +32 -0
  33. package/dist/components/pagination/pagination.types.d.ts +24 -0
  34. package/dist/components/search-dialog/parts/root.js +1 -1
  35. package/dist/components/skeleton/index.d.ts +3 -0
  36. package/dist/components/skeleton/skeleton.context.js +12 -0
  37. package/dist/components/skeleton/skeleton.d.ts +157 -0
  38. package/dist/components/skeleton/skeleton.js +130 -0
  39. package/dist/components/skeleton/skeleton.props.d.ts +47 -0
  40. package/dist/components/skeleton/skeleton.props.js +57 -0
  41. package/dist/components/skeleton/skeleton.slots.d.ts +15 -0
  42. package/dist/components/skeleton/skeleton.slots.js +28 -0
  43. package/dist/components/skeleton/skeleton.types.d.ts +13 -0
  44. package/dist/components/surface/surface.d.ts +1 -1
  45. package/dist/icons/glyphs.js +2 -2
  46. package/dist/index.d.ts +18 -3
  47. package/dist/index.js +15 -4
  48. package/dist/primitives/index.d.ts +1 -1
  49. package/dist/primitives/index.js +2 -2
  50. package/dist/props.d.ts +37 -33
  51. package/dist/props.js +37 -33
  52. package/dist/styles.css +841 -6
  53. package/package.json +4 -4
package/dist/styles.css CHANGED
@@ -1705,6 +1705,29 @@
1705
1705
  --_toolbar-input-height: var(--noctis-toolbar-input-height, var(--noctis-size-control-xs));
1706
1706
  --_toolbar-input-padding-inline: var(--noctis-toolbar-input-padding-inline, var(--noctis-space-1\.5));
1707
1707
  }
1708
+ [data-slot="noctis-pagination"] {
1709
+ --_pagination-gap: var(--noctis-pagination-gap, var(--noctis-space-3));
1710
+ }
1711
+ [data-slot="noctis-pagination-info"] {
1712
+ --_pagination-info-font-size: var(--noctis-pagination-info-font-size, var(--noctis-text-small));
1713
+ --_pagination-info-color: var(--noctis-pagination-info-color, var(--noctis-color-muted));
1714
+ }
1715
+ [data-slot="noctis-pagination-page-size"] {
1716
+ --_pagination-page-size-gap: var(--noctis-pagination-page-size-gap, var(--noctis-space-2));
1717
+ }
1718
+ [data-slot="noctis-pagination-page"] {
1719
+ --_pagination-page-gap: var(--noctis-pagination-page-gap, var(--noctis-space-2));
1720
+ --_pagination-page-inline-size: var(--noctis-pagination-page-inline-size, 3.5rem);
1721
+ --_pagination-page-block-size: var(--noctis-pagination-page-block-size, var(--noctis-size-control-md));
1722
+ }
1723
+ [data-slot="noctis-pagination-separator"] {
1724
+ --_pagination-separator-color: var(--noctis-pagination-separator-color, var(--noctis-color-border));
1725
+ --_pagination-separator-inline-size: var(--noctis-pagination-separator-inline-size, 1px);
1726
+ --_pagination-separator-block-size: var(--noctis-pagination-separator-block-size, 1.5rem);
1727
+ }
1728
+ [data-slot="noctis-pagination"][data-size="sm"] [data-slot="noctis-pagination-page"] {
1729
+ --_pagination-page-block-size: var(--noctis-pagination-page-block-size, var(--noctis-size-control-sm));
1730
+ }
1708
1731
  [data-slot="noctis-accordion-trigger"] {
1709
1732
  --_accordion-trigger-padding-inline: var(--noctis-accordion-trigger-padding-inline, 0);
1710
1733
  --_accordion-trigger-padding-block: var(--noctis-accordion-trigger-padding-block, var(--noctis-space-4));
@@ -2108,6 +2131,45 @@
2108
2131
  --_combobox-input-height: var(--noctis-combobox-input-height, var(--noctis-size-control-lg));
2109
2132
  --_combobox-input-padding-inline: var(--noctis-combobox-input-padding-inline, 0.875rem);
2110
2133
  }
2134
+ [data-slot="noctis-command"] {
2135
+ --_command-inset-block-start: var(--noctis-command-inset-block-start, 12vh);
2136
+ --_command-height: var(--noctis-command-height, min(60vh, 460px));
2137
+ --_command-width: var(--noctis-command-width, min(40rem, 92vw));
2138
+ --_command-border-radius: var(--noctis-command-border-radius, var(--noctis-radius-md));
2139
+ --_command-transition-duration: var(--noctis-command-transition-duration, var(--noctis-duration-overlay));
2140
+ }
2141
+ [data-slot="noctis-command-input"] {
2142
+ --_command-input-height: var(--noctis-command-input-height, 3rem);
2143
+ }
2144
+ [data-slot="noctis-command-list"] {
2145
+ --_command-list-padding: var(--noctis-command-list-padding, 0.375rem);
2146
+ --_command-list-height: var(--noctis-command-list-height, auto);
2147
+ --_command-list-transition-duration: var(--noctis-command-list-transition-duration, var(--noctis-duration-regular));
2148
+ }
2149
+ [data-slot="noctis-command-item"] {
2150
+ --_command-item-height: var(--noctis-command-item-height, 2.5rem);
2151
+ --_command-item-padding-inline: var(--noctis-command-item-padding-inline, 0.375rem);
2152
+ --_command-item-padding-block: var(--noctis-command-item-padding-block, 0.25rem);
2153
+ --_command-item-gap: var(--noctis-command-item-gap, 0.5rem);
2154
+ --_command-item-border-radius: var(--noctis-command-item-border-radius, var(--noctis-radius-sm));
2155
+ --_command-item-color: var(--noctis-command-item-color, var(--noctis-color-secondary));
2156
+ --_command-item-color-highlighted: var(--noctis-command-item-color-highlighted, var(--noctis-color-foreground));
2157
+ --_command-item-background-color-highlighted: var(
2158
+ --noctis-command-item-background-color-highlighted,
2159
+ var(--noctis-color-control-ghost-hover)
2160
+ );
2161
+ --_command-item-background-color-active: var(
2162
+ --noctis-command-item-background-color-active,
2163
+ var(--noctis-color-control-ghost-selected)
2164
+ );
2165
+ }
2166
+ [data-slot="noctis-command-empty"] {
2167
+ --_command-empty-padding-block: var(--noctis-command-empty-padding-block, var(--noctis-space-6));
2168
+ }
2169
+ [data-slot="noctis-command-group-label"] {
2170
+ --_command-group-label-padding-inline: var(--noctis-command-group-label-padding-inline, var(--noctis-space-2\.5));
2171
+ --_command-group-label-padding-inline-start: var(--noctis-command-group-label-padding-inline-start, 0.375rem);
2172
+ }
2111
2173
  [data-slot="noctis-search-dialog"] {
2112
2174
  --_search-dialog-inset-block-start: var(--noctis-search-dialog-inset-block-start, 12vh);
2113
2175
  --_search-dialog-height: var(--noctis-search-dialog-height, min(56vh, 420px));
@@ -2466,6 +2528,62 @@
2466
2528
  [data-slot="noctis-badge"][data-size="sm"] [data-slot="noctis-badge-icon"] {
2467
2529
  --_badge-icon-size: var(--noctis-badge-icon-size, 0.75rem);
2468
2530
  }
2531
+ [data-slot="noctis-skeleton"] {
2532
+ --_skeleton-root-gap: var(--noctis-skeleton-root-gap, var(--noctis-space-3));
2533
+ }
2534
+ [data-slot="noctis-skeleton-box"],
2535
+ [data-slot="noctis-skeleton-circle"],
2536
+ [data-slot="noctis-skeleton-line"] {
2537
+ --_skeleton-fill-background-color: var(--noctis-skeleton-fill-background-color, var(--noctis-color-well));
2538
+ --_skeleton-shimmer-background-color: var(--noctis-skeleton-shimmer-background-color, var(--noctis-color-surface-raised));
2539
+ --_skeleton-shimmer-animation-duration: var(
2540
+ --noctis-skeleton-shimmer-animation-duration,
2541
+ calc(var(--noctis-duration-slow) * 4)
2542
+ );
2543
+ }
2544
+ [data-slot="noctis-skeleton-box"] {
2545
+ --_skeleton-box-block-size: var(--noctis-skeleton-box-block-size, 1.25rem);
2546
+ --_skeleton-box-border-radius: var(--noctis-skeleton-box-border-radius, var(--noctis-radius-md));
2547
+ }
2548
+ [data-slot="noctis-skeleton-circle"] {
2549
+ --_skeleton-circle-size: var(--noctis-skeleton-circle-size, 2.5rem);
2550
+ }
2551
+ [data-slot="noctis-skeleton-line"] {
2552
+ --_skeleton-line-block-size: var(--noctis-skeleton-line-block-size, 0.7em);
2553
+ --_skeleton-line-border-radius: var(--noctis-skeleton-line-border-radius, var(--noctis-radius-sm));
2554
+ }
2555
+ [data-slot="noctis-skeleton-text"] {
2556
+ --_skeleton-text-gap: var(--noctis-skeleton-text-gap, var(--noctis-space-2));
2557
+ }
2558
+ [data-slot="noctis-breadcrumb"] {
2559
+ --_breadcrumb-gap: var(--noctis-breadcrumb-gap, 0.375rem);
2560
+ --_breadcrumb-font-size: var(--noctis-breadcrumb-font-size, var(--noctis-text-small));
2561
+ }
2562
+ [data-slot="noctis-breadcrumb-list"] {
2563
+ --_breadcrumb-list-gap: var(--noctis-breadcrumb-list-gap, 0.375rem);
2564
+ }
2565
+ [data-slot="noctis-breadcrumb-icon"] {
2566
+ --_breadcrumb-icon-size: var(--noctis-breadcrumb-icon-size, 1rem);
2567
+ }
2568
+ [data-slot="noctis-breadcrumb-separator"] {
2569
+ --_breadcrumb-separator-size: var(--noctis-breadcrumb-separator-size, 1rem);
2570
+ }
2571
+ [data-slot="noctis-breadcrumb-link"] {
2572
+ --_breadcrumb-link-border-radius: var(--noctis-breadcrumb-link-border-radius, var(--noctis-radius-sm));
2573
+ }
2574
+ [data-slot="noctis-breadcrumb"][data-size="sm"] {
2575
+ --_breadcrumb-gap: var(--noctis-breadcrumb-gap, 0.25rem);
2576
+ --_breadcrumb-font-size: var(--noctis-breadcrumb-font-size, var(--noctis-text-mini));
2577
+ }
2578
+ [data-slot="noctis-breadcrumb"][data-size="sm"] [data-slot="noctis-breadcrumb-list"] {
2579
+ --_breadcrumb-list-gap: var(--noctis-breadcrumb-list-gap, 0.25rem);
2580
+ }
2581
+ [data-slot="noctis-breadcrumb"][data-size="sm"] [data-slot="noctis-breadcrumb-icon"] {
2582
+ --_breadcrumb-icon-size: var(--noctis-breadcrumb-icon-size, 0.875rem);
2583
+ }
2584
+ [data-slot="noctis-breadcrumb"][data-size="sm"] [data-slot="noctis-breadcrumb-separator"] {
2585
+ --_breadcrumb-separator-size: var(--noctis-breadcrumb-separator-size, 0.875rem);
2586
+ }
2469
2587
  [data-slot="noctis-otp-field"] {
2470
2588
  --_otp-field-gap: var(--noctis-otp-field-gap, 0.5rem);
2471
2589
  }
@@ -4072,6 +4190,122 @@
4072
4190
  }
4073
4191
  }
4074
4192
 
4193
+ @layer noctis.components {
4194
+ /*
4195
+ * Breadcrumb is self-contained, so every rule keys off the element's own `data-slot`. The metrics
4196
+ * flow through the `--_breadcrumb-*` internals, whose per-size defaults live in the generated
4197
+ * component layer (`tokens.css`) keyed off the `data-size` the root stamps. Colours aren't minted —
4198
+ * the link/page/separator read the `muted`/`foreground`/`subtle` roles directly, so a retheme
4199
+ * propagates for free.
4200
+ *
4201
+ * The root declares `--_breadcrumb-gap` (the icon↔label gap) and `--_breadcrumb-font-size`; both
4202
+ * inherit down to every crumb. The list, icon, and separator carry their own per-size internals.
4203
+ */
4204
+
4205
+ /* root — the nav landmark. Sets the trail's type and a calm default colour the crumbs override. */
4206
+ [data-slot="noctis-breadcrumb"] {
4207
+ font-family: var(--noctis-font-sans);
4208
+ font-size: var(--_breadcrumb-font-size);
4209
+ line-height: 1;
4210
+ color: var(--noctis-color-muted);
4211
+ }
4212
+
4213
+ /* list — the ordered row of crumbs and separators. */
4214
+ [data-slot="noctis-breadcrumb-list"] {
4215
+ display: flex;
4216
+ flex-wrap: wrap;
4217
+ align-items: center;
4218
+ gap: var(--_breadcrumb-list-gap);
4219
+ margin-block: 0;
4220
+ margin-inline: 0;
4221
+ padding-block: 0;
4222
+ padding-inline: 0;
4223
+ list-style: none;
4224
+ }
4225
+
4226
+ /* item — wraps a single crumb (a link or the current page). */
4227
+ [data-slot="noctis-breadcrumb-item"] {
4228
+ display: inline-flex;
4229
+ align-items: center;
4230
+ }
4231
+
4232
+ /* link — an interactive ancestor crumb. Muted by default, lifting to the foreground on hover/focus. */
4233
+ [data-slot="noctis-breadcrumb-link"] {
4234
+ display: inline-flex;
4235
+ align-items: center;
4236
+ gap: var(--_breadcrumb-gap);
4237
+ border-radius: var(--_breadcrumb-link-border-radius);
4238
+ color: var(--noctis-color-muted);
4239
+ text-decoration: none;
4240
+ cursor: pointer;
4241
+ transition: color var(--noctis-duration-quick) var(--noctis-ease-out);
4242
+ }
4243
+
4244
+ [data-slot="noctis-breadcrumb-link"]:hover {
4245
+ color: var(--noctis-color-foreground);
4246
+ }
4247
+
4248
+ [data-slot="noctis-breadcrumb-link"]:focus-visible {
4249
+ outline: var(--noctis-size-focus-ring-width) solid var(--noctis-color-ring);
4250
+ outline-offset: var(--noctis-size-focus-ring-offset);
4251
+ }
4252
+
4253
+ /* page — the current page crumb. Reads in the foreground at a slightly heavier weight. */
4254
+ [data-slot="noctis-breadcrumb-page"] {
4255
+ display: inline-flex;
4256
+ align-items: center;
4257
+ gap: var(--_breadcrumb-gap);
4258
+ color: var(--noctis-color-foreground);
4259
+ font-weight: 500;
4260
+ }
4261
+
4262
+ /* separator — the presentational glyph between crumbs. Subtle, clamped to the separator size. */
4263
+ [data-slot="noctis-breadcrumb-separator"] {
4264
+ display: inline-flex;
4265
+ flex-shrink: 0;
4266
+ align-items: center;
4267
+ color: var(--noctis-color-subtle);
4268
+ }
4269
+
4270
+ [data-slot="noctis-breadcrumb-separator"] svg {
4271
+ inline-size: var(--_breadcrumb-separator-size);
4272
+ block-size: var(--_breadcrumb-separator-size);
4273
+ }
4274
+
4275
+ /* ellipsis — the collapsed-run indicator. Muted, clamped to the crumb icon size. */
4276
+ [data-slot="noctis-breadcrumb-ellipsis"] {
4277
+ display: inline-flex;
4278
+ flex-shrink: 0;
4279
+ align-items: center;
4280
+ justify-content: center;
4281
+ color: var(--noctis-color-muted);
4282
+ }
4283
+
4284
+ [data-slot="noctis-breadcrumb-ellipsis"] svg {
4285
+ inline-size: var(--_breadcrumb-icon-size);
4286
+ block-size: var(--_breadcrumb-icon-size);
4287
+ }
4288
+
4289
+ /* icon — a crumb's leading glyph, clamped to the icon size; inherits the crumb's text colour. */
4290
+ [data-slot="noctis-breadcrumb-icon"] {
4291
+ display: inline-flex;
4292
+ flex-shrink: 0;
4293
+ align-items: center;
4294
+ justify-content: center;
4295
+ }
4296
+
4297
+ [data-slot="noctis-breadcrumb-icon"] svg {
4298
+ inline-size: var(--_breadcrumb-icon-size);
4299
+ block-size: var(--_breadcrumb-icon-size);
4300
+ }
4301
+
4302
+ @media (prefers-reduced-motion: reduce) {
4303
+ [data-slot="noctis-breadcrumb-link"] {
4304
+ transition: none;
4305
+ }
4306
+ }
4307
+ }
4308
+
4075
4309
  @layer noctis.components {
4076
4310
  /*
4077
4311
  * Button is a class-escape-hatch primitive: `Button.props()` styles a *foreign* element (the rail's
@@ -4312,8 +4546,9 @@
4312
4546
  * menu trigger's popup is open (and a `Menu.Trigger` rendered as a `Button` carries
4313
4547
  * `data-slot="noctis-menu-trigger"`, not `"noctis-button"`), so keying on the marker is what keeps the
4314
4548
  * seam on exactly the real segments. The general-sibling (`~`) and `:has()` selectors look through any
4315
- * guard between two segments. Logical properties throughout (`margin-inline-start`, `inset-inline-start`),
4316
- * so the whole thing mirrors under RTL. The divider's width and colour flow through the
4549
+ * guard between two segments. Logical properties throughout (`border-inline-start-width`,
4550
+ * `inset-inline-start`), so the whole thing mirrors under RTL. The divider's width and colour flow
4551
+ * through the
4317
4552
  * `--_button-group-seam-*` internals declared on the group slot in the generated layer, so
4318
4553
  * `--noctis-button-group-seam-*` retunes it; the high-contrast `strong` border token reads clearly
4319
4554
  * across every button variant.
@@ -4347,19 +4582,24 @@
4347
4582
  }
4348
4583
 
4349
4584
  /*
4350
- * Square each later segment's inner-start corner, and pull it onto the previous segment's border so
4351
- * the two share one edge. Anchored on the group root, reaching only its direct-child marked segments.
4585
+ * Weld the segments: square each junction's corners and DROP the touching (inner) borders, so the
4586
+ * seam drawn below is the single divider. A bordered variant (secondary/outline) used to keep its
4587
+ * own 1px border at the junction *and* get the seam on top — two lines, a doubled divider; dropping
4588
+ * the inner borders leaves exactly the seam. Segments sit flush (no negative margin); a borderless
4589
+ * variant (primary/ghost/…) has no inner border to drop, so it is unaffected and the seam still
4590
+ * divides it. Anchored on the group root, reaching only its direct-child marked segments.
4352
4591
  */
4353
4592
  [data-slot="noctis-button-group"] > [data-button-group-item] ~ [data-button-group-item] {
4354
- margin-inline-start: -1px;
4355
4593
  border-start-start-radius: 0;
4356
4594
  border-end-start-radius: 0;
4595
+ border-inline-start-width: 0;
4357
4596
  }
4358
4597
 
4359
- /* Square the inner-end corner of every segment that has a later sibling segment. */
4598
+ /* Square the inner-end corner of every segment that has a later sibling, and drop its inner-end border. */
4360
4599
  [data-slot="noctis-button-group"] > [data-button-group-item]:has(~ [data-button-group-item]) {
4361
4600
  border-start-end-radius: 0;
4362
4601
  border-end-end-radius: 0;
4602
+ border-inline-end-width: 0;
4363
4603
  }
4364
4604
 
4365
4605
  /*
@@ -5953,6 +6193,377 @@
5953
6193
  }
5954
6194
  }
5955
6195
 
6196
+ @layer noctis.components {
6197
+ /*
6198
+ * Command — an accessible command palette. The panel renders through `Surface` (its stable
6199
+ * `data-surface` marker paints the background, border, and shadow, and stamps the `menu` elevation
6200
+ * scope), so this file styles only the palette's own slots: layout, the input row, the rows and
6201
+ * sections, the messages, the drill-in breadcrumb, the footer hints, and the modal scale-fade.
6202
+ *
6203
+ * One panel slot (`command`) serves both mounting modes. The modal `Command.Dialog` stamps
6204
+ * `data-modal` on the panel, which the geometry rules gate on (fixed, centred, the scale-fade); the
6205
+ * inline `Command.Root` omits it, so the same panel sits in normal flow and only the shared metrics
6206
+ * (radius, capped height, the rows) apply. Both modes leave the panel auto-height: it grows and
6207
+ * shrinks with its list, which animates its own height as the result set changes (the list feeds its
6208
+ * measured content height into `--_command-list-height`), so the palette glides between sizes rather
6209
+ * than jumping. Every rule keys off the prefixed `data-slot` anchor (no `:where()`), so the cascade
6210
+ * layer alone is the override seam; identity properties read a `--_command-*` internal or a semantic
6211
+ * `var(--noctis-*)` role, never a literal.
6212
+ */
6213
+
6214
+ /* backdrop — the blurred scrim behind the modal palette, fading with the panel. The 2px blur is a
6215
+ * non-token geometric literal (a filter radius), the same class of value as the panel's transition
6216
+ * offsets. */
6217
+ [data-slot="noctis-command-backdrop"] {
6218
+ position: fixed;
6219
+ inset: 0;
6220
+ z-index: var(--noctis-z-modal);
6221
+ background-color: var(--noctis-color-overlay);
6222
+ backdrop-filter: blur(2px);
6223
+ transition: opacity var(--noctis-duration-overlay) var(--noctis-ease-overlay);
6224
+ }
6225
+
6226
+ [data-slot="noctis-command-backdrop"][data-starting-style],
6227
+ [data-slot="noctis-command-backdrop"][data-ending-style] {
6228
+ opacity: 0;
6229
+ }
6230
+
6231
+ /* panel — the column holding the input row over the scrolling list over the optional footer. The
6232
+ * shared shape (rounded, clipping its scroll, capped height) applies in both modes; the paint comes
6233
+ * from the composed Surface. */
6234
+ [data-slot="noctis-command"] {
6235
+ display: flex;
6236
+ min-block-size: 0;
6237
+ max-block-size: var(--_command-height);
6238
+ inline-size: 100%;
6239
+ flex-direction: column;
6240
+ overflow: hidden;
6241
+ border-radius: var(--_command-border-radius);
6242
+ outline: none;
6243
+ }
6244
+
6245
+ /* the modal panel: centred, top-anchored, auto-height (capped by the shared `max-block-size` so it
6246
+ * stays on-screen; the list scrolls past that), and the scale-fade enter/exit. The transition
6247
+ * animates the individual properties (not `transform`) so the close doesn't snap. */
6248
+ [data-slot="noctis-command"][data-modal] {
6249
+ position: fixed;
6250
+ inset-inline: 0;
6251
+ inset-block-start: var(--_command-inset-block-start);
6252
+ z-index: var(--noctis-z-modal);
6253
+ margin-inline: auto;
6254
+ inline-size: var(--_command-width);
6255
+ transform-origin: top;
6256
+ transition:
6257
+ opacity var(--_command-transition-duration) var(--noctis-ease-overlay),
6258
+ translate var(--_command-transition-duration) var(--noctis-ease-overlay),
6259
+ scale var(--_command-transition-duration) var(--noctis-ease-overlay);
6260
+ }
6261
+
6262
+ [data-slot="noctis-command"][data-modal][data-starting-style] {
6263
+ translate: 0 -0.75rem;
6264
+ scale: 0.96;
6265
+ opacity: 0;
6266
+ }
6267
+
6268
+ [data-slot="noctis-command"][data-modal][data-ending-style] {
6269
+ translate: 0 -0.375rem;
6270
+ scale: 0.97;
6271
+ opacity: 0;
6272
+ }
6273
+
6274
+ /* header — the input row: an optional drill-in breadcrumb and leading glyph, the query field, and an
6275
+ * optional trailing action. No divider under it — the input reads as one surface with the list. The
6276
+ * inline padding lines the leading glyph up with the rows' icon column (list padding + row padding),
6277
+ * and the gap matches a row's icon→label gap, so the input and the rows share one left rhythm. */
6278
+ [data-slot="noctis-command-header"] {
6279
+ display: flex;
6280
+ flex-shrink: 0;
6281
+ align-items: center;
6282
+ gap: var(--noctis-space-2);
6283
+ padding-inline: var(--noctis-space-3);
6284
+ }
6285
+
6286
+ /* the leading search glyph (an un-slotted Icon in the header). */
6287
+ [data-slot="noctis-command-header"] > svg {
6288
+ flex-shrink: 0;
6289
+ color: var(--noctis-color-muted);
6290
+ }
6291
+
6292
+ /* breadcrumb — the drill-in trail shown before the field once the palette has been pushed into a
6293
+ * sub-page; each segment reads as quiet meta, not a control. */
6294
+ [data-slot="noctis-command-breadcrumb"] {
6295
+ display: inline-flex;
6296
+ flex-shrink: 0;
6297
+ align-items: center;
6298
+ gap: var(--noctis-space-2);
6299
+ font-size: var(--noctis-text-small);
6300
+ line-height: var(--noctis-leading-small);
6301
+ color: var(--noctis-color-muted);
6302
+ }
6303
+
6304
+ /* one breadcrumb segment: a soft chip on the ghost role so the trail lifts off the panel; clicking
6305
+ * it truncates the drill-in stack back to that depth. */
6306
+ [data-slot="noctis-command-breadcrumb"] > button {
6307
+ display: inline-flex;
6308
+ cursor: pointer;
6309
+ align-items: center;
6310
+ border: none;
6311
+ border-radius: var(--noctis-radius-sm);
6312
+ background-color: var(--noctis-color-control-ghost-hover);
6313
+ padding-inline: var(--noctis-space-2);
6314
+ padding-block: var(--noctis-space-1);
6315
+ font: inherit;
6316
+ color: var(--noctis-color-foreground);
6317
+ outline: none;
6318
+ transition: background-color var(--noctis-duration-quick) var(--noctis-ease-out);
6319
+ }
6320
+
6321
+ [data-slot="noctis-command-breadcrumb"] > button:hover {
6322
+ background-color: var(--noctis-color-control-ghost-selected);
6323
+ }
6324
+
6325
+ [data-slot="noctis-command-breadcrumb"] > button:focus-visible {
6326
+ outline: var(--noctis-size-focus-ring-width) solid var(--noctis-color-ring);
6327
+ outline-offset: var(--noctis-size-focus-ring-width);
6328
+ }
6329
+
6330
+ /* input — the query field: transparent, filling the header, with the native clear button hidden. */
6331
+ [data-slot="noctis-command-input"] {
6332
+ block-size: var(--_command-input-height);
6333
+ min-inline-size: 0;
6334
+ flex: 1;
6335
+ border: none;
6336
+ background-color: transparent;
6337
+ font-size: var(--noctis-text-regular);
6338
+ line-height: var(--noctis-leading-regular);
6339
+ color: var(--noctis-color-foreground);
6340
+ outline: none;
6341
+ }
6342
+
6343
+ [data-slot="noctis-command-input"]::placeholder {
6344
+ color: var(--noctis-color-placeholder);
6345
+ }
6346
+
6347
+ [data-slot="noctis-command-input"]::-webkit-search-cancel-button {
6348
+ appearance: none;
6349
+ }
6350
+
6351
+ /* input-action — the trailing affordance slot in the input row (a mode pill, a hint, an action). */
6352
+ [data-slot="noctis-command-input-action"] {
6353
+ display: inline-flex;
6354
+ flex-shrink: 0;
6355
+ align-items: center;
6356
+ gap: var(--noctis-space-2);
6357
+ font-size: var(--noctis-text-mini);
6358
+ line-height: var(--noctis-leading-mini);
6359
+ color: var(--noctis-color-muted);
6360
+ }
6361
+
6362
+ /* list — the scrolling listbox and its thin scrollbar. Its `block-size` tracks the measured content
6363
+ * height (the hook writes `--_command-list-height`) and tweens between sizes, so the auto-height
6364
+ * panel grows and shrinks with the rows. `flex: 0 1` lets it shrink — but never grow past its
6365
+ * content — so once the panel hits its cap the list yields and scrolls instead of overflowing.
6366
+ *
6367
+ * The scrollbar is thin and themed (the shared menu-scope thumb role) with no reserved gutter, so it
6368
+ * appears only when the rows overflow the capped panel and a short list shows no empty track.
6369
+ * `scroll-padding-block` keeps the inner list padding in view so the keyboard never parks the first or
6370
+ * last row flush against an edge (which read as a cropped row) — the end rows stop a padding short. */
6371
+ [data-slot="noctis-command-list"] {
6372
+ min-block-size: 0;
6373
+ flex: 0 1 auto;
6374
+ overflow-y: auto;
6375
+ block-size: var(--_command-list-height, auto);
6376
+ scroll-padding-block: var(--_command-list-padding);
6377
+ scrollbar-width: thin;
6378
+ scrollbar-color: var(--noctis-color-scrollbar-thumb) transparent;
6379
+ transition: block-size var(--_command-list-transition-duration) var(--noctis-ease-out);
6380
+ }
6381
+
6382
+ /* sizer — the measured content wrapper inside the scroll viewport. It carries the list's inner
6383
+ * padding (so the scrollbar rides outside it) and its natural height is what the hook observes. */
6384
+ [data-slot="noctis-command-list-sizer"] {
6385
+ padding: var(--_command-list-padding);
6386
+ }
6387
+
6388
+ /* list-view — the per-page view. Remounted on every drill in/out (keyed by depth), so this enter
6389
+ * animation replays each time: the new view fades in as the old one gives way (a pure opacity
6390
+ * cross-fade, like the reference — no slide, no scale, so it never fights the panel's height tween).
6391
+ * Opacity only, so the sizer's measured height is untouched. */
6392
+ [data-slot="noctis-command-list-view"] {
6393
+ animation: noctis-command-view-enter var(--noctis-duration-regular) var(--noctis-ease-out);
6394
+ }
6395
+
6396
+ @keyframes noctis-command-view-enter {
6397
+ from {
6398
+ opacity: 0;
6399
+ }
6400
+
6401
+ to {
6402
+ opacity: 1;
6403
+ }
6404
+ }
6405
+
6406
+ /* group — a labelled section wrapper (no box of its own). */
6407
+ [data-slot="noctis-command-group"] {
6408
+ display: block;
6409
+ }
6410
+
6411
+ /*
6412
+ * group-label — a section heading, deliberately NOT an option. The cues are script-independent (no
6413
+ * casing — Noctis is localized): the smallest type, the most muted foreground, a gap above. Its text
6414
+ * is outdented to the row's content edge, left of the option-label column.
6415
+ */
6416
+ [data-slot="noctis-command-group-label"] {
6417
+ padding-inline: var(--_command-group-label-padding-inline-start) var(--_command-group-label-padding-inline);
6418
+ padding-block: var(--noctis-space-3) var(--noctis-space-1);
6419
+ font-size: var(--noctis-text-micro);
6420
+ line-height: var(--noctis-leading-micro);
6421
+ color: var(--noctis-color-subtle);
6422
+ }
6423
+
6424
+ /*
6425
+ * item — one command row. Dimensions/colours flow through the `--_command-item-*` internals. The
6426
+ * highlight (neutral, signal-free) appears instantly on the keyboard- or pointer-active row and
6427
+ * fades out over the quick stop; the accent stays a signal, never a hover.
6428
+ */
6429
+ [data-slot="noctis-command-item"] {
6430
+ display: flex;
6431
+ min-block-size: var(--_command-item-height);
6432
+ cursor: pointer;
6433
+ align-items: center;
6434
+ gap: var(--_command-item-gap);
6435
+ border-radius: var(--_command-item-border-radius);
6436
+ padding-block: var(--_command-item-padding-block);
6437
+ padding-inline: var(--_command-item-padding-inline);
6438
+ font-size: var(--noctis-text-small);
6439
+ line-height: var(--noctis-leading-small);
6440
+ color: var(--_command-item-color);
6441
+ outline: none;
6442
+ user-select: none;
6443
+ transition-property: color, background-color;
6444
+ transition-timing-function: var(--noctis-ease-out);
6445
+ transition-duration: var(--noctis-duration-quick);
6446
+ }
6447
+
6448
+ [data-slot="noctis-command-item"][data-highlighted] {
6449
+ transition-duration: 0s;
6450
+ }
6451
+
6452
+ [data-slot="noctis-command-item"]:not([data-disabled]):hover,
6453
+ [data-slot="noctis-command-item"]:not([data-disabled])[data-highlighted] {
6454
+ background-color: var(--_command-item-background-color-highlighted);
6455
+ color: var(--_command-item-color-highlighted);
6456
+ }
6457
+
6458
+ /* a firmer fill while a row is pressed, as tactile feedback before it commits. */
6459
+ [data-slot="noctis-command-item"]:not([data-disabled]):active {
6460
+ background-color: var(--_command-item-background-color-active);
6461
+ }
6462
+
6463
+ [data-slot="noctis-command-item"][data-disabled] {
6464
+ cursor: not-allowed;
6465
+ opacity: var(--noctis-opacity-disabled);
6466
+ }
6467
+
6468
+ /* item-icon — the leading command glyph, quieter than the label. Also catches a bare `Icon`. */
6469
+ [data-slot="noctis-command-item-icon"],
6470
+ [data-slot="noctis-command-item"] > [data-slot="noctis-icon"] {
6471
+ display: inline-flex;
6472
+ flex-shrink: 0;
6473
+ color: var(--noctis-color-muted);
6474
+ }
6475
+
6476
+ /* item-label — the row's text column; truncates rather than wrapping. Its `flex` also claims the
6477
+ * row's free space, so any trailing content the consumer adds after it (a `Kbd` shortcut, a badge, a
6478
+ * count) is pushed to the row's end — the component itself owns no trailing slot. */
6479
+ [data-slot="noctis-command-item-label"] {
6480
+ min-inline-size: 0;
6481
+ flex: 1;
6482
+ overflow: hidden;
6483
+ text-overflow: ellipsis;
6484
+ white-space: nowrap;
6485
+ color: inherit;
6486
+ }
6487
+
6488
+ /* separator — a thin neutral divider between sections (the menu separator idiom). */
6489
+ [data-slot="noctis-command-separator"] {
6490
+ block-size: 1px;
6491
+ margin-block: var(--noctis-space-1);
6492
+ background-color: var(--noctis-color-border);
6493
+ }
6494
+
6495
+ /*
6496
+ * empty — the centred no-results message. Base UI keeps the element mounted as a live region but
6497
+ * renders its children only while the list is empty, so the styling is gated on `:not(:empty)` —
6498
+ * otherwise the childless element would reserve its block padding and open a gap above the list.
6499
+ */
6500
+ [data-slot="noctis-command-empty"]:not(:empty) {
6501
+ display: flex;
6502
+ flex: 1;
6503
+ align-items: center;
6504
+ justify-content: center;
6505
+ padding-block: var(--_command-empty-padding-block);
6506
+ padding-inline: var(--noctis-space-2);
6507
+ text-align: center;
6508
+ font-size: var(--noctis-text-small);
6509
+ line-height: var(--noctis-leading-small);
6510
+ color: var(--noctis-color-muted);
6511
+ }
6512
+
6513
+ /* loading — the centred async progress shell shown before any results settle; the spinner glyph
6514
+ * rides the shared spin token. */
6515
+ [data-slot="noctis-command-loading"] {
6516
+ display: flex;
6517
+ align-items: center;
6518
+ justify-content: center;
6519
+ gap: var(--noctis-space-2);
6520
+ padding-block: var(--_command-empty-padding-block);
6521
+ font-size: var(--noctis-text-small);
6522
+ line-height: var(--noctis-leading-small);
6523
+ color: var(--noctis-color-muted);
6524
+ }
6525
+
6526
+ [data-slot="noctis-command-loading"] > svg {
6527
+ flex-shrink: 0;
6528
+ animation: var(--noctis-animate-spin);
6529
+ color: var(--noctis-color-muted);
6530
+ }
6531
+
6532
+ /* footer — a bare, end-aligned region below the list (a quiet, bordered strip). Unopinionated about
6533
+ * its contents: the consumer composes whatever status or hints go here. */
6534
+ [data-slot="noctis-command-footer"] {
6535
+ display: flex;
6536
+ flex-shrink: 0;
6537
+ align-items: center;
6538
+ justify-content: flex-end;
6539
+ gap: var(--noctis-space-3);
6540
+ border-block-start: 1px solid var(--noctis-color-border);
6541
+ padding-inline: var(--noctis-space-4);
6542
+ padding-block: var(--noctis-space-2);
6543
+ font-size: var(--noctis-text-mini);
6544
+ line-height: var(--noctis-leading-mini);
6545
+ color: var(--noctis-color-muted);
6546
+ }
6547
+
6548
+ @media (prefers-reduced-motion: reduce) {
6549
+ [data-slot="noctis-command-backdrop"],
6550
+ [data-slot="noctis-command"][data-modal],
6551
+ [data-slot="noctis-command-list"],
6552
+ [data-slot="noctis-command-breadcrumb"] > button,
6553
+ [data-slot="noctis-command-item"] {
6554
+ transition: none;
6555
+ }
6556
+
6557
+ [data-slot="noctis-command-list-view"] {
6558
+ animation: none;
6559
+ }
6560
+
6561
+ [data-slot="noctis-command-loading"] > svg {
6562
+ animation: none;
6563
+ }
6564
+ }
6565
+ }
6566
+
5956
6567
  @layer noctis.components {
5957
6568
  /*
5958
6569
  * ContextMenu is precompiled: every rule keys off the prefixed `data-slot` anchor (no `:where()`, so
@@ -7761,6 +8372,97 @@
7761
8372
  }
7762
8373
  }
7763
8374
 
8375
+ @layer noctis.components {
8376
+ /*
8377
+ * Pagination is a self-contained CONTAINER, so every rule keys off its own `data-slot`. It hosts
8378
+ * real Noctis controls — the nav buttons render `Button` welded by a `ButtonGroup`, the page field
8379
+ * renders `Input`, the page-size picker renders `Select` — and those keep their own slots, paint,
8380
+ * and per-size metrics: Pagination styles only its own layout (the row + cluster gaps), the info
8381
+ * line's type, the page field's width, and the hairline separator. The control `size` is fed to the
8382
+ * hosted controls through React context in the orchestration, not a token re-point here. Identity
8383
+ * values flow through the generated `--_pagination-*` internals; the rest is structural layout.
8384
+ */
8385
+
8386
+ /* root — the navigation row. Wraps when narrow; the consumer adds their own justify if they want
8387
+ * the info line and controls pushed apart. */
8388
+ [data-slot="noctis-pagination"] {
8389
+ display: flex;
8390
+ flex-wrap: wrap;
8391
+ align-items: center;
8392
+ gap: var(--_pagination-gap);
8393
+ }
8394
+
8395
+ /* info — the “Showing X–Y of Z” status line, quieter than the controls. */
8396
+ [data-slot="noctis-pagination-info"] {
8397
+ font-size: var(--_pagination-info-font-size);
8398
+ color: var(--_pagination-info-color);
8399
+ }
8400
+
8401
+ /* page-size — the cluster laying out the “Per page” label beside its select. Layout only; the label
8402
+ * carries its own quiet type/colour and the hosted Select keeps its own. */
8403
+ [data-slot="noctis-pagination-page-size"] {
8404
+ display: inline-flex;
8405
+ align-items: center;
8406
+ gap: var(--_pagination-page-size-gap);
8407
+ }
8408
+
8409
+ [data-slot="noctis-pagination-page-size-label"] {
8410
+ font-size: var(--noctis-text-small);
8411
+ color: var(--noctis-color-muted);
8412
+ }
8413
+
8414
+ /* page — the “Page [n] of N” cluster. The hosted Input is clamped to the page-field width and its
8415
+ * value is centred. */
8416
+ [data-slot="noctis-pagination-page"] {
8417
+ display: inline-flex;
8418
+ align-items: center;
8419
+ gap: var(--_pagination-page-gap);
8420
+ font-size: var(--noctis-text-small);
8421
+ color: var(--noctis-color-muted);
8422
+ }
8423
+
8424
+ [data-slot="noctis-pagination-page"] [data-slot="noctis-input"] {
8425
+ inline-size: var(--_pagination-page-inline-size);
8426
+ }
8427
+
8428
+ [data-slot="noctis-pagination-page"] input {
8429
+ text-align: center;
8430
+ }
8431
+
8432
+ /*
8433
+ * Inside the controls `ButtonGroup`, the page field welds flush like a button segment: the wrapper is
8434
+ * positioned so the group's seam pseudo-element anchors on it, and the inner Input is flattened — a
8435
+ * squared radius (it must NOT follow the pill `radius-control`), its inner borders dropped so the
8436
+ * group's seams are the only dividers (no doubled line), and its rest shadow removed so it sits level
8437
+ * with the flattened buttons. The top/bottom field border stays, lining up with the buttons' edges.
8438
+ */
8439
+ [data-slot="noctis-pagination-controls"] [data-slot="noctis-pagination-page"] {
8440
+ position: relative;
8441
+ }
8442
+
8443
+ [data-slot="noctis-pagination-controls"] [data-slot="noctis-pagination-page"] [data-slot="noctis-input"] {
8444
+ block-size: var(--_pagination-page-block-size);
8445
+ border-inline-start-width: 0;
8446
+ border-inline-end-width: 0;
8447
+ border-radius: 0;
8448
+ box-shadow: none;
8449
+ }
8450
+
8451
+ /* controls — the navigation landmark wrapping the welded ButtonGroup of nav buttons. */
8452
+ [data-slot="noctis-pagination-controls"] {
8453
+ display: inline-flex;
8454
+ align-items: center;
8455
+ }
8456
+
8457
+ /* separator — a vertical hairline between clusters (its own minted rule, like Toolbar's). */
8458
+ [data-slot="noctis-pagination-separator"] {
8459
+ flex-shrink: 0;
8460
+ inline-size: var(--_pagination-separator-inline-size);
8461
+ block-size: var(--_pagination-separator-block-size);
8462
+ background-color: var(--_pagination-separator-color);
8463
+ }
8464
+ }
8465
+
7764
8466
  @layer noctis.components {
7765
8467
  /*
7766
8468
  * PreviewCard is precompiled: every rule keys off the prefixed `data-slot` anchor (no `:where()`, so
@@ -10283,6 +10985,139 @@
10283
10985
  }
10284
10986
  }
10285
10987
 
10988
+ @layer noctis.components {
10989
+ /*
10990
+ * Skeleton is self-contained and accent-independent, so every rule keys off the element's own
10991
+ * `data-slot`. The shared placeholder fill, the shimmer band colour, and the sweep duration flow
10992
+ * through the `--_skeleton-fill-*`/`--_skeleton-shimmer-*` internals, which the generated component
10993
+ * layer (`tokens.css`) declares on all three shape slots at once, so a standalone shape paints with
10994
+ * no `Skeleton.Root` ancestor. Per-shape metrics (box height/radius, circle diameter, line
10995
+ * height/radius) ride each shape's own slot; the root and text wrappers are layout only.
10996
+ *
10997
+ * Animation is keyed off each shape's own `data-variant`: `shimmer` (a light band masked over the
10998
+ * fill and translated across), `pulse` (an opacity fade on the shape), or `none` (static). Every
10999
+ * variant degrades to a calm static fill under `prefers-reduced-motion: reduce` (the foot of the file).
11000
+ */
11001
+
11002
+ /* root — the accessible loading group. A vertical stack by default; restyle via className for any
11003
+ * other arrangement. Not a painted placeholder itself. */
11004
+ [data-slot="noctis-skeleton"] {
11005
+ display: flex;
11006
+ flex-direction: column;
11007
+ gap: var(--_skeleton-root-gap);
11008
+ }
11009
+
11010
+ /* text — a column of line bars. Not painted itself; its `Skeleton.Line`s carry the fill + animation. */
11011
+ [data-slot="noctis-skeleton-text"] {
11012
+ display: flex;
11013
+ flex-direction: column;
11014
+ gap: var(--_skeleton-text-gap);
11015
+ }
11016
+
11017
+ /* The last line of a multi-line block is drawn short so the paragraph reads ragged, not as a box.
11018
+ * `:not(:first-child)` keeps a lone line full width. */
11019
+ [data-slot="noctis-skeleton-text"] > [data-slot="noctis-skeleton-line"]:last-child:not(:first-child) {
11020
+ inline-size: 60%;
11021
+ }
11022
+
11023
+ /* ─── the painted shapes ─────────────────────────────────────────────────────────────────────── */
11024
+
11025
+ /* Shared base: a neutral well-coloured fill, clipped so the shimmer band rides inside the shape. */
11026
+ [data-slot="noctis-skeleton-box"],
11027
+ [data-slot="noctis-skeleton-circle"],
11028
+ [data-slot="noctis-skeleton-line"] {
11029
+ position: relative;
11030
+ overflow: hidden;
11031
+ background-color: var(--_skeleton-fill-background-color);
11032
+ }
11033
+
11034
+ /* box — a rectangle. Fills its inline space; its height is the override seam (a default at md). */
11035
+ [data-slot="noctis-skeleton-box"] {
11036
+ inline-size: 100%;
11037
+ block-size: var(--_skeleton-box-block-size);
11038
+ border-radius: var(--_skeleton-box-border-radius);
11039
+ }
11040
+
11041
+ /* circle — an avatar/icon disc. A square footprint rounded to a full circle; never shrinks in a row. */
11042
+ [data-slot="noctis-skeleton-circle"] {
11043
+ flex-shrink: 0;
11044
+ inline-size: var(--_skeleton-circle-size);
11045
+ block-size: var(--_skeleton-circle-size);
11046
+ border-radius: var(--noctis-radius-full);
11047
+ }
11048
+
11049
+ /* line — a single text-line bar. Fills its inline space; the height tracks the surrounding text. */
11050
+ [data-slot="noctis-skeleton-line"] {
11051
+ inline-size: 100%;
11052
+ block-size: var(--_skeleton-line-block-size);
11053
+ border-radius: var(--_skeleton-line-border-radius);
11054
+ }
11055
+
11056
+ /* ─── shimmer: a light band masked over the fill, swept across ───────────────────────────────────
11057
+ * The overlay paints the brighter shimmer colour, masked to a soft transparent→opaque→transparent
11058
+ * band (black/transparent are mask alpha, not brand colour), then translated its full width so the
11059
+ * highlight sweeps the shape. At each loop end the band sits fully off-shape, so the reset is unseen. */
11060
+ [data-slot="noctis-skeleton-box"][data-variant="shimmer"]::after,
11061
+ [data-slot="noctis-skeleton-circle"][data-variant="shimmer"]::after,
11062
+ [data-slot="noctis-skeleton-line"][data-variant="shimmer"]::after {
11063
+ content: "";
11064
+ position: absolute;
11065
+ inset: 0;
11066
+ background-color: var(--_skeleton-shimmer-background-color);
11067
+ mask-image: linear-gradient(to right, transparent, black, transparent);
11068
+ animation: noctis-skeleton-shimmer var(--_skeleton-shimmer-animation-duration) var(--noctis-ease-in-out) infinite;
11069
+ }
11070
+
11071
+ /* The sweep follows the reading direction — reversed under RTL. */
11072
+ [dir="rtl"] [data-slot="noctis-skeleton-box"][data-variant="shimmer"]::after,
11073
+ [dir="rtl"] [data-slot="noctis-skeleton-circle"][data-variant="shimmer"]::after,
11074
+ [dir="rtl"] [data-slot="noctis-skeleton-line"][data-variant="shimmer"]::after {
11075
+ animation-direction: reverse;
11076
+ }
11077
+
11078
+ @keyframes noctis-skeleton-shimmer {
11079
+ from {
11080
+ transform: translateX(-100%);
11081
+ }
11082
+
11083
+ to {
11084
+ transform: translateX(100%);
11085
+ }
11086
+ }
11087
+
11088
+ /* ─── pulse: a gentle opacity fade on the shape itself ───────────────────────────────────────── */
11089
+ [data-slot="noctis-skeleton-box"][data-variant="pulse"],
11090
+ [data-slot="noctis-skeleton-circle"][data-variant="pulse"],
11091
+ [data-slot="noctis-skeleton-line"][data-variant="pulse"] {
11092
+ animation: noctis-skeleton-pulse var(--_skeleton-shimmer-animation-duration) var(--noctis-ease-in-out) infinite alternate;
11093
+ }
11094
+
11095
+ @keyframes noctis-skeleton-pulse {
11096
+ from {
11097
+ opacity: 0.45;
11098
+ }
11099
+
11100
+ to {
11101
+ opacity: 1;
11102
+ }
11103
+ }
11104
+
11105
+ /* Reduced motion: hold every placeholder as a calm static fill — drop the swept band, freeze the fade. */
11106
+ @media (prefers-reduced-motion: reduce) {
11107
+ [data-slot="noctis-skeleton-box"][data-variant="shimmer"]::after,
11108
+ [data-slot="noctis-skeleton-circle"][data-variant="shimmer"]::after,
11109
+ [data-slot="noctis-skeleton-line"][data-variant="shimmer"]::after {
11110
+ display: none;
11111
+ }
11112
+
11113
+ [data-slot="noctis-skeleton-box"][data-variant="pulse"],
11114
+ [data-slot="noctis-skeleton-circle"][data-variant="pulse"],
11115
+ [data-slot="noctis-skeleton-line"][data-variant="pulse"] {
11116
+ animation: none;
11117
+ }
11118
+ }
11119
+ }
11120
+
10286
11121
  @layer noctis.components {
10287
11122
  /* root — the vertical stack: an optional label/value header above the control */
10288
11123
  [data-slot="noctis-slider"] {