@lepid-labs/styles 1.0.0 → 1.0.2

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 (50) hide show
  1. package/css.d.ts +8 -0
  2. package/luminous-precision/base.css +6 -2
  3. package/luminous-precision/components/badge.css +25 -0
  4. package/luminous-precision/components/button-group.css +130 -0
  5. package/luminous-precision/components/button-hold.css +112 -0
  6. package/luminous-precision/components/icon-button.css +71 -0
  7. package/luminous-precision/components/layout.css +348 -0
  8. package/luminous-precision/components/page.css +85 -0
  9. package/luminous-precision/components/pre.css +75 -0
  10. package/luminous-precision/components/shell.css +165 -0
  11. package/luminous-precision/components/status-card.css +159 -0
  12. package/luminous-precision/components/stepper.css +108 -0
  13. package/luminous-precision/components/table.css +20 -0
  14. package/luminous-precision/components/tabs.css +42 -0
  15. package/luminous-precision/design.md +64 -4
  16. package/luminous-precision/index.css +8 -0
  17. package/luminous-precision/tokens.css +6 -0
  18. package/neon-butterfly/base.css +6 -2
  19. package/neon-butterfly/components/badge.css +25 -0
  20. package/neon-butterfly/components/button-group.css +128 -0
  21. package/neon-butterfly/components/button-hold.css +112 -0
  22. package/neon-butterfly/components/icon-button.css +66 -0
  23. package/neon-butterfly/components/layout.css +348 -0
  24. package/neon-butterfly/components/page.css +85 -0
  25. package/neon-butterfly/components/pre.css +75 -0
  26. package/neon-butterfly/components/shell.css +196 -0
  27. package/neon-butterfly/components/status-card.css +156 -0
  28. package/neon-butterfly/components/stepper.css +107 -0
  29. package/neon-butterfly/components/table.css +20 -0
  30. package/neon-butterfly/components/tabs.css +42 -0
  31. package/neon-butterfly/design.md +60 -4
  32. package/neon-butterfly/index.css +8 -0
  33. package/neon-butterfly/tokens.css +6 -0
  34. package/package.json +58 -15
  35. package/summer-cloud/base.css +6 -2
  36. package/summer-cloud/components/badge.css +26 -0
  37. package/summer-cloud/components/button-group.css +131 -0
  38. package/summer-cloud/components/button-hold.css +132 -0
  39. package/summer-cloud/components/icon-button.css +76 -0
  40. package/summer-cloud/components/layout.css +348 -0
  41. package/summer-cloud/components/page.css +85 -0
  42. package/summer-cloud/components/pre.css +74 -0
  43. package/summer-cloud/components/shell.css +197 -0
  44. package/summer-cloud/components/status-card.css +158 -0
  45. package/summer-cloud/components/stepper.css +101 -0
  46. package/summer-cloud/components/table.css +20 -0
  47. package/summer-cloud/components/tabs.css +42 -0
  48. package/summer-cloud/design.md +64 -2
  49. package/summer-cloud/index.css +8 -0
  50. package/summer-cloud/tokens.css +6 -0
