@nomideusz/svelte-calendar 0.3.1 → 0.4.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.
@@ -4,27 +4,40 @@
4
4
  import { presets } from '../../theme/presets.js';
5
5
 
6
6
  /* ─── Field definition types ─────────────────────── */
7
- type RangeField = {
7
+ type BaseField = {
8
8
  key: string;
9
9
  label: string;
10
+ group?: string;
11
+ };
12
+
13
+ type RangeField = BaseField & {
10
14
  type: 'range';
11
15
  min: number;
12
16
  max: number;
13
17
  step: number;
18
+ enabledWhen?: string;
14
19
  };
15
20
 
16
- type ToggleField = {
17
- key: string;
18
- label: string;
21
+ type ToggleField = BaseField & {
19
22
  type: 'toggle';
20
23
  };
21
24
 
22
- export type SettingsField = RangeField | ToggleField;
25
+ type SelectField = BaseField & {
26
+ type: 'select';
27
+ options: { value: string; label: string }[];
28
+ };
29
+
30
+ type SegmentField = BaseField & {
31
+ type: 'segment';
32
+ options: { value: string; label: string }[];
33
+ };
34
+
35
+ export type SettingsField = RangeField | ToggleField | SelectField | SegmentField;
23
36
 
24
37
  /* ─── Props ───────────────────────────────────────── */
25
38
  interface Props {
26
39
  fields: SettingsField[];
27
- values: Record<string, number | boolean>;
40
+ values: Record<string, string | number | boolean>;
28
41
  theme: PresetName;
29
42
  }
30
43
 
@@ -38,270 +51,535 @@
38
51
  neutral: 'Neutral',
39
52
  bare: 'Bare',
40
53
  };
41
- const themeAccents: Record<PresetName, string> = {
42
- midnight: '#ef4444',
43
- parchment: '#b85c2f',
44
- indigo: '#6366f1',
45
- neutral: '#94a0b4',
46
- bare: '#94a0b4',
47
- };
54
+ const darkThemes = new Set<PresetName>(['midnight']);
55
+ const panelId = 'stg-panel';
48
56
 
49
57
  let open = $state(true);
50
58
 
51
- function setVal(key: string, v: number | boolean) {
59
+ const themeStyle = $derived(presets[theme]);
60
+ const colorScheme = $derived(darkThemes.has(theme) ? 'dark' : 'light');
61
+
62
+ function setVal(key: string, v: string | number | boolean) {
52
63
  values = { ...values, [key]: v };
53
64
  }
54
65
 
55
- function fmt(v: number | boolean): string {
66
+ function fmt(v: string | number | boolean): string {
67
+ if (typeof v === 'string') return v;
56
68
  if (typeof v === 'boolean') return v ? 'true' : 'false';
57
69
  if (Number.isInteger(v)) return String(v);
58
70
  return v.toFixed(2);
59
71
  }
72
+
73
+ function controlId(key: string): string {
74
+ return `stg-${key.replace(/[^a-zA-Z0-9_-]/g, '-')}`;
75
+ }
76
+
77
+ /* Split fields into top-level (no group name) and grouped */
78
+ const { topFields, groupedFields } = $derived.by(() => {
79
+ const top: SettingsField[] = [];
80
+ const orderedGroups: { name: string; id: string; fields: SettingsField[] }[] = [];
81
+ const indexByName = new Map<string, number>();
82
+
83
+ for (const field of fields) {
84
+ const groupName = field.group ?? '';
85
+
86
+ if (!groupName) {
87
+ top.push(field);
88
+ continue;
89
+ }
90
+
91
+ const existingIndex = indexByName.get(groupName);
92
+ if (existingIndex === undefined) {
93
+ const id = groupName
94
+ .toLowerCase()
95
+ .replace(/[^a-z0-9]+/g, '-')
96
+ .replace(/(^-|-$)/g, '');
97
+ indexByName.set(groupName, orderedGroups.length);
98
+ orderedGroups.push({ name: groupName, id: id || 'general', fields: [field] });
99
+ } else {
100
+ orderedGroups[existingIndex].fields.push(field);
101
+ }
102
+ }
103
+
104
+ return { topFields: top, groupedFields: orderedGroups };
105
+ });
60
106
  </script>
61
107
 
62
- <div class="stg">
63
- <button class="stg-toggle" onclick={() => (open = !open)}>
64
- <svg class="stg-icon" class:stg-icon--open={open} viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round">
108
+ <div class="stg" style="{themeStyle}; color-scheme: {colorScheme}">
109
+ <button
110
+ class="stg-hd"
111
+ aria-expanded={open}
112
+ aria-controls={panelId}
113
+ onclick={() => (open = !open)}
114
+ >
115
+ <svg
116
+ class="stg-chevron"
117
+ class:stg-chevron--open={open}
118
+ viewBox="0 0 16 16"
119
+ fill="none"
120
+ stroke="currentColor"
121
+ stroke-width="1.5"
122
+ stroke-linecap="round"
123
+ >
65
124
  <path d="M6 4l4 4-4 4" />
66
125
  </svg>
67
126
  <span>Settings</span>
68
127
  </button>
69
128
 
70
129
  {#if open}
71
- <div class="stg-body" transition:slide={{ duration: 180 }}>
72
- <!-- Theme picker -->
73
- <div class="stg-row">
74
- <span class="stg-label">Theme</span>
75
- <div class="stg-themes">
76
- {#each themeKeys as k (k)}
77
- <button
78
- class="stg-chip"
79
- class:stg-chip--active={theme === k}
80
- style="--chip-c: {themeAccents[k]}"
81
- onclick={() => (theme = k)}
82
- >
83
- <span class="stg-chip-dot"></span>
84
- {themeLabels[k]}
85
- </button>
86
- {/each}
130
+ <div class="stg-body" id={panelId} transition:slide={{ duration: 180 }}>
131
+ <!-- ─── Top bar: theme + ungrouped fields ─── -->
132
+ <div class="stg-bar">
133
+ <div class="stg-bar-item">
134
+ <span class="stg-col-title" id="stg-theme-label">Theme</span>
135
+ <div class="stg-pills" role="radiogroup" aria-labelledby="stg-theme-label">
136
+ {#each themeKeys as k (k)}
137
+ <button
138
+ type="button"
139
+ class="stg-pill"
140
+ class:stg-pill--on={theme === k}
141
+ role="radio"
142
+ aria-checked={theme === k}
143
+ onclick={() => (theme = k)}
144
+ >
145
+ {themeLabels[k]}
146
+ </button>
147
+ {/each}
148
+ </div>
87
149
  </div>
88
- </div>
89
150
 
90
- <!-- Dynamic fields -->
91
- {#each fields as f (f.key)}
92
- <div class="stg-row">
93
- <span class="stg-label">{f.label}</span>
94
- {#if f.type === 'range'}
95
- <div class="stg-slider-wrap">
96
- <input
97
- class="stg-slider"
98
- type="range"
99
- min={f.min}
100
- max={f.max}
101
- step={f.step}
102
- value={values[f.key]}
103
- oninput={(e) => setVal(f.key, Number((e.target as HTMLInputElement).value))}
104
- />
105
- <span class="stg-val">{fmt(values[f.key])}</span>
151
+ {#each topFields as f (f.key)}
152
+ {@const fid = controlId(f.key)}
153
+ {#if f.type === 'segment'}
154
+ <div class="stg-bar-item">
155
+ {#if f.label}
156
+ <span class="stg-col-title" id={`${fid}-label`}>{f.label}</span>
157
+ {/if}
158
+ <div class="stg-pills" role="radiogroup" aria-labelledby={f.label ? `${fid}-label` : undefined} aria-label={f.label ? undefined : f.key}>
159
+ {#each f.options as option (option.value)}
160
+ <button
161
+ type="button"
162
+ class="stg-pill"
163
+ class:stg-pill--on={String(values[f.key]) === option.value}
164
+ role="radio"
165
+ aria-checked={String(values[f.key]) === option.value}
166
+ onclick={() => setVal(f.key, option.value)}
167
+ >
168
+ {option.label}
169
+ </button>
170
+ {/each}
171
+ </div>
106
172
  </div>
107
- {:else if f.type === 'toggle'}
108
- <button
109
- class="stg-sw"
110
- class:stg-sw--on={values[f.key] === true}
111
- onclick={() => setVal(f.key, !values[f.key])}
112
- aria-pressed={values[f.key] === true}
113
- aria-label={f.label}
114
- >
115
- <span class="stg-sw-knob"></span>
116
- </button>
117
173
  {/if}
174
+ {/each}
175
+ </div>
176
+
177
+ <!-- ─── Grouped fields in columns ─── -->
178
+ {#if groupedFields.length > 0}
179
+ <div class="stg-grid">
180
+ {#each groupedFields as section (section.id)}
181
+ <section class="stg-col" aria-labelledby={`stg-s-${section.id}`}>
182
+ <h3 class="stg-col-title" id={`stg-s-${section.id}`}>{section.name}</h3>
183
+
184
+ {#each section.fields as f (f.key)}
185
+ {@const fid = controlId(f.key)}
186
+
187
+ {#if f.type === 'toggle'}
188
+ <label class="stg-row stg-row--toggle" for={fid}>
189
+ <span class="stg-lbl">{f.label}</span>
190
+ <input
191
+ id={fid}
192
+ class="stg-sr"
193
+ type="checkbox"
194
+ role="switch"
195
+ checked={values[f.key] === true}
196
+ onchange={(e) => setVal(f.key, (e.target as HTMLInputElement).checked)}
197
+ />
198
+ <span class="stg-sw" aria-hidden="true">
199
+ <span class="stg-sw-knob"></span>
200
+ </span>
201
+ </label>
202
+
203
+ {:else if f.type === 'range'}
204
+ {@const disabled = !!f.enabledWhen && values[f.enabledWhen] !== true}
205
+ <div class="stg-row stg-row--rng" class:stg-row--disabled={disabled}>
206
+ <div class="stg-rng-hd">
207
+ <label class="stg-lbl" for={fid}>{f.label}</label>
208
+ <span class="stg-num">{fmt(values[f.key])}</span>
209
+ </div>
210
+ <input
211
+ id={fid}
212
+ class="stg-rng"
213
+ type="range"
214
+ min={f.min}
215
+ max={f.max}
216
+ step={f.step}
217
+ value={values[f.key]}
218
+ {disabled}
219
+ oninput={(e) => setVal(f.key, Number((e.target as HTMLInputElement).value))}
220
+ />
221
+ </div>
222
+
223
+ {:else if f.type === 'select'}
224
+ <div class="stg-row">
225
+ <label class="stg-lbl" for={fid}>{f.label}</label>
226
+ <select
227
+ id={fid}
228
+ class="stg-sel"
229
+ value={String(values[f.key] ?? '')}
230
+ onchange={(e) => setVal(f.key, (e.target as HTMLSelectElement).value)}
231
+ >
232
+ {#each f.options as option (option.value)}
233
+ <option value={option.value}>{option.label}</option>
234
+ {/each}
235
+ </select>
236
+ </div>
237
+
238
+ {:else if f.type === 'segment'}
239
+ <div class="stg-row stg-row--seg">
240
+ {#if f.label}
241
+ <span class="stg-lbl" id={`${fid}-label`}>{f.label}</span>
242
+ {/if}
243
+ <div class="stg-pills" role="radiogroup" aria-labelledby={f.label ? `${fid}-label` : undefined} aria-label={f.label ? undefined : f.key}>
244
+ {#each f.options as option (option.value)}
245
+ <button
246
+ type="button"
247
+ class="stg-pill"
248
+ class:stg-pill--on={String(values[f.key]) === option.value}
249
+ role="radio"
250
+ aria-checked={String(values[f.key]) === option.value}
251
+ onclick={() => setVal(f.key, option.value)}
252
+ >
253
+ {option.label}
254
+ </button>
255
+ {/each}
256
+ </div>
257
+ </div>
258
+ {/if}
259
+ {/each}
260
+ </section>
261
+ {/each}
118
262
  </div>
119
- {/each}
263
+ {/if}
120
264
  </div>
121
265
  {/if}
122
266
  </div>
123
267
 
124
268
  <style>
269
+ /* ─── Root: themed via --dt-* tokens from inline style ─── */
125
270
  .stg {
126
- border: 1px solid rgba(148, 160, 180, 0.06);
271
+ border: 1px solid var(--dt-border, rgba(148, 160, 180, 0.10));
127
272
  border-radius: 10px;
128
- background: rgba(148, 160, 180, 0.02);
273
+ background: var(--dt-surface, #10141c);
129
274
  overflow: hidden;
275
+ font-family: var(--dt-sans, 'Outfit', system-ui, sans-serif);
130
276
  margin-bottom: 20px;
131
277
  }
132
278
 
133
- /* ─── Toggle header ──────────────────────────────── */
134
- .stg-toggle {
279
+ /* ─── Collapsible header ─────────────────────────── */
280
+ .stg-hd {
135
281
  display: flex;
136
282
  align-items: center;
137
- gap: 6px;
283
+ gap: 7px;
138
284
  width: 100%;
139
285
  padding: 10px 14px;
140
286
  background: none;
141
287
  border: none;
142
288
  cursor: pointer;
143
- font: 500 10px / 1 'Outfit', system-ui, sans-serif;
144
- letter-spacing: 0.08em;
289
+ font-weight: 600;
290
+ font-size: 12px;
291
+ line-height: 1;
292
+ letter-spacing: 0.06em;
145
293
  text-transform: uppercase;
146
- color: rgba(148, 160, 180, 0.4);
294
+ color: var(--dt-text-2, rgba(148, 160, 180, 0.62));
147
295
  transition: color 120ms;
148
296
  }
149
- .stg-toggle:hover {
150
- color: rgba(220, 225, 235, 0.7);
297
+ .stg-hd:hover {
298
+ color: var(--dt-text, rgba(228, 234, 245, 0.88));
151
299
  }
152
300
 
153
- .stg-icon {
154
- width: 12px;
155
- height: 12px;
156
- transition: transform 160ms ease;
301
+ .stg-chevron {
302
+ width: 11px;
303
+ height: 11px;
157
304
  flex-shrink: 0;
305
+ transition: transform 150ms ease;
158
306
  }
159
- .stg-icon--open {
307
+ .stg-chevron--open {
160
308
  transform: rotate(90deg);
161
309
  }
162
310
 
163
- /* ─── Body ────────────────────────────────────────── */
311
+ /* ─── Body ───────────────────────────────────────── */
164
312
  .stg-body {
165
- padding: 2px 14px 14px;
313
+ padding: 0 14px 12px;
166
314
  display: flex;
167
315
  flex-direction: column;
168
- gap: 10px;
316
+ gap: 6px;
317
+ }
318
+
319
+ /* ─── Top bar: theme + mode pills ────────────────── */
320
+ .stg-bar {
321
+ display: flex;
322
+ flex-wrap: wrap;
323
+ align-items: center;
324
+ gap: 6px 20px;
325
+ padding-bottom: 4px;
169
326
  }
170
327
 
171
- /* ─── Row ─────────────────────────────────────────── */
328
+ .stg-bar-item {
329
+ display: flex;
330
+ align-items: center;
331
+ gap: 8px;
332
+ }
333
+
334
+ /* ─── Column grid for grouped fields ─────────────── */
335
+ .stg-grid {
336
+ display: grid;
337
+ grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
338
+ gap: 2px 20px;
339
+ border-top: 1px solid color-mix(in srgb, var(--dt-border, rgba(148, 160, 180, 0.10)) 50%, transparent);
340
+ padding-top: 6px;
341
+ }
342
+
343
+ .stg-col {
344
+ padding: 4px 0 2px;
345
+ min-width: 0;
346
+ }
347
+
348
+ .stg-col-title {
349
+ margin: 0 0 2px;
350
+ font-weight: 600;
351
+ font-size: 11px;
352
+ line-height: 1;
353
+ letter-spacing: 0.08em;
354
+ text-transform: uppercase;
355
+ color: var(--dt-text-3, rgba(148, 160, 180, 0.40));
356
+ }
357
+
358
+ /* ─── Row (inline: label left, control right) ──── */
172
359
  .stg-row {
173
360
  display: flex;
174
361
  align-items: center;
175
- gap: 12px;
176
- min-height: 28px;
362
+ justify-content: space-between;
363
+ gap: 10px;
364
+ min-height: 26px;
365
+ padding: 2px 0;
177
366
  }
178
- .stg-label {
179
- flex-shrink: 0;
180
- width: 100px;
181
- font: 400 10px / 1 'SF Mono', 'Cascadia Code', 'Fira Code', Consolas, monospace;
182
- color: rgba(148, 160, 180, 0.35);
183
- white-space: nowrap;
367
+
368
+ .stg-row--rng {
369
+ flex-direction: column;
370
+ align-items: stretch;
371
+ justify-content: flex-start;
372
+ gap: 2px;
373
+ min-height: auto;
374
+ padding: 4px 0;
184
375
  }
185
376
 
186
- /* ─── Theme chips ────────────────────────────────── */
187
- .stg-themes {
377
+ .stg-row--disabled {
378
+ opacity: 0.35;
379
+ pointer-events: none;
380
+ }
381
+
382
+ .stg-rng-hd {
188
383
  display: flex;
189
- gap: 6px;
190
- flex-wrap: wrap;
384
+ align-items: baseline;
385
+ justify-content: space-between;
386
+ gap: 8px;
191
387
  }
192
- .stg-chip {
193
- display: inline-flex;
194
- align-items: center;
195
- gap: 5px;
196
- padding: 4px 10px;
197
- border-radius: 100px;
198
- border: 1px solid rgba(148, 160, 180, 0.08);
199
- background: rgba(148, 160, 180, 0.03);
388
+
389
+ .stg-row--seg {
390
+ flex-direction: column;
391
+ align-items: flex-start;
392
+ justify-content: flex-start;
393
+ gap: 3px;
394
+ padding: 3px 0;
395
+ }
396
+
397
+ .stg-row--toggle {
200
398
  cursor: pointer;
201
- font: 400 10px / 1 'Outfit', system-ui, sans-serif;
202
- color: rgba(148, 160, 180, 0.4);
203
- transition: border-color 140ms, background 140ms, color 140ms;
204
- }
205
- .stg-chip:hover {
206
- border-color: rgba(148, 160, 180, 0.15);
207
- color: rgba(220, 225, 235, 0.6);
208
- }
209
- .stg-chip--active {
210
- border-color: var(--chip-c);
211
- background: color-mix(in srgb, var(--chip-c) 8%, transparent);
212
- color: var(--chip-c);
213
- }
214
- .stg-chip-dot {
215
- width: 7px;
216
- height: 7px;
399
+ }
400
+
401
+ /* ─── Label ──────────────────────────────────────── */
402
+ .stg-lbl {
403
+ font-weight: 400;
404
+ font-size: 13px;
405
+ line-height: 1.2;
406
+ color: var(--dt-text-2, rgba(148, 160, 180, 0.62));
407
+ white-space: nowrap;
408
+ flex-shrink: 0;
409
+ }
410
+
411
+ /* ─── Toggle switch ──────────────────────────────── */
412
+ .stg-sr {
413
+ position: absolute;
414
+ width: 1px;
415
+ height: 1px;
416
+ margin: -1px;
417
+ padding: 0;
418
+ overflow: hidden;
419
+ clip: rect(0, 0, 0, 0);
420
+ border: 0;
421
+ }
422
+
423
+ .stg-sw {
424
+ position: relative;
425
+ display: block;
426
+ width: 32px;
427
+ height: 18px;
428
+ border-radius: 9px;
429
+ border: 1px solid var(--dt-border, rgba(148, 160, 180, 0.10));
430
+ background: color-mix(in srgb, var(--dt-text-3, rgba(148, 160, 180, 0.40)) 10%, transparent);
431
+ cursor: pointer;
432
+ flex-shrink: 0;
433
+ transition: background 150ms, border-color 150ms;
434
+ }
435
+ .stg-sr:checked + .stg-sw {
436
+ background: var(--dt-accent-dim, rgba(99, 102, 241, 0.12));
437
+ border-color: color-mix(in srgb, var(--dt-accent, #6366f1) 35%, transparent);
438
+ }
439
+
440
+ .stg-sw-knob {
441
+ position: absolute;
442
+ top: 2px;
443
+ left: 2px;
444
+ width: 12px;
445
+ height: 12px;
217
446
  border-radius: 50%;
218
- background: var(--chip-c);
219
- opacity: 0.6;
447
+ background: var(--dt-text-3, rgba(148, 160, 180, 0.40));
448
+ transition: transform 150ms ease, background 150ms;
220
449
  }
221
- .stg-chip--active .stg-chip-dot {
222
- opacity: 1;
223
- box-shadow: 0 0 6px var(--chip-c);
450
+ .stg-sr:checked + .stg-sw .stg-sw-knob {
451
+ transform: translateX(14px);
452
+ background: var(--dt-accent, #6366f1);
224
453
  }
225
454
 
226
455
  /* ─── Range slider ───────────────────────────────── */
227
- .stg-slider-wrap {
228
- flex: 1;
229
- display: flex;
230
- align-items: center;
231
- gap: 10px;
232
- }
233
- .stg-slider {
234
- flex: 1;
456
+ .stg-rng {
457
+ width: 100%;
235
458
  -webkit-appearance: none;
236
459
  appearance: none;
237
460
  height: 3px;
238
461
  border-radius: 2px;
239
- background: rgba(148, 160, 180, 0.1);
462
+ background: color-mix(in srgb, var(--dt-text-3, rgba(148, 160, 180, 0.40)) 30%, transparent);
240
463
  outline: none;
241
464
  cursor: pointer;
242
465
  }
243
- .stg-slider::-webkit-slider-thumb {
466
+ .stg-rng::-webkit-slider-thumb {
244
467
  -webkit-appearance: none;
245
- width: 14px;
246
- height: 14px;
468
+ width: 12px;
469
+ height: 12px;
247
470
  border-radius: 50%;
248
- background: rgba(220, 225, 235, 0.5);
249
- border: 2px solid rgba(220, 225, 235, 0.15);
471
+ background: var(--dt-text-2, rgba(148, 160, 180, 0.62));
472
+ border: none;
250
473
  cursor: grab;
251
474
  transition: background 100ms, transform 100ms;
252
475
  }
253
- .stg-slider::-webkit-slider-thumb:hover {
254
- background: rgba(220, 225, 235, 0.7);
476
+ .stg-rng::-webkit-slider-thumb:hover {
477
+ background: var(--dt-text, rgba(228, 234, 245, 0.88));
255
478
  transform: scale(1.15);
256
479
  }
257
- .stg-slider::-webkit-slider-thumb:active {
480
+ .stg-rng::-webkit-slider-thumb:active {
258
481
  cursor: grabbing;
259
482
  }
260
- .stg-slider::-moz-range-thumb {
261
- width: 14px;
262
- height: 14px;
483
+ .stg-rng::-moz-range-thumb {
484
+ width: 12px;
485
+ height: 12px;
263
486
  border-radius: 50%;
264
- background: rgba(220, 225, 235, 0.5);
265
- border: 2px solid rgba(220, 225, 235, 0.15);
487
+ background: var(--dt-text-2, rgba(148, 160, 180, 0.62));
488
+ border: none;
266
489
  cursor: grab;
267
490
  }
268
- .stg-val {
491
+
492
+ .stg-rng:disabled {
493
+ cursor: default;
494
+ }
495
+
496
+ .stg-num {
269
497
  flex-shrink: 0;
270
- min-width: 38px;
498
+ min-width: 22px;
271
499
  text-align: right;
272
- font: 500 10px / 1 'SF Mono', 'Cascadia Code', monospace;
273
- color: rgba(220, 225, 235, 0.45);
500
+ font: 500 11px / 1 var(--dt-mono, 'SF Mono', 'Cascadia Code', ui-monospace, monospace);
274
501
  font-variant-numeric: tabular-nums;
502
+ color: var(--dt-text-2, rgba(148, 160, 180, 0.62));
275
503
  }
276
504
 
277
- /* ─── Toggle switch ──────────────────────────────── */
278
- .stg-sw {
279
- position: relative;
280
- width: 34px;
281
- height: 18px;
282
- border-radius: 9px;
283
- border: 1px solid rgba(148, 160, 180, 0.12);
284
- background: rgba(148, 160, 180, 0.08);
505
+ /* ─── Select dropdown ────────────────────────────── */
506
+ .stg-sel {
507
+ appearance: none;
508
+ font-weight: 400;
509
+ font-size: 13px;
510
+ line-height: 1;
511
+ padding: 5px 24px 5px 8px;
512
+ border-radius: 6px;
513
+ border: 1px solid var(--dt-border, rgba(148, 160, 180, 0.10));
514
+ background: color-mix(in srgb, var(--dt-text-3, rgba(148, 160, 180, 0.40)) 8%, var(--dt-surface, #10141c));
515
+ color: var(--dt-text, rgba(228, 234, 245, 0.88));
516
+ outline: none;
285
517
  cursor: pointer;
286
- padding: 0;
287
- transition: background 160ms, border-color 160ms;
518
+ background-image: linear-gradient(45deg, transparent 50%, var(--dt-text-3, rgba(148, 160, 180, 0.40)) 50%),
519
+ linear-gradient(135deg, var(--dt-text-3, rgba(148, 160, 180, 0.40)) 50%, transparent 50%);
520
+ background-position: calc(100% - 12px) center, calc(100% - 8px) center;
521
+ background-size: 4px 4px, 4px 4px;
522
+ background-repeat: no-repeat;
523
+ transition: border-color 120ms;
288
524
  }
289
- .stg-sw--on {
290
- background: rgba(99, 102, 241, 0.25);
291
- border-color: rgba(99, 102, 241, 0.35);
525
+ .stg-sel:hover {
526
+ border-color: var(--dt-border-day, rgba(148, 160, 180, 0.14));
292
527
  }
293
- .stg-sw-knob {
294
- position: absolute;
295
- top: 2px;
296
- left: 2px;
297
- width: 12px;
298
- height: 12px;
299
- border-radius: 50%;
300
- background: rgba(148, 160, 180, 0.4);
301
- transition: transform 160ms ease, background 160ms;
528
+ .stg-sel:focus {
529
+ border-color: color-mix(in srgb, var(--dt-accent, #6366f1) 45%, transparent);
530
+ }
531
+ .stg-sel option {
532
+ background: var(--dt-bg, #0b0e14);
533
+ color: var(--dt-text, rgba(228, 234, 245, 0.88));
534
+ }
535
+
536
+ /* ─── Segment pills ──────────────────────────────── */
537
+ .stg-pills {
538
+ display: flex;
539
+ flex-wrap: wrap;
540
+ gap: 4px;
541
+ }
542
+
543
+ .stg-pill {
544
+ padding: 4px 9px;
545
+ border-radius: 100px;
546
+ border: 1px solid color-mix(in srgb, var(--dt-border, rgba(148, 160, 180, 0.10)) 60%, transparent);
547
+ background: color-mix(in srgb, var(--dt-text-3, rgba(148, 160, 180, 0.40)) 8%, transparent);
548
+ color: var(--dt-text-2, rgba(148, 160, 180, 0.62));
549
+ font-weight: 400;
550
+ font-size: 12px;
551
+ line-height: 1;
552
+ cursor: pointer;
553
+ transition: border-color 120ms, color 120ms, background 120ms;
554
+ }
555
+ .stg-pill:hover {
556
+ color: var(--dt-text, rgba(228, 234, 245, 0.88));
557
+ border-color: var(--dt-border, rgba(148, 160, 180, 0.10));
302
558
  }
303
- .stg-sw--on .stg-sw-knob {
304
- transform: translateX(16px);
305
- background: rgba(99, 102, 241, 0.9);
559
+ .stg-pill--on {
560
+ color: var(--dt-accent, #6366f1);
561
+ border-color: color-mix(in srgb, var(--dt-accent, #6366f1) 35%, transparent);
562
+ background: var(--dt-accent-dim, rgba(99, 102, 241, 0.12));
563
+ }
564
+
565
+ /* ─── Focus rings ────────────────────────────────── */
566
+ .stg-sr:focus-visible + .stg-sw,
567
+ .stg-sel:focus-visible,
568
+ .stg-rng:focus-visible,
569
+ .stg-pill:focus-visible,
570
+ .stg-hd:focus-visible {
571
+ outline: 2px solid color-mix(in srgb, var(--dt-accent, #6366f1) 55%, transparent);
572
+ outline-offset: 2px;
573
+ }
574
+
575
+ /* ─── Responsive: stack columns on small screens ── */
576
+ @media (max-width: 600px) {
577
+ .stg-grid {
578
+ grid-template-columns: 1fr;
579
+ }
580
+ .stg-bar {
581
+ flex-direction: column;
582
+ gap: 6px;
583
+ }
306
584
  }
307
585
  </style>