custom-elements-ts 0.0.17 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/.eslintrc.json +46 -0
  2. package/.github/workflows/ci.yml +49 -0
  3. package/.prettierrc +7 -0
  4. package/LICENSE +20 -0
  5. package/README.md +270 -35
  6. package/demos/counter/counter.element.html +1 -0
  7. package/demos/counter/counter.element.scss +234 -0
  8. package/demos/counter/counter.element.ts +68 -0
  9. package/demos/counter/index.html +205 -0
  10. package/demos/counter/index.ts +1 -0
  11. package/demos/site/code-example/code-example.element.scss +168 -0
  12. package/demos/site/code-example/code-example.element.ts +88 -0
  13. package/demos/site/event-log/event-log.element.scss +179 -0
  14. package/demos/site/event-log/event-log.element.ts +134 -0
  15. package/demos/site/favicon.svg +14 -0
  16. package/demos/site/index.html +346 -0
  17. package/demos/site/index.ts +13 -0
  18. package/demos/site/message/message.element.scss +75 -0
  19. package/demos/site/message/message.element.ts +76 -0
  20. package/demos/site/og-image.png +0 -0
  21. package/demos/site/styles/site.css +1023 -0
  22. package/demos/site/styles/tokens.css +56 -0
  23. package/demos/site/toast/toast.element.scss +110 -0
  24. package/demos/site/toast/toast.element.ts +63 -0
  25. package/demos/todo-dashboard/index.html +141 -0
  26. package/demos/todo-dashboard/index.ts +4 -0
  27. package/demos/todo-dashboard/todo-dashboard.element.scss +1145 -0
  28. package/demos/todo-dashboard/todo-dashboard.element.ts +332 -0
  29. package/demos/todo-dashboard/todo-filters.element.ts +54 -0
  30. package/demos/todo-dashboard/todo-item.element.ts +126 -0
  31. package/demos/todo-dashboard/todo-stats.element.ts +189 -0
  32. package/package.json +73 -24
  33. package/src/custom-element.ts +206 -0
  34. package/{esm5/index.d.ts → src/index.ts} +7 -5
  35. package/src/listen.ts +70 -0
  36. package/src/prop.ts +92 -0
  37. package/src/state.ts +129 -0
  38. package/src/template-runtime.ts +435 -0
  39. package/src/toggle.ts +66 -0
  40. package/src/tsconfig.json +24 -0
  41. package/src/util.ts +33 -0
  42. package/src/watch.ts +14 -0
  43. package/tests/basic.spec.ts +70 -0
  44. package/tests/custom-element.spec.ts +77 -0
  45. package/tests/dispatch.spec.ts +52 -0
  46. package/tests/init.spec.ts +94 -0
  47. package/tests/listen.spec.ts +118 -0
  48. package/tests/prop.spec.ts +118 -0
  49. package/tests/templating-runtime.spec.ts +575 -0
  50. package/tests/toggle.spec.ts +92 -0
  51. package/tests/watch.spec.ts +183 -0
  52. package/tools/build.js +119 -0
  53. package/tools/bundle.js +167 -0
  54. package/tools/rollup-config.js +70 -0
  55. package/tools/start.js +188 -0
  56. package/tsconfig.json +38 -0
  57. package/vite.config.mts +30 -0
  58. package/bundles/custom-elements-ts.umd.js +0 -359
  59. package/bundles/custom-elements-ts.umd.js.map +0 -1
  60. package/esm2015/custom-elements-ts.js +0 -283
  61. package/esm2015/custom-elements-ts.js.map +0 -1
  62. package/esm5/custom-element.d.ts +0 -12
  63. package/esm5/custom-elements-ts.js +0 -344
  64. package/esm5/custom-elements-ts.js.map +0 -1
  65. package/esm5/listen.d.ts +0 -17
  66. package/esm5/prop.d.ts +0 -2
  67. package/esm5/toggle.d.ts +0 -1
  68. package/esm5/util.d.ts +0 -4
  69. package/esm5/watch.d.ts +0 -1