@@ -0,0 +1,159 @@
1
+ /* luminous-precision — status card: a dense dashboard card whose left edge, ring, and
2
+ * flash carry state (generalized from pulse's repo card).
3
+ * .ld-status-card--<tone> left edge in the semantic color
4
+ * [data-ld-attention="<tone>"] outline + glow ring calling for action
5
+ * [data-ld-changed] replays the change flash while present */
6
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-status-card {
7
+ --ld-status-attention: var(--ld-warning);
8
+ position: relative;
9
+ display: flex;
10
+ flex-direction: column;
11
+ padding: 0;
12
+ background: var(--ld-surface-glass);
13
+ border: 1px solid var(--ld-border);
14
+ border-top-color: var(--ld-border-lit);
15
+ border-left: 3px solid transparent;
16
+ border-radius: var(--ld-radius-lg);
17
+ backdrop-filter: blur(var(--ld-blur));
18
+ -webkit-backdrop-filter: blur(var(--ld-blur));
19
+ color: var(--ld-on-surface);
20
+ overflow: hidden;
21
+ transition: background var(--ld-transition), box-shadow var(--ld-transition);
22
+ }
23
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-status-card:hover { box-shadow: 0 0 12px var(--ld-primary-glow); }
24
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-status-card--info { border-left-color: var(--ld-info); }
25
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-status-card--success { border-left-color: var(--ld-success); }
26
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-status-card--warning { border-left-color: var(--ld-warning); }
27
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-status-card--danger { border-left-color: var(--ld-danger); }
28
+
29
+ /* attention ring — the tone rides on the attribute value */
30
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-status-card[data-ld-attention] {
31
+ outline: 1px solid color-mix(in srgb, var(--ld-status-attention) 70%, transparent);
32
+ box-shadow: 0 0 14px color-mix(in srgb, var(--ld-status-attention) 35%, transparent);
33
+ }
34
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-status-card[data-ld-attention]:hover {
35
+ box-shadow: 0 0 18px color-mix(in srgb, var(--ld-status-attention) 55%, transparent);
36
+ }
37
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-status-card[data-ld-attention="info"] { --ld-status-attention: var(--ld-info); }
38
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-status-card[data-ld-attention="success"] { --ld-status-attention: var(--ld-success); }
39
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-status-card[data-ld-attention="danger"] { --ld-status-attention: var(--ld-danger); }
40
+
41
+ /* change flash — fades from a primary tint back to the resting glass */
42
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-status-card[data-ld-changed] { animation: ld-lp-status-flash 30s ease-out forwards; }
43
+ @keyframes ld-lp-status-flash {
44
+ from { background: color-mix(in srgb, var(--ld-primary) 20%, var(--ld-surface)); }
45
+ to { background: var(--ld-surface-glass); }
46
+ }
47
+
48
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-status-card__watermark {
49
+ position: absolute;
50
+ top: 0;
51
+ left: -0.25rem;
52
+ width: 2rem;
53
+ height: 2rem;
54
+ color: var(--ld-on-surface);
55
+ opacity: 0.12;
56
+ pointer-events: none;
57
+ rotate: -25deg;
58
+ }
59
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-status-card__watermark > svg { width: 100%; height: 100%; }
60
+
61
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-status-card__head {
62
+ display: flex;
63
+ align-items: center;
64
+ justify-content: space-between;
65
+ gap: var(--ld-space-2);
66
+ padding: 0.625rem 0.75rem 0.5rem 0.875rem;
67
+ }
68
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-status-card__title {
69
+ min-width: 0;
70
+ overflow: hidden;
71
+ font-family: var(--ld-font-display);
72
+ letter-spacing: -0.01em;
73
+ font-size: var(--ld-text-sm);
74
+ font-weight: var(--ld-font-weight-medium);
75
+ white-space: nowrap;
76
+ text-overflow: ellipsis;
77
+ color: inherit;
78
+ text-decoration: none;
79
+ transition: color var(--ld-transition);
80
+ }
81
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-status-card__title[href]:hover { color: var(--ld-primary); }
82
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-status-card__prefix { color: var(--ld-faint); }
83
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-status-card__prefix::after { content: " // "; opacity: 0.5; }
84
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-status-card__meta {
85
+ display: flex;
86
+ align-items: center;
87
+ gap: 0.375rem;
88
+ flex-shrink: 0;
89
+ }
90
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-status-card__note {
91
+ font-size: 11px;
92
+ color: var(--ld-faint);
93
+ white-space: nowrap;
94
+ }
95
+
96
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-status-card__rows {
97
+ list-style: none;
98
+ margin: 0;
99
+ padding: 0;
100
+ border-top: 1px solid var(--ld-border);
101
+ }
102
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-status-card__row {
103
+ display: grid;
104
+ grid-template-columns: auto 1fr auto;
105
+ align-items: baseline;
106
+ gap: 0.375rem;
107
+ padding: 0.3rem 0.75rem 0.3rem 0.875rem;
108
+ font-size: 12px;
109
+ color: inherit;
110
+ text-decoration: none;
111
+ transition: background 0.1s;
112
+ }
113
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-status-card__row[href]:hover { background: color-mix(in srgb, var(--ld-primary) 8%, transparent); }
114
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-status-card__row-lead {
115
+ min-width: 1.5rem;
116
+ font-size: 11px;
117
+ font-weight: var(--ld-font-weight-bold);
118
+ color: var(--ld-faint);
119
+ text-align: right;
120
+ font-variant-numeric: tabular-nums;
121
+ }
122
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-status-card__row-main {
123
+ min-width: 0;
124
+ overflow: hidden;
125
+ white-space: nowrap;
126
+ text-overflow: ellipsis;
127
+ color: var(--ld-on-surface);
128
+ }
129
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-status-card__row-trail {
130
+ display: flex;
131
+ align-items: center;
132
+ gap: var(--ld-space-1);
133
+ flex-shrink: 0;
134
+ font-size: 10px;
135
+ color: var(--ld-faint);
136
+ }
137
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-status-card__footer {
138
+ display: block;
139
+ padding: 0.3rem 0.875rem;
140
+ border-top: 1px solid var(--ld-border);
141
+ font-size: 11px;
142
+ color: var(--ld-faint);
143
+ text-decoration: none;
144
+ transition: color var(--ld-transition);
145
+ }
146
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-status-card__footer[href]:hover { color: var(--ld-accent); }
147
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-status-card__empty {
148
+ margin: 0;
149
+ padding: 0.375rem 0.875rem 0.5rem;
150
+ border-top: 1px solid var(--ld-border);
151
+ font-size: 12px;
152
+ color: var(--ld-faint);
153
+ opacity: 0.7;
154
+ }
155
+
156
+ @media (prefers-reduced-motion: reduce) {
157
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-status-card { transition: none; }
158
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-status-card[data-ld-changed] { animation: none; }
159
+ }
@@ -0,0 +1,108 @@
1
+ /* luminous-precision — stepper: a milestone rail with labelled step nodes.
2
+ * Orchid rail and complete nodes; the current node lights up electric teal,
3
+ * the theme's active-path signal.
4
+ * .ld-stepper__step--complete solid orchid node, dark icon
5
+ * .ld-stepper__step--current indigo node, 2px teal border, teal glow
6
+ * .ld-stepper__step--upcoming sunken node, hairline border, faint icon
7
+ * The fill follows the state modifiers — no progress value to compute. */
8
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-stepper {
9
+ display: flex;
10
+ align-items: flex-start;
11
+ list-style: none;
12
+ margin: 0;
13
+ padding: 0;
14
+ }
15
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-stepper__step {
16
+ position: relative;
17
+ flex: 1 1 0;
18
+ min-width: 0;
19
+ display: flex;
20
+ flex-direction: column;
21
+ align-items: center;
22
+ gap: var(--ld-space-2);
23
+ text-align: center;
24
+ }
25
+ /* rail segment: from this node's centre to the next node's centre; the track is
26
+ * ::before, the fill ::after. Segments meet under the nodes, so the rail reads
27
+ * as one bar and the fill ends exactly at the current node. */
28
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-stepper__step::before,
29
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-stepper__step::after {
30
+ content: "";
31
+ position: absolute;
32
+ top: 0.75rem;
33
+ left: 50%;
34
+ width: 100%;
35
+ height: 0.5rem;
36
+ border-radius: 1rem;
37
+ }
38
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-stepper__step:last-child::before,
39
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-stepper__step:last-child::after { display: none; }
40
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-stepper__step::before {
41
+ background: var(--ld-surface-sunken);
42
+ box-shadow: inset 0 0 0 1px var(--ld-border);
43
+ }
44
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-stepper__step::after {
45
+ width: 0;
46
+ background: var(--ld-primary);
47
+ box-shadow: 0 0 8px var(--ld-primary-glow);
48
+ transition: width var(--ld-transition);
49
+ }
50
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-stepper__step--complete::after { width: 100%; }
51
+
52
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-stepper__node {
53
+ position: relative;
54
+ z-index: 1;
55
+ display: inline-flex;
56
+ align-items: center;
57
+ justify-content: center;
58
+ width: 2rem;
59
+ height: 2rem;
60
+ border-radius: 50%;
61
+ box-sizing: border-box;
62
+ font-family: var(--ld-font-mono);
63
+ font-size: 12px;
64
+ font-weight: var(--ld-font-weight-bold);
65
+ line-height: 1;
66
+ transition: background var(--ld-transition), color var(--ld-transition), box-shadow var(--ld-transition);
67
+ }
68
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-stepper__node > svg { width: 1rem; height: 1rem; }
69
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-stepper__step--complete .ld-stepper__node {
70
+ background: var(--ld-primary);
71
+ color: var(--ld-on-primary);
72
+ box-shadow: 0 0 8px var(--ld-primary-glow);
73
+ }
74
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-stepper__step--current .ld-stepper__node {
75
+ background: var(--ld-surface);
76
+ border: 2px solid var(--ld-accent);
77
+ color: var(--ld-accent);
78
+ box-shadow: 0 0 12px var(--ld-accent-glow);
79
+ }
80
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-stepper__step--upcoming .ld-stepper__node {
81
+ background: var(--ld-surface-sunken);
82
+ border: 1px solid var(--ld-border);
83
+ color: var(--ld-faint);
84
+ }
85
+
86
+ /* label voice: uppercase 600 mono, tracked 0.05em */
87
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-stepper__label {
88
+ font-family: var(--ld-font-mono);
89
+ font-size: 12px;
90
+ font-weight: var(--ld-font-weight-medium);
91
+ text-transform: uppercase;
92
+ letter-spacing: var(--ld-tracking-wide);
93
+ line-height: 1.3;
94
+ color: var(--ld-faint);
95
+ transition: color var(--ld-transition), text-shadow var(--ld-transition);
96
+ }
97
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-stepper__step--complete .ld-stepper__label { color: var(--ld-on-surface); }
98
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-stepper__step--current .ld-stepper__label {
99
+ color: var(--ld-accent);
100
+ text-shadow: 0 0 8px var(--ld-accent-glow);
101
+ }
102
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-stepper__step--upcoming .ld-stepper__label { color: var(--ld-faint); }
103
+
104
+ @media (prefers-reduced-motion: reduce) {
105
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-stepper__step::after,
106
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-stepper__node,
107
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-stepper__label { transition: none; }
108
+ }
@@ -26,3 +26,23 @@
26
26
  background: var(--ld-surface-glass);
27
27
  box-shadow: inset 2px 0 0 0 var(--ld-accent);
28
28
  }
29
+
30
+ /* Interactive rows (.ld-table--interactive): the cursor is the affordance —
31
+ * plain tables hover too. Focus is an outline so it
32
+ * composes with the selected state (aria-selected="true"), which sits last
33
+ * so it outranks hover. */
34
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-table--interactive tbody tr {
35
+ cursor: pointer;
36
+ }
37
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-table--interactive tbody tr:focus-visible {
38
+ outline: 1px solid var(--ld-primary-border);
39
+ outline-offset: -1px;
40
+ background: var(--ld-surface-glass);
41
+ }
42
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-table--interactive tbody tr:active {
43
+ background: rgba(198, 165, 255, 0.06);
44
+ }
45
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-table--interactive tbody tr[aria-selected="true"] {
46
+ background: rgba(198, 165, 255, 0.1);
47
+ box-shadow: inset 2px 0 0 0 var(--ld-accent), inset 0 0 12px var(--ld-primary-glow);
48
+ }
@@ -31,3 +31,45 @@
31
31
  :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-tabpanel {
32
32
  padding: var(--ld-space-3) 0;
33
33
  }
34
+
35
+ /* Sliding indicator: one bar on the tablist, pinned to the active tab by
36
+ * anchor positioning, takes over the underline. Its inline edges transition
37
+ * separately — with data-ld-dir ("forward" | "back", set by the Tabs
38
+ * component) the edge nearer the target leads and the far edge trails, so the
39
+ * bar reaches for the tab; without it the bar just slides. Browsers without
40
+ * anchor positioning keep the static underline above. */
41
+ @supports (anchor-name: --ld-a) and (anchor-scope: --ld-a) {
42
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-tabs {
43
+ position: relative;
44
+ anchor-scope: --ld-tab-active;
45
+ }
46
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-tabs :is(.ld-tab--active, .ld-tab[aria-selected="true"]) {
47
+ anchor-name: --ld-tab-active;
48
+ border-bottom-color: transparent;
49
+ }
50
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-tabs:where(:has(.ld-tab--active, .ld-tab[aria-selected="true"]))::after {
51
+ content: "";
52
+ position: absolute;
53
+ position-anchor: --ld-tab-active;
54
+ inset-block-end: anchor(bottom);
55
+ inset-inline: anchor(start) anchor(end);
56
+ block-size: 2px;
57
+ background: var(--ld-accent);
58
+ box-shadow: 0 0 10px var(--ld-accent-glow), 0 0 2px var(--ld-accent);
59
+ pointer-events: none;
60
+ transition: inset-inline-start var(--ld-slide-lead), inset-inline-end var(--ld-slide-lead);
61
+ }
62
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-tabs[data-ld-dir="forward"]::after {
63
+ transition: inset-inline-start var(--ld-slide-trail), inset-inline-end var(--ld-slide-lead);
64
+ }
65
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-tabs[data-ld-dir="back"]::after {
66
+ transition: inset-inline-start var(--ld-slide-lead), inset-inline-end var(--ld-slide-trail);
67
+ }
68
+ }
69
+
70
+ @media (prefers-reduced-motion: reduce) {
71
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-tabs::after,
72
+ :where([data-ld-style="luminous-precision"], [data-ld-style="luminous-precision"] *).ld-tabs[data-ld-dir]::after {
73
+ transition: none;
74
+ }
75
+ }
@@ -56,30 +56,90 @@ tonal layers and glow, not drop shadows. Never pure white or pure black.
56
56
  Class prefix `ld-` (shared token/class contract with the other themes).