@@ -0,0 +1,1145 @@
1
+ /* ─────────────────────────────────────────────────────────────
2
+ cts-todo-dashboard
3
+ Dark, technical surface aligned with demos/site tokens.
4
+ Sharp radii (2–4px), Geist typography, single TS-blue accent.
5
+ ───────────────────────────────────────────────────────────── */
6
+
7
+ :host {
8
+ /* Surfaces */
9
+ --bg: #0a0c10;
10
+ --bg-elev-1: #11141a;
11
+ --bg-elev-2: #161a22;
12
+ --bg-elev-3: #1c2230;
13
+
14
+ /* Lines */
15
+ --line: rgba(255, 255, 255, 0.06);
16
+ --line-strong: rgba(255, 255, 255, 0.1);
17
+ --line-accent: rgba(49, 120, 198, 0.32);
18
+
19
+ /* Type */
20
+ --fg: #e6ecf3;
21
+ --fg-muted: #97a1ad;
22
+ --fg-subtle: #5b6370;
23
+
24
+ /* Accent — TypeScript brand blue. One color, used sparingly. */
25
+ --accent: #3178c6;
26
+ --accent-bright: #4189d6;
27
+ --accent-fg: #9cc0eb;
28
+ --accent-soft: rgba(49, 120, 198, 0.14);
29
+ --accent-line: rgba(49, 120, 198, 0.42);
30
+
31
+ /* Status — kept neutral except for destructive remove */
32
+ --ok: #6fcf97;
33
+ --ok-soft: rgba(111, 207, 151, 0.18);
34
+ --danger: #c46a6a;
35
+ --danger-soft: rgba(196, 106, 106, 0.1);
36
+
37
+ /* Radii — sharp, technical, editorial. */
38
+ --radius-xs: 2px;
39
+ --radius-sm: 2px;
40
+ --radius-md: 3px;
41
+ --radius-lg: 4px;
42
+
43
+ /* Motion */
44
+ --ease: cubic-bezier(0.22, 1, 0.36, 1);
45
+ --dur-fast: 140ms;
46
+ --dur: 220ms;
47
+ --dur-slow: 420ms;
48
+
49
+ display: block;
50
+ width: min(720px, calc(100vw - 32px));
51
+ color: var(--fg);
52
+ font-family:
53
+ 'Geist',
54
+ 'Inter',
55
+ ui-sans-serif,
56
+ system-ui,
57
+ -apple-system,
58
+ 'Segoe UI',
59
+ Roboto,
60
+ 'Helvetica Neue',
61
+ Arial,
62
+ sans-serif;
63
+ -webkit-font-smoothing: antialiased;
64
+ text-rendering: optimizeLegibility;
65
+ }
66
+
67
+ * {
68
+ box-sizing: border-box;
69
+ }
70
+
71
+ /* ── Shell ───────────────────────────────────────────────── */
72
+
73
+ .shell {
74
+ position: relative;
75
+ overflow: hidden;
76
+ border: 1px solid var(--line-strong);
77
+ border-radius: var(--radius-lg);
78
+ background:
79
+ radial-gradient(120% 120% at 0% 0%, rgba(49, 120, 198, 0.06) 0%, transparent 55%),
80
+ linear-gradient(180deg, var(--bg-elev-1) 0%, #0f131b 100%);
81
+ box-shadow:
82
+ 0 1px 0 rgba(255, 255, 255, 0.04) inset,
83
+ 0 22px 50px -28px rgba(0, 0, 0, 0.65),
84
+ 0 8px 24px -12px rgba(0, 0, 0, 0.45);
85
+ animation: shell-in 520ms var(--ease) both;
86
+ }
87
+
88
+ .shell.compact {
89
+ width: min(540px, calc(100vw - 32px));
90
+ }
91
+
92
+ @keyframes shell-in {
93
+ from {
94
+ opacity: 0;
95
+ transform: translateY(8px);
96
+ }
97
+ to {
98
+ opacity: 1;
99
+ transform: translateY(0);
100
+ }
101
+ }
102
+
103
+ /* ── Header ──────────────────────────────────────────────── */
104
+
105
+ .header {
106
+ display: grid;
107
+ grid-template-columns: 1fr auto;
108
+ align-items: flex-start;
109
+ gap: 18px;
110
+ padding: 22px 24px 18px;
111
+ }
112
+
113
+ .title-block {
114
+ min-width: 0;
115
+ }
116
+
117
+ .eyebrow {
118
+ display: inline-flex;
119
+ align-items: center;
120
+ gap: 8px;
121
+ margin: 0 0 10px;
122
+ padding: 0;
123
+ color: var(--fg-muted);
124
+ font-family:
125
+ 'Geist Mono', 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, 'Courier New',
126
+ monospace;
127
+ font-size: 11px;
128
+ font-weight: 500;
129
+ letter-spacing: 0.14em;
130
+ text-transform: uppercase;
131
+ }
132
+
133
+ .eyebrow-dot {
134
+ width: 6px;
135
+ height: 6px;
136
+ background: var(--accent);
137
+ box-shadow: 0 0 10px var(--accent-line);
138
+ }
139
+
140
+ h1 {
141
+ margin: 0 0 8px;
142
+ color: var(--fg);
143
+ font-size: 26px;
144
+ line-height: 1.1;
145
+ font-weight: 600;
146
+ letter-spacing: -0.02em;
147
+ text-wrap: balance;
148
+ }
149
+
150
+ .meta {
151
+ display: flex;
152
+ flex-wrap: wrap;
153
+ align-items: baseline;
154
+ gap: 8px;
155
+ margin: 0;
156
+ color: var(--fg-muted);
157
+ font-size: 13px;
158
+ font-variant-numeric: tabular-nums;
159
+ }
160
+
161
+ .meta strong {
162
+ color: var(--fg);
163
+ font-weight: 600;
164
+ }
165
+
166
+ .dot-sep {
167
+ color: var(--fg-subtle);
168
+ }
169
+
170
+ /* ── Revision badge ──────────────────────────────────────── */
171
+
172
+ .revision {
173
+ display: inline-flex;
174
+ align-items: center;
175
+ gap: 8px;
176
+ padding: 5px 9px;
177
+ border: 1px solid var(--line-strong);
178
+ border-radius: var(--radius-xs);
179
+ background: rgba(255, 255, 255, 0.02);
180
+ color: var(--fg-muted);
181
+ font-family:
182
+ 'Geist Mono', 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, 'Courier New',
183
+ monospace;
184
+ font-size: 11px;
185
+ font-weight: 500;
186
+ letter-spacing: 0.04em;
187
+ }
188
+
189
+ .revision-pulse {
190
+ position: relative;
191
+ width: 6px;
192
+ height: 6px;
193
+ background: var(--ok);
194
+ box-shadow: 0 0 0 2px var(--ok-soft);
195
+ }
196
+
197
+ .revision-pulse::after {
198
+ content: '';
199
+ position: absolute;
200
+ inset: -3px;
201
+ border: 1px solid rgba(111, 207, 151, 0.55);
202
+ animation: pulse 2200ms var(--ease) infinite;
203
+ }
204
+
205
+ @keyframes pulse {
206
+ 0% {
207
+ transform: scale(0.6);
208
+ opacity: 0.9;
209
+ }
210
+ 100% {
211
+ transform: scale(2.4);
212
+ opacity: 0;
213
+ }
214
+ }
215
+
216
+ .revision-label {
217
+ color: var(--fg-muted);
218
+ text-transform: uppercase;
219
+ letter-spacing: 0.12em;
220
+ font-size: 10px;
221
+ }
222
+
223
+ .revision-num {
224
+ color: var(--accent-fg);
225
+ font-weight: 600;
226
+ }
227
+
228
+ /* ── Progress bar ────────────────────────────────────────── */
229
+
230
+ .progress {
231
+ position: relative;
232
+ height: 2px;
233
+ margin: 0 24px;
234
+ background: var(--line);
235
+ overflow: hidden;
236
+ }
237
+
238
+ .progress-fill {
239
+ display: block;
240
+ width: 100%;
241
+ height: 100%;
242
+ transform: scaleX(var(--progress, 0));
243
+ transform-origin: left center;
244
+ background: linear-gradient(90deg, var(--accent) 0%, var(--accent-bright) 100%);
245
+ transition: transform 360ms var(--ease);
246
+ will-change: transform;
247
+ }
248
+
249
+ /* ── Child component hosts ───────────────────────────────── */
250
+
251
+ cts-todo-stats,
252
+ cts-todo-filters,
253
+ cts-todo-item {
254
+ display: block;
255
+ }
256
+
257
+ /* ── Stats panel (Progress + Sprint goal) ────────────────── */
258
+
259
+ .stats-panel {
260
+ display: grid;
261
+ grid-template-columns: 1fr 1fr;
262
+ align-items: stretch;
263
+ gap: 10px;
264
+ padding: 18px 24px 0;
265
+ }
266
+
267
+ .stat-card {
268
+ position: relative;
269
+ display: grid;
270
+ grid-template-rows: auto auto auto 1fr;
271
+ gap: 10px;
272
+ min-width: 0;
273
+ padding: 14px 16px 14px;
274
+ border: 1px solid var(--line);
275
+ border-radius: var(--radius-md);
276
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.018) 0%, rgba(255, 255, 255, 0) 100%);
277
+ overflow: hidden;
278
+ }
279
+
280
+ .stat-card::before {
281
+ content: '';
282
+ position: absolute;
283
+ inset: 0;
284
+ pointer-events: none;
285
+ background: radial-gradient(80% 60% at 100% 0%, rgba(49, 120, 198, 0.05) 0%, transparent 60%);
286
+ opacity: 0.8;
287
+ transition: opacity var(--dur) var(--ease);
288
+ }
289
+
290
+ .stat-card.is-complete::before,
291
+ .stat-card.is-met::before {
292
+ background: radial-gradient(80% 60% at 100% 0%, rgba(49, 120, 198, 0.16) 0%, transparent 60%);
293
+ opacity: 1;
294
+ }
295
+
296
+ .stat-head {
297
+ display: flex;
298
+ align-items: center;
299
+ justify-content: space-between;
300
+ gap: 10px;
301
+ min-height: 24px;
302
+ }
303
+
304
+ .stat-label {
305
+ color: var(--fg-muted);
306
+ font-family:
307
+ 'Geist Mono', 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, 'Courier New',
308
+ monospace;
309
+ font-size: 10px;
310
+ font-weight: 500;
311
+ letter-spacing: 0.14em;
312
+ text-transform: uppercase;
313
+ }
314
+
315
+ .stat-tag {
316
+ display: inline-flex;
317
+ align-items: center;
318
+ gap: 6px;
319
+ height: 20px;
320
+ padding: 0 7px;
321
+ border: 1px solid var(--line-strong);
322
+ border-radius: var(--radius-xs);
323
+ background: rgba(255, 255, 255, 0.02);
324
+ color: var(--fg-subtle);
325
+ font-family:
326
+ 'Geist Mono', 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, 'Courier New',
327
+ monospace;
328
+ font-size: 9.5px;
329
+ font-weight: 500;
330
+ letter-spacing: 0.12em;
331
+ text-transform: uppercase;
332
+ }
333
+
334
+ .stat-tag::before {
335
+ content: '';
336
+ width: 5px;
337
+ height: 5px;
338
+ background: var(--fg-subtle);
339
+ }
340
+
341
+ .stat-tag[data-state='live']::before {
342
+ background: var(--accent);
343
+ box-shadow: 0 0 6px var(--accent-line);
344
+ }
345
+
346
+ .stat-tag[data-state='complete']::before {
347
+ background: var(--ok);
348
+ box-shadow: 0 0 6px var(--ok-soft);
349
+ }
350
+
351
+ .stat-tag[data-state='live'] {
352
+ color: var(--accent-fg);
353
+ border-color: var(--accent-line);
354
+ background: var(--accent-soft);
355
+ }
356
+
357
+ .stat-tag[data-state='complete'] {
358
+ color: var(--ok);
359
+ border-color: rgba(111, 207, 151, 0.35);
360
+ background: rgba(111, 207, 151, 0.1);
361
+ }
362
+
363
+ /* Progress display */
364
+
365
+ .progress-display {
366
+ display: inline-flex;
367
+ align-items: baseline;
368
+ gap: 0;
369
+ margin-top: 2px;
370
+ font-family:
371
+ 'Geist Mono', 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, 'Courier New',
372
+ monospace;
373
+ line-height: 0.95;
374
+ font-variant-numeric: tabular-nums;
375
+ }
376
+
377
+ .progress-num {
378
+ font-size: 48px;
379
+ font-weight: 600;
380
+ letter-spacing: -0.045em;
381
+ background: linear-gradient(180deg, var(--fg) 0%, #c8d3e0 100%);
382
+ -webkit-background-clip: text;
383
+ background-clip: text;
384
+ -webkit-text-fill-color: transparent;
385
+ color: var(--fg);
386
+ }
387
+
388
+ .is-complete .progress-num {
389
+ background: linear-gradient(180deg, #b6e6c8 0%, var(--ok) 100%);
390
+ -webkit-background-clip: text;
391
+ background-clip: text;
392
+ -webkit-text-fill-color: transparent;
393
+ }
394
+
395
+ .progress-num.is-empty {
396
+ color: var(--fg-subtle);
397
+ -webkit-text-fill-color: var(--fg-subtle);
398
+ background: none;
399
+ }
400
+
401
+ .progress-suffix {
402
+ margin-left: 2px;
403
+ color: var(--fg-subtle);
404
+ font-size: 22px;
405
+ font-weight: 500;
406
+ letter-spacing: -0.02em;
407
+ }
408
+
409
+ .is-complete .progress-suffix {
410
+ color: var(--ok);
411
+ }
412
+
413
+ /* Inline mini progress track inside the card */
414
+
415
+ .progress-track {
416
+ position: relative;
417
+ height: 3px;
418
+ border-radius: 1px;
419
+ background: var(--line);
420
+ overflow: hidden;
421
+ }
422
+
423
+ .progress-track-fill {
424
+ display: block;
425
+ width: 100%;
426
+ height: 100%;
427
+ transform: scaleX(var(--p, 0));
428
+ transform-origin: left center;
429
+ background: linear-gradient(90deg, var(--accent) 0%, var(--accent-bright) 100%);
430
+ transition: transform 420ms var(--ease);
431
+ will-change: transform;
432
+ }
433
+
434
+ .is-complete .progress-track-fill {
435
+ background: linear-gradient(90deg, var(--accent-bright) 0%, var(--ok) 100%);
436
+ }
437
+
438
+ /* Goal pips */
439
+
440
+ .goal-pips {
441
+ display: flex;
442
+ align-items: center;
443
+ flex-wrap: wrap;
444
+ gap: 5px;
445
+ margin-top: 2px;
446
+ min-height: 22px;
447
+ }
448
+
449
+ .pip {
450
+ width: 16px;
451
+ height: 16px;
452
+ border: 1px solid var(--line-strong);
453
+ background: rgba(255, 255, 255, 0.02);
454
+ transition:
455
+ background var(--dur-fast) var(--ease),
456
+ border-color var(--dur-fast) var(--ease),
457
+ box-shadow var(--dur-fast) var(--ease);
458
+ }
459
+
460
+ .pip.is-filled {
461
+ border-color: var(--accent);
462
+ background: linear-gradient(180deg, var(--accent-bright) 0%, var(--accent) 100%);
463
+ box-shadow: 0 0 0 1px rgba(49, 120, 198, 0.2);
464
+ }
465
+
466
+ .is-met .pip.is-filled {
467
+ border-color: var(--ok);
468
+ background: linear-gradient(180deg, #88d6a4 0%, var(--ok) 100%);
469
+ box-shadow: 0 0 0 1px rgba(111, 207, 151, 0.25);
470
+ }
471
+
472
+ .pip-more {
473
+ margin-left: 4px;
474
+ color: var(--fg-subtle);
475
+ font-family:
476
+ 'Geist Mono', 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, 'Courier New',
477
+ monospace;
478
+ font-size: 11px;
479
+ letter-spacing: 0.04em;
480
+ }
481
+
482
+ /* Captions used at the bottom of both cards */
483
+
484
+ .stat-caption {
485
+ display: inline-flex;
486
+ align-items: baseline;
487
+ flex-wrap: wrap;
488
+ gap: 6px;
489
+ margin: 0;
490
+ align-self: end;
491
+ color: var(--fg-muted);
492
+ font-family:
493
+ 'Geist Mono', 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, 'Courier New',
494
+ monospace;
495
+ font-size: 11.5px;
496
+ font-variant-numeric: tabular-nums;
497
+ letter-spacing: 0.01em;
498
+ }
499
+
500
+ .stat-caption strong {
501
+ color: var(--fg);
502
+ font-weight: 600;
503
+ }
504
+
505
+ .goal-sep {
506
+ color: var(--fg-subtle);
507
+ }
508
+
509
+ .goal-met {
510
+ color: var(--ok);
511
+ font-weight: 600;
512
+ letter-spacing: 0.04em;
513
+ text-transform: uppercase;
514
+ font-size: 11px;
515
+ }
516
+
517
+ .goal-over {
518
+ color: var(--accent-fg);
519
+ font-weight: 600;
520
+ }
521
+
522
+ /* Stepper */
523
+
524
+ .stepper {
525
+ display: inline-flex;
526
+ align-items: center;
527
+ gap: 0;
528
+ border: 1px solid var(--line-strong);
529
+ border-radius: var(--radius-xs);
530
+ background: rgba(255, 255, 255, 0.02);
531
+ overflow: hidden;
532
+ }
533
+
534
+ .stepper-btn {
535
+ width: 26px;
536
+ height: 24px;
537
+ padding: 0;
538
+ border: 0;
539
+ border-radius: 0;
540
+ background: transparent;
541
+ color: var(--fg-muted);
542
+ font-family:
543
+ 'Geist Mono', 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, 'Courier New',
544
+ monospace;
545
+ font-size: 14px;
546
+ font-weight: 600;
547
+ line-height: 1;
548
+ }
549
+
550
+ .stepper-btn:hover {
551
+ background: rgba(255, 255, 255, 0.06);
552
+ color: var(--fg);
553
+ }
554
+
555
+ .stepper-btn:disabled {
556
+ opacity: 0.35;
557
+ background: transparent;
558
+ }
559
+
560
+ .stepper-value {
561
+ display: inline-flex;
562
+ align-items: center;
563
+ justify-content: center;
564
+ min-width: 22px;
565
+ height: 24px;
566
+ padding: 0 4px;
567
+ border-left: 1px solid var(--line);
568
+ border-right: 1px solid var(--line);
569
+ color: var(--fg);
570
+ font-family:
571
+ 'Geist Mono', 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, 'Courier New',
572
+ monospace;
573
+ font-size: 11px;
574
+ font-weight: 600;
575
+ font-variant-numeric: tabular-nums;
576
+ }
577
+
578
+ /* ── Composer ────────────────────────────────────────────── */
579
+
580
+ .composer {
581
+ display: grid;
582
+ grid-template-columns: 1fr auto;
583
+ align-items: center;
584
+ gap: 10px;
585
+ padding: 18px 24px 20px;
586
+ border-bottom: 1px solid var(--line);
587
+ }
588
+
589
+ .composer-field {
590
+ position: relative;
591
+ display: grid;
592
+ grid-template-columns: auto 1fr auto;
593
+ align-items: center;
594
+ gap: 10px;
595
+ padding: 0 12px;
596
+ height: 42px;
597
+ border: 1px solid var(--line-strong);
598
+ border-radius: var(--radius-sm);
599
+ background: var(--bg);
600
+ transition:
601
+ border-color var(--dur) var(--ease),
602
+ box-shadow var(--dur) var(--ease);
603
+ }
604
+
605
+ .composer-field:focus-within {
606
+ border-color: var(--accent-line);
607
+ box-shadow: 0 0 0 3px var(--accent-soft);
608
+ }
609
+
610
+ .composer-icon {
611
+ display: inline-grid;
612
+ place-items: center;
613
+ width: 18px;
614
+ height: 18px;
615
+ color: var(--fg-subtle);
616
+ transition: color var(--dur) var(--ease);
617
+ }
618
+
619
+ .composer-field:focus-within .composer-icon {
620
+ color: var(--accent-fg);
621
+ }
622
+
623
+ .composer input {
624
+ width: 100%;
625
+ height: 100%;
626
+ border: 0;
627
+ padding: 0;
628
+ background: transparent;
629
+ color: var(--fg);
630
+ font: inherit;
631
+ font-size: 14px;
632
+ outline: none;
633
+ }
634
+
635
+ .composer input::placeholder {
636
+ color: var(--fg-subtle);
637
+ }
638
+
639
+ kbd {
640
+ display: inline-flex;
641
+ align-items: center;
642
+ height: 20px;
643
+ padding: 0 6px;
644
+ border: 1px solid var(--line-strong);
645
+ border-radius: var(--radius-xs);
646
+ background: rgba(255, 255, 255, 0.02);
647
+ color: var(--fg-muted);
648
+ font-family:
649
+ 'Geist Mono', 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, 'Courier New',
650
+ monospace;
651
+ font-size: 10px;
652
+ letter-spacing: 0.04em;
653
+ user-select: none;
654
+ }
655
+
656
+ /* ── Buttons ─────────────────────────────────────────────── */
657
+
658
+ button {
659
+ display: inline-flex;
660
+ align-items: center;
661
+ justify-content: center;
662
+ gap: 6px;
663
+ height: 38px;
664
+ padding: 0 14px;
665
+ border: 1px solid var(--line-strong);
666
+ border-radius: var(--radius-sm);
667
+ background: rgba(255, 255, 255, 0.02);
668
+ color: var(--fg);
669
+ font-family: inherit;
670
+ font-size: 13px;
671
+ font-weight: 500;
672
+ letter-spacing: -0.005em;
673
+ cursor: pointer;
674
+ transition:
675
+ border-color var(--dur-fast) var(--ease),
676
+ background var(--dur-fast) var(--ease),
677
+ color var(--dur-fast) var(--ease),
678
+ transform var(--dur-fast) var(--ease);
679
+ }
680
+
681
+ button:hover {
682
+ border-color: rgba(255, 255, 255, 0.18);
683
+ background: rgba(255, 255, 255, 0.04);
684
+ }
685
+
686
+ button:focus-visible {
687
+ outline: none;
688
+ border-color: var(--accent-line);
689
+ box-shadow: 0 0 0 3px var(--accent-soft);
690
+ }
691
+
692
+ button:active {
693
+ transform: translateY(1px);
694
+ }
695
+
696
+ button:disabled {
697
+ cursor: not-allowed;
698
+ opacity: 0.4;
699
+ transform: none;
700
+ }
701
+
702
+ button:disabled:hover {
703
+ border-color: var(--line-strong);
704
+ background: rgba(255, 255, 255, 0.02);
705
+ }
706
+
707
+ .primary {
708
+ border-color: var(--accent);
709
+ background: linear-gradient(180deg, var(--accent-bright) 0%, var(--accent) 100%);
710
+ color: #f4f8fd;
711
+ box-shadow:
712
+ 0 1px 0 rgba(255, 255, 255, 0.18) inset,
713
+ 0 6px 16px -8px rgba(49, 120, 198, 0.6);
714
+ }
715
+
716
+ .primary:hover {
717
+ border-color: var(--accent-bright);
718
+ background: linear-gradient(180deg, #5499e0 0%, var(--accent-bright) 100%);
719
+ }
720
+
721
+ .primary:disabled {
722
+ background: var(--accent-soft);
723
+ border-color: transparent;
724
+ color: var(--accent-fg);
725
+ box-shadow: none;
726
+ }
727
+
728
+ /* ── Filters ─────────────────────────────────────────────── */
729
+
730
+ .filters {
731
+ display: flex;
732
+ gap: 4px;
733
+ padding: 10px 18px;
734
+ border-bottom: 1px solid var(--line);
735
+ background: rgba(255, 255, 255, 0.012);
736
+ }
737
+
738
+ .filter {
739
+ position: relative;
740
+ height: 34px;
741
+ padding: 0 12px;
742
+ border: 0;
743
+ border-radius: var(--radius-xs);
744
+ background: transparent;
745
+ color: var(--fg-muted);
746
+ font-size: 12px;
747
+ font-weight: 500;
748
+ text-transform: capitalize;
749
+ }
750
+
751
+ .filter:hover {
752
+ background: rgba(255, 255, 255, 0.04);
753
+ color: var(--fg);
754
+ border-color: transparent;
755
+ }
756
+
757
+ .filter[aria-pressed='true'] {
758
+ color: var(--fg);
759
+ background: rgba(255, 255, 255, 0.05);
760
+ }
761
+
762
+ .filter[aria-pressed='true']::after {
763
+ content: '';
764
+ position: absolute;
765
+ left: 12px;
766
+ right: 12px;
767
+ bottom: -11px;
768
+ height: 1px;
769
+ background: var(--accent);
770
+ box-shadow: 0 0 8px 0 rgba(49, 120, 198, 0.55);
771
+ }
772
+
773
+ .filter-count {
774
+ display: inline-flex;
775
+ align-items: center;
776
+ height: 18px;
777
+ min-width: 18px;
778
+ padding: 0 5px;
779
+ border-radius: var(--radius-xs);
780
+ background: rgba(255, 255, 255, 0.05);
781
+ color: var(--fg-muted);
782
+ font-family:
783
+ 'Geist Mono', 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, 'Courier New',
784
+ monospace;
785
+ font-size: 10px;
786
+ font-weight: 500;
787
+ font-variant-numeric: tabular-nums;
788
+ }
789
+
790
+ .filter[aria-pressed='true'] .filter-count {
791
+ background: var(--accent-soft);
792
+ color: var(--accent-fg);
793
+ }
794
+
795
+ /* ── List ────────────────────────────────────────────────── */
796
+
797
+ .list {
798
+ margin: 0;
799
+ padding: 0;
800
+ list-style: none;
801
+ }
802
+
803
+ .row {
804
+ display: grid;
805
+ grid-template-columns: auto 1fr auto;
806
+ align-items: center;
807
+ gap: 14px;
808
+ padding: 14px 24px;
809
+ border-bottom: 1px solid var(--line);
810
+ transition: background var(--dur-fast) var(--ease);
811
+ animation: row-in 360ms var(--ease) both;
812
+ animation-delay: calc(var(--index, 0) * 40ms);
813
+ }
814
+
815
+ .row:last-child {
816
+ border-bottom: 0;
817
+ }
818
+
819
+ cts-todo-item .row:last-child {
820
+ border-bottom: 1px solid var(--line);
821
+ }
822
+
823
+ cts-todo-item:last-child .row:last-child {
824
+ border-bottom: 0;
825
+ }
826
+
827
+ .row:hover {
828
+ background: rgba(255, 255, 255, 0.02);
829
+ }
830
+
831
+ @keyframes row-in {
832
+ from {
833
+ opacity: 0;
834
+ transform: translateY(4px);
835
+ }
836
+ to {
837
+ opacity: 1;
838
+ transform: translateY(0);
839
+ }
840
+ }
841
+
842
+ /* Check button — sharp 2px square, GitHub-issue style */
843
+
844
+ .check {
845
+ width: 20px;
846
+ height: 20px;
847
+ min-width: 20px;
848
+ padding: 0;
849
+ border: 1px solid rgba(255, 255, 255, 0.2);
850
+ border-radius: var(--radius-xs);
851
+ background: rgba(255, 255, 255, 0.02);
852
+ color: transparent;
853
+ transition:
854
+ border-color var(--dur-fast) var(--ease),
855
+ background var(--dur-fast) var(--ease),
856
+ color var(--dur-fast) var(--ease),
857
+ box-shadow var(--dur-fast) var(--ease),
858
+ transform var(--dur-fast) var(--ease);
859
+ }
860
+
861
+ .check:hover {
862
+ border-color: var(--accent-line);
863
+ background: var(--accent-soft);
864
+ }
865
+
866
+ .check[aria-pressed='true'] {
867
+ border-color: var(--accent);
868
+ background: var(--accent);
869
+ color: #f4f8fd;
870
+ box-shadow: 0 0 0 3px var(--accent-soft);
871
+ }
872
+
873
+ .check[aria-pressed='true']:hover {
874
+ background: var(--accent-bright);
875
+ border-color: var(--accent-bright);
876
+ }
877
+
878
+ .check-tick {
879
+ display: grid;
880
+ place-items: center;
881
+ width: 100%;
882
+ height: 100%;
883
+ opacity: 0;
884
+ transform: scale(0.6);
885
+ transition:
886
+ opacity var(--dur-fast) var(--ease),
887
+ transform var(--dur) var(--ease);
888
+ }
889
+
890
+ .check[aria-pressed='true'] .check-tick {
891
+ opacity: 1;
892
+ transform: scale(1);
893
+ }
894
+
895
+ /* Task label */
896
+
897
+ .task-label {
898
+ min-width: 0;
899
+ font-size: 14px;
900
+ color: var(--fg);
901
+ letter-spacing: -0.005em;
902
+ }
903
+
904
+ input.task-label {
905
+ height: 32px;
906
+ width: 100%;
907
+ padding: 0 8px;
908
+ margin: 0 -8px;
909
+ border: 1px solid transparent;
910
+ border-radius: var(--radius-xs);
911
+ background: transparent;
912
+ color: var(--fg);
913
+ font: inherit;
914
+ font-size: 14px;
915
+ outline: none;
916
+ transition:
917
+ border-color var(--dur-fast) var(--ease),
918
+ background var(--dur-fast) var(--ease);
919
+ }
920
+
921
+ input.task-label:hover {
922
+ border-color: var(--line-strong);
923
+ background: rgba(255, 255, 255, 0.02);
924
+ }
925
+
926
+ input.task-label:focus {
927
+ border-color: var(--accent-line);
928
+ background: var(--bg);
929
+ box-shadow: 0 0 0 3px var(--accent-soft);
930
+ }
931
+
932
+ .task-label.is-done {
933
+ color: var(--fg-subtle);
934
+ text-decoration: line-through;
935
+ text-decoration-color: rgba(91, 99, 112, 0.6);
936
+ text-decoration-thickness: 1px;
937
+ overflow: hidden;
938
+ text-overflow: ellipsis;
939
+ white-space: nowrap;
940
+ }
941
+
942
+ /* Remove button */
943
+
944
+ .remove {
945
+ width: 30px;
946
+ height: 30px;
947
+ padding: 0;
948
+ border-color: transparent;
949
+ background: transparent;
950
+ color: var(--fg-subtle);
951
+ opacity: 0;
952
+ transform: translateX(-2px);
953
+ transition:
954
+ opacity var(--dur-fast) var(--ease),
955
+ transform var(--dur-fast) var(--ease),
956
+ color var(--dur-fast) var(--ease),
957
+ background var(--dur-fast) var(--ease),
958
+ border-color var(--dur-fast) var(--ease);
959
+ }
960
+
961
+ .row:hover .remove,
962
+ .row:focus-within .remove,
963
+ .remove:focus-visible {
964
+ opacity: 1;
965
+ transform: translateX(0);
966
+ }
967
+
968
+ .remove:hover {
969
+ color: var(--danger);
970
+ background: var(--danger-soft);
971
+ border-color: rgba(196, 106, 106, 0.25);
972
+ }
973
+
974
+ .remove svg {
975
+ display: block;
976
+ }
977
+
978
+ /* ── Empty state ─────────────────────────────────────────── */
979
+
980
+ .empty {
981
+ display: grid;
982
+ justify-items: center;
983
+ gap: 8px;
984
+ padding: 56px 22px 64px;
985
+ text-align: center;
986
+ }
987
+
988
+ .empty-icon {
989
+ display: inline-grid;
990
+ place-items: center;
991
+ width: 56px;
992
+ height: 56px;
993
+ margin-bottom: 6px;
994
+ border-radius: var(--radius-md);
995
+ background: rgba(255, 255, 255, 0.025);
996
+ border: 1px solid var(--line-strong);
997
+ color: var(--fg-muted);
998
+ }
999
+
1000
+ .empty-title {
1001
+ margin: 0;
1002
+ color: var(--fg);
1003
+ font-size: 14px;
1004
+ font-weight: 600;
1005
+ letter-spacing: -0.005em;
1006
+ }
1007
+
1008
+ .empty-sub {
1009
+ margin: 0;
1010
+ max-width: 32ch;
1011
+ color: var(--fg-muted);
1012
+ font-size: 13px;
1013
+ line-height: 1.55;
1014
+ text-wrap: balance;
1015
+ }
1016
+
1017
+ /* ── Footer ──────────────────────────────────────────────── */
1018
+
1019
+ .footer {
1020
+ display: flex;
1021
+ align-items: center;
1022
+ justify-content: space-between;
1023
+ gap: 12px;
1024
+ padding: 14px 18px 14px 24px;
1025
+ border-top: 1px solid var(--line);
1026
+ background: rgba(0, 0, 0, 0.18);
1027
+ }
1028
+
1029
+ .footer-stats {
1030
+ display: inline-flex;
1031
+ align-items: baseline;
1032
+ gap: 6px;
1033
+ color: var(--fg-muted);
1034
+ font-family:
1035
+ 'Geist Mono', 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, 'Courier New',
1036
+ monospace;
1037
+ font-size: 12px;
1038
+ font-variant-numeric: tabular-nums;
1039
+ }
1040
+
1041
+ .footer-stats strong {
1042
+ color: var(--fg);
1043
+ font-size: 14px;
1044
+ font-weight: 600;
1045
+ }
1046
+
1047
+ .footer-divider {
1048
+ color: var(--fg-subtle);
1049
+ }
1050
+
1051
+ .footer-label {
1052
+ margin-left: 4px;
1053
+ color: var(--fg-subtle);
1054
+ font-family:
1055
+ 'Geist',
1056
+ 'Inter',
1057
+ ui-sans-serif,
1058
+ system-ui,
1059
+ -apple-system,
1060
+ 'Segoe UI',
1061
+ Roboto,
1062
+ 'Helvetica Neue',
1063
+ Arial,
1064
+ sans-serif;
1065
+ font-size: 12px;
1066
+ letter-spacing: 0.01em;
1067
+ }
1068
+
1069
+ .footer-actions {
1070
+ display: flex;
1071
+ gap: 6px;
1072
+ }
1073
+
1074
+ .footer-actions button {
1075
+ height: 32px;
1076
+ padding: 0 11px;
1077
+ font-size: 12px;
1078
+ background: transparent;
1079
+ border-color: transparent;
1080
+ color: var(--fg-muted);
1081
+ }
1082
+
1083
+ .footer-actions button:hover {
1084
+ border-color: var(--line-strong);
1085
+ background: rgba(255, 255, 255, 0.04);
1086
+ color: var(--fg);
1087
+ }
1088
+
1089
+ /* ── Responsive ──────────────────────────────────────────── */
1090
+
1091
+ @media (max-width: 560px) {
1092
+ :host {
1093
+ width: 100%;
1094
+ }
1095
+
1096
+ .header,
1097
+ .stats-panel,
1098
+ .composer,
1099
+ .footer,
1100
+ .row {
1101
+ padding-right: 16px;
1102
+ padding-left: 16px;
1103
+ }
1104
+
1105
+ .header {
1106
+ grid-template-columns: 1fr;
1107
+ gap: 12px;
1108
+ }
1109
+
1110
+ .revision {
1111
+ justify-self: start;
1112
+ }
1113
+
1114
+ .composer {
1115
+ grid-template-columns: 1fr;
1116
+ }
1117
+
1118
+ .stats-panel {
1119
+ grid-template-columns: 1fr;
1120
+ }
1121
+
1122
+ .filters {
1123
+ padding-right: 10px;
1124
+ padding-left: 10px;
1125
+ }
1126
+
1127
+ .progress {
1128
+ margin: 0 16px;
1129
+ }
1130
+
1131
+ kbd {
1132
+ display: none;
1133
+ }
1134
+ }
1135
+
1136
+ @media (prefers-reduced-motion: reduce) {
1137
+ .shell,
1138
+ .row {
1139
+ animation: none;
1140
+ }
1141
+ .revision-pulse::after {
1142
+ animation: none;
1143
+ display: none;
1144
+ }
1145
+ }