57
57
 
58
58
  - **Card** `.ld-card` — glass pane, 1rem radius, lit top edge.
59
+ - **Status card** `.ld-status-card` — dense dashboard card, 1rem radius, Sora title (from pulse's repo
60
+ card): 3px left edge takes a semantic `--<tone>`, `data-ld-attention="<tone>"`
61
+ adds an outline + glow ring, `data-ld-changed` replays a 30s tint flash.
62
+ Parts: `__watermark`, `__head`, `__prefix`, `__title`, `__meta`, `__note`,
63
+ `__rows`, `__row` (`-lead`/`-main`/`-trail`), `__footer`, `__empty`.
59
64
  - **NavLink** `.ld-link` — glass list row; hover shifts to solid indigo and
60
65
  lights a 2px teal bar on the left edge.
61
66
  - **Button** `.ld-btn` — ghost by default; `--primary` is the one solid fill
62
67
  (orchid, dark text), `--accent` teal ghost, `--danger` rose ghost. `--sm` is a
63
68
  compact size scaled to `.ld-badge`, for inline and table-row actions.
69
+ - **Hold button** `.ld-btn--hold` (+ `__ring`, `__body`, `__label`, `__hint`,
70
+ `__meter`) — hold-to-confirm: a 3px conic ring around the icon well fills
71
+ as `--ld-hold` goes 0→1; `--danger` paints it rose, `--primary` orchid,
72
+ `--accent` teal, neutral alone. `data-ld-hold="fired"` blooms the button
73
+ and ring in the ring color (dropped under reduced motion; the fill stays).
74
+ Window: `--ld-hold-duration` 300 ms. The ring *is* the confirmation.
75
+ - **Button group** `.ld-btn-group` (+ `--block`) — one long track with
76
+ `.ld-btn` segments lined up inside; the track owns the chrome and the
77
+ segments drop theirs. Lit-border track on the sunken well; faint mono
78
+ labels. The pressed segment (`aria-pressed="true"` / `.ld-btn--active`) is a
79
+ raised pane with an orchid border, orchid label and soft glow; no hover
80
+ pulse inside the track.
81
+ With one pressed segment the chrome is a single sliding pane: the edge
82
+ nearer the new segment leads, the far edge trails on a tight spring with barely any overshoot
83
+ (`--ld-slide-lead` / `--ld-slide-trail`, direction from `data-ld-dir`).
84
+ Variants tint the label only; `--block` fills the row with equal-width
85
+ segments. Use `role="group"` + `aria-label`.
86
+ - **Icon button** `.ld-icon-btn` (+ `--active`, `--danger`) — chromeless
87
+ 2rem square for icon-only actions (`<button>` or `<a>`; always
88
+ `aria-label`). Faint glyph at rest; hover lights it orchid on a faint tint
89
+ with a soft glow. Active (`--active`, `aria-pressed="true"`,
90
+ `aria-current="page"`) is an orchid glyph in a hairline orchid inset.
91
+ `--danger` turns the glyph red on hover. Child `svg` sized to 1.25em.
64
92
  - **Form** `.ld-input`, `.ld-textarea`, `.ld-select`, `.ld-label`,
65
93
  `.ld-field`, `.ld-checkbox`, `.ld-radio`, `.ld-switch`, `.ld-choice` —
66
94
  checked/focus states glow teal.
67
95
  - **Badge** `.ld-badge` — pill chip: 15% tint of its hue behind
68
- full-saturation text (+ semantic modifiers).
96
+ full-saturation text (+ semantic modifiers). `--sm` is the 9px dashboard
97
+ pill; `--pulse` the one solid amber call to action with a breathing glow.
69
98
  - **Alert** `.ld-alert` — glass, 3px left bar carries the semantic color.
70
99
  - **Dialog** `.ld-dialog` — native `<dialog>`, indigo pane with orchid glow,
71
100
  Sora title, blurred obsidian backdrop.
72
101
  - **Tabs** `.ld-tabs`/`.ld-tab`/`.ld-tabpanel` — active tab underlined in
73
- teal with text glow.
102
+ teal with text glow; the underline is one lit bar that reaches for the new
103
+ tab and settles with barely any overshoot (same slide tokens).
74
104
  - **Table** `.ld-table` — lit header rule; row hover shifts glass and lights
75
- the teal left bar.
105
+ the teal left bar. `.ld-table--interactive` adds the pointer, an orchid
106
+ focus outline, and a selected row (`aria-selected`) held in orchid glow
107
+ with the teal bar.
76
108
  - **Progress** `.ld-progress`, **Spinner** `.ld-spinner` — glowing orchid
77
109
  indicators (`--accent` bar variant in teal).
110
+ - **App shell** `.ld-shell` (+ `__skip`, `__header`, `__toggle`, `__brand`,
111
+ `__actions`, `__nav`, `__scrim`, `__main`, `__footer`; `.ld-topnav`/`__item`;
112
+ `.ld-sidenav` + `__section`, `__heading`, `__item`, `__icon`, `__label`,
113
+ `__badge`, `__footer`) — brand in Sora with an orchid-glow mark; mono uppercase top-nav and nav labels in faint. The current page takes the electric-teal path (indigo pane, 2px teal left bar, teal glow) — the same language as `.ld-link` hover; in the tab bar it becomes a teal top rule. The header and nav are unfilled — hairline
114
+ borders over the page background (`.ld-bg`), which the shell paints; the
115
+ header blurs what scrolls under it. The drawer and tab bar take a fill. Structure (`components/layout.css`) is shared by every theme:
116
+ collapsed rail on `data-ld-nav-collapsed`, drawer on `data-ld-nav-open`
117
+ below 48rem, `--tabbar` docks the nav at the bottom. The header
118
+ always spans the full width above the nav.
119
+ - **Page layouts** `.ld-page` (`--narrow`/`--wide`/`--full`, `__header`,
120
+ `__eyebrow`, `__title`, `__subtitle`, `__actions`), `.ld-grid`,
121
+ `.ld-split`, `.ld-aside-layout`, `.ld-center` — structure only
122
+ (`components/page.css`, shared by every theme); the page
123
+ title uses the display face.
124
+ - **Stepper** `.ld-stepper` (+ `__step`, `__node`, `__label`) — milestone
125
+ rail: glowing orchid fill on a sunken track, 2rem nodes, uppercase mono
126
+ labels. `__step--complete` is a solid orchid node; `--current` lights the
127
+ active path in teal — 2px teal border, teal icon and glow, teal label;
128
+ `--upcoming` a hairline sunken node in faint. The rail is per-step
129
+ (`::before` track, `::after` fill), so the fill follows the modifiers and
130
+ `aria-current="step"` marks the active node.
78
131
  - **Muted text** `.ld-muted` — faint secondary/empty-state text;
79
132
  `color: var(--ld-faint)`, italic.
80
133
  - **Pre / log block** `.ld-pre` — command/log `<pre>`; the surface-sunken
81
134
  well (already documented above as this theme's "log wells" token),
82
- hairline border, radius, small mono, horizontal scroll.
135
+ hairline border, radius, small mono, horizontal scroll. `.ld-pre--log`
136
+ bounds it (`--ld-log-max-height`, 420px), scrolls both ways, wraps long
137
+ lines, and lays faint teal scanlines over the well.
138
+ - **Copy control** `.ld-pre-copy` — an `.ld-icon-btn` pinned top-right in
139
+ a `.ld-pre-wrap`; fades in on hover/focus-within, always shown on touch
140
+ (`hover: none`). `data-ld-copy="copied"` swaps the copy glyph for a
141
+ success-tinted check; `"failed"` tints it danger. Status goes to a
142
+ visually hidden `aria-live` `.ld-pre-copy-status`.
83
143
 
84
144
  ## Code syntax
85
145
 
@@ -1,9 +1,16 @@
1
1
  /* luminous-precision — full theme entry point */
2
2
  @import "./tokens.css";
3
3
  @import "./base.css";
4
+ @import "./components/layout.css";
5
+ @import "./components/shell.css";
6
+ @import "./components/page.css";
4
7
  @import "./components/card.css";
8
+ @import "./components/status-card.css";
5
9
  @import "./components/link.css";
6
10
  @import "./components/button.css";
11
+ @import "./components/button-hold.css";
12
+ @import "./components/button-group.css";
13
+ @import "./components/icon-button.css";
7
14
  @import "./components/form.css";
8
15
  @import "./components/badge.css";
9
16
  @import "./components/alert.css";
@@ -11,5 +18,6 @@
11
18
  @import "./components/tabs.css";
12
19
  @import "./components/table.css";
13
20
  @import "./components/progress.css";
21
+ @import "./components/stepper.css";
14
22
  @import "./components/muted.css";
15
23
  @import "./components/pre.css";
@@ -62,6 +62,12 @@
62
62
  --ld-radius-lg: 1rem; /* cards and large containers */
63
63
  --ld-blur: 12px;
64
64
  --ld-transition: 0.3s;
65
+ --ld-hold-duration: 300ms; /* (extra) hold-to-confirm commitment window */
66
+ /* (extra) sliding indicator — tabs underline, button-group pill. The edge
67
+ * nearer the target leads; the far edge trails on a tight, near-critically-damped spring that barely overshoots
68
+ * inward, so the indicator stretches across the gap and snaps back to shape. */
69
+ --ld-slide-lead: 0.2s cubic-bezier(0.2, 0.9, 0.3, 1);
70
+ --ld-slide-trail: 0.45s linear(0, 0.044, 0.148, 0.279, 0.415, 0.544, 0.657, 0.753, 0.83, 0.889, 0.934, 0.966, 0.988, 1.002, 1.01, 1.014, 1.015, 1.015, 1.013, 1.011, 1.009, 1.007, 1.005, 1.004, 1);
65
71
 
66
72
  /* spacing (8px rhythm) */
67
73
  --ld-space-1: 0.25rem;
@@ -32,8 +32,12 @@
32
32
 
33
33
  /* Opt-in page background: the butterfly circuit artwork blended into the navy.
34
34
  * Matches the switchboard landing page. Apply to <body>, or to the attributed
35
- * container itself. */
36
- :where([data-ld-style="neon-butterfly"], [data-ld-style="neon-butterfly"] *).ld-bg {
35
+ * container itself. An app shell
36
+ * (.ld-shell) always paints it, and so does the <body> around one, so the
37
+ * page stays covered past the shell (overscroll, short pages). */
38
+ :where([data-ld-style="neon-butterfly"], [data-ld-style="neon-butterfly"] *).ld-bg,
39
+ :where(html[data-ld-style="neon-butterfly"]) body:has(.ld-shell),
40
+ :where([data-ld-style="neon-butterfly"], [data-ld-style="neon-butterfly"] *).ld-shell {
37
41
  background: var(--ld-bg) url("./assets/butterfly-circuit.png") center / cover fixed;
38
42
  background-blend-mode: luminosity;
39
43
  background-color: var(--ld-bg-blend);
@@ -15,3 +15,28 @@
15
15
  :where([data-ld-style="neon-butterfly"], [data-ld-style="neon-butterfly"] *).ld-badge--success { border-color: var(--ld-success); color: var(--ld-success); }
16
16
  :where([data-ld-style="neon-butterfly"], [data-ld-style="neon-butterfly"] *).ld-badge--warning { border-color: var(--ld-warning); color: var(--ld-warning); }
17
17
  :where([data-ld-style="neon-butterfly"], [data-ld-style="neon-butterfly"] *).ld-badge--danger { border-color: var(--ld-danger); color: var(--ld-danger); }
18
+
19
+ /* Compact size — dashboard pills inside .ld-status-card__meta and row trails. */
20
+ :where([data-ld-style="neon-butterfly"], [data-ld-style="neon-butterfly"] *).ld-badge--sm {
21
+ padding: 0.125rem 0.375rem;
22
+ font-size: 9px;
23
+ letter-spacing: 0.06em;
24
+ }
25
+ :where([data-ld-style="neon-butterfly"], [data-ld-style="neon-butterfly"] *).ld-badge[href] { text-decoration: none; }
26
+ :where([data-ld-style="neon-butterfly"], [data-ld-style="neon-butterfly"] *).ld-badge[href]:hover { filter: brightness(1.1); }
27
+
28
+ /* Pulse — the one solid, glowing call to action (work is waiting on you). */
29
+ :where([data-ld-style="neon-butterfly"], [data-ld-style="neon-butterfly"] *).ld-badge--pulse {
30
+ background: var(--ld-warning);
31
+ border-color: transparent;
32
+ color: var(--ld-bg);
33
+ box-shadow: 0 0 8px color-mix(in srgb, var(--ld-warning) 60%, transparent);
34
+ animation: ld-nb-badge-pulse 2s ease-in-out infinite;
35
+ }
36
+ @keyframes ld-nb-badge-pulse {
37
+ 0%, 100% { box-shadow: 0 0 6px color-mix(in srgb, var(--ld-warning) 50%, transparent); }
38
+ 50% { box-shadow: 0 0 16px color-mix(in srgb, var(--ld-warning) 90%, transparent); }
39
+ }
40
+ @media (prefers-reduced-motion: reduce) {
41
+ :where([data-ld-style="neon-butterfly"], [data-ld-style="neon-butterfly"] *).ld-badge--pulse { animation: none; }
42
+ }
@@ -0,0 +1,128 @@
1
+ /* neon-butterfly — button group: one glass track, chromeless segments; the
2
+ * pressed segment takes the lilac edge and glow. */
3
+ :where([data-ld-style="neon-butterfly"], [data-ld-style="neon-butterfly"] *).ld-btn-group {
4
+ display: inline-flex;
5
+ align-items: stretch;
6
+ gap: 2px;
7
+ max-width: 100%;
8
+ padding: 3px;
9
+ background: var(--ld-surface-glass);
10
+ border: 1px solid var(--ld-border);
11
+ border-radius: var(--ld-radius);
12
+ }
13
+ /* --block: fill the row, equal-width segments */
14
+ :where([data-ld-style="neon-butterfly"], [data-ld-style="neon-butterfly"] *).ld-btn-group--block {
15
+ display: flex;
16
+ }
17
+ :where([data-ld-style="neon-butterfly"], [data-ld-style="neon-butterfly"] *).ld-btn-group--block > .ld-btn {
18
+ flex: 1 1 0;
19
+ }
20
+
21
+ /* segments: the track is the chrome, so the button drops its own */
22
+ :where([data-ld-style="neon-butterfly"], [data-ld-style="neon-butterfly"] *).ld-btn-group > .ld-btn {
23
+ justify-content: center;
24
+ background: transparent;
25
+ border-color: transparent;
26
+ border-radius: calc(var(--ld-radius) - 3px);
27
+ color: var(--ld-faint);
28
+ white-space: nowrap;
29
+ transition:
30
+ background var(--ld-transition),
31
+ border-color var(--ld-transition),
32
+ color var(--ld-transition);
33
+ }
34
+ /* variants tint the label only */
35
+ :where([data-ld-style="neon-butterfly"], [data-ld-style="neon-butterfly"] *).ld-btn-group > .ld-btn--primary {
36
+ color: var(--ld-primary);
37
+ }
38
+ :where([data-ld-style="neon-butterfly"], [data-ld-style="neon-butterfly"] *).ld-btn-group > .ld-btn--accent {
39
+ color: var(--ld-accent);
40
+ }
41
+ :where([data-ld-style="neon-butterfly"], [data-ld-style="neon-butterfly"] *).ld-btn-group > .ld-btn--danger {
42
+ color: var(--ld-danger);
43
+ }
44
+ :where([data-ld-style="neon-butterfly"], [data-ld-style="neon-butterfly"] *).ld-btn-group > .ld-btn:hover,
45
+ :where([data-ld-style="neon-butterfly"], [data-ld-style="neon-butterfly"] *).ld-btn-group > .ld-btn:focus-visible {
46
+ background: var(--ld-surface-sunken);
47
+ border-color: transparent;
48
+ color: var(--ld-on-surface);
49
+ animation: none;
50
+ }
51
+ :where([data-ld-style="neon-butterfly"], [data-ld-style="neon-butterfly"] *).ld-btn-group > .ld-btn:focus-visible {
52
+ /* an outline, not the border: it must stay visible on the pressed segment */
53
+ outline: 1px solid var(--ld-primary);
54
+ outline-offset: 1px;
55
+ }
56
+ :where([data-ld-style="neon-butterfly"], [data-ld-style="neon-butterfly"] *).ld-btn-group > .ld-btn--primary:hover,
57
+ :where([data-ld-style="neon-butterfly"], [data-ld-style="neon-butterfly"] *).ld-btn-group > .ld-btn--primary:focus-visible {
58
+ color: var(--ld-primary);
59
+ }
60
+ :where([data-ld-style="neon-butterfly"], [data-ld-style="neon-butterfly"] *).ld-btn-group > .ld-btn--accent:hover,
61
+ :where([data-ld-style="neon-butterfly"], [data-ld-style="neon-butterfly"] *).ld-btn-group > .ld-btn--accent:focus-visible {
62
+ color: var(--ld-accent);
63
+ }
64
+ :where([data-ld-style="neon-butterfly"], [data-ld-style="neon-butterfly"] *).ld-btn-group > .ld-btn--danger:hover,
65
+ :where([data-ld-style="neon-butterfly"], [data-ld-style="neon-butterfly"] *).ld-btn-group > .ld-btn--danger:focus-visible {
66
+ color: var(--ld-danger);
67
+ }
68
+
69
+ /* pressed segment (toggle / segmented use) */
70
+ :where([data-ld-style="neon-butterfly"], [data-ld-style="neon-butterfly"] *).ld-btn-group > .ld-btn--active,
71
+ :where([data-ld-style="neon-butterfly"], [data-ld-style="neon-butterfly"] *).ld-btn-group > .ld-btn[aria-pressed="true"] {
72
+ background: var(--ld-surface-sunken);
73
+ border-color: var(--ld-primary-border);
74
+ color: var(--ld-primary);
75
+ text-shadow: 0 0 8px var(--ld-primary-glow);
76
+ }
77
+ :where([data-ld-style="neon-butterfly"], [data-ld-style="neon-butterfly"] *).ld-btn-group > .ld-btn:disabled {
78
+ background: transparent;
79
+ color: var(--ld-faint);
80
+ }
81
+
82
+ /* Sliding pill: with exactly one pressed segment, its chrome moves to one pill
83
+ * on the track, pinned to the segment by anchor positioning, so a change of
84
+ * selection travels instead of blinking. Its inline edges transition
85
+ * separately — with data-ld-dir ("forward" | "back", set by ButtonGroup) the
86
+ * edge nearer the target leads and the far edge trails, stretching the pill
87
+ * across the gap; without it the pill just slides. Several pressed segments
88
+ * (multi-toggle), or no anchor positioning, keep the per-segment chrome above. */
89
+ @supports (anchor-name: --ld-a) and (anchor-scope: --ld-a) {
90
+ :where([data-ld-style="neon-butterfly"], [data-ld-style="neon-butterfly"] *).ld-btn-group {
91
+ position: relative;
92
+ isolation: isolate;
93
+ anchor-scope: --ld-btn-group-active;
94
+ }
95
+ :where([data-ld-style="neon-butterfly"], [data-ld-style="neon-butterfly"] *).ld-btn-group:not(:has(> :is(.ld-btn--active, .ld-btn[aria-pressed="true"]) ~ :is(.ld-btn--active, .ld-btn[aria-pressed="true"]))) > :is(.ld-btn--active, .ld-btn[aria-pressed="true"]) {
96
+ anchor-name: --ld-btn-group-active;
97
+ background: transparent;
98
+ border-color: transparent;
99
+ }
100
+ :where([data-ld-style="neon-butterfly"], [data-ld-style="neon-butterfly"] *).ld-btn-group:where(:has(> :is(.ld-btn--active, .ld-btn[aria-pressed="true"])):not(:has(> :is(.ld-btn--active, .ld-btn[aria-pressed="true"]) ~ :is(.ld-btn--active, .ld-btn[aria-pressed="true"]))))::before {
101
+ content: "";
102
+ position: absolute;
103
+ z-index: -1;
104
+ position-anchor: --ld-btn-group-active;
105
+ inset-block: anchor(top) anchor(bottom);
106
+ inset-inline: anchor(start) anchor(end);
107
+ background: var(--ld-surface-sunken);
108
+ border: 1px solid var(--ld-primary-border);
109
+ border-radius: calc(var(--ld-radius) - 3px);
110
+ box-shadow: 0 0 10px -2px var(--ld-primary-glow);
111
+ pointer-events: none;
112
+ transition: inset-inline-start var(--ld-slide-lead), inset-inline-end var(--ld-slide-lead);
113
+ }
114
+ :where([data-ld-style="neon-butterfly"], [data-ld-style="neon-butterfly"] *).ld-btn-group[data-ld-dir="forward"]::before {
115
+ transition: inset-inline-start var(--ld-slide-trail), inset-inline-end var(--ld-slide-lead);
116
+ }
117
+ :where([data-ld-style="neon-butterfly"], [data-ld-style="neon-butterfly"] *).ld-btn-group[data-ld-dir="back"]::before {
118
+ transition: inset-inline-start var(--ld-slide-lead), inset-inline-end var(--ld-slide-trail);
119
+ }
120
+ }
121
+
122
+ @media (prefers-reduced-motion: reduce) {
123
+ :where([data-ld-style="neon-butterfly"], [data-ld-style="neon-butterfly"] *).ld-btn-group > .ld-btn,
124
+ :where([data-ld-style="neon-butterfly"], [data-ld-style="neon-butterfly"] *).ld-btn-group::before,
125
+ :where([data-ld-style="neon-butterfly"], [data-ld-style="neon-butterfly"] *).ld-btn-group[data-ld-dir]::before {
126
+ transition: none;
127
+ }
128
+ }