@klodd/ds 5.13.1 → 5.14.1

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.
@@ -470,3 +470,130 @@
470
470
  );
471
471
  }
472
472
  }
473
+
474
+
475
+ /* ================================================================
476
+ ==== LIGHT MODE PRIMITIVES
477
+ Radix Light-skalor. Konsumeras BARA av [data-theme="light"]-blocket
478
+ i 10-semantic.css och app-filer. Aldrig direkt i komponenter.
479
+
480
+ Hex-värden hämtade från @radix-ui/colors v3.0.0 via unpkg
481
+ (2026-05-26). P3-varianter finns i Radix-paketet men ingår inte
482
+ här - klodd-ds håller sRGB-hex för portabilitet, matchande
483
+ dark-rampernas format ovan.
484
+
485
+ Naming: <ramp>-light-<step> för solid, <ramp>-light-a<step> för
486
+ alpha. Dark-skalorna ovan saknar suffix (de är default).
487
+ ================================================================ */
488
+
489
+
490
+ /* ================================================================
491
+ ==== MAUVE LIGHT (neutral, --gray-N i light mode)
492
+ ================================================================ */
493
+ :root {
494
+ --gray-light-1: #fdfcfd;
495
+ --gray-light-2: #faf9fb;
496
+ --gray-light-3: #f2eff3;
497
+ --gray-light-4: #eae7ec;
498
+ --gray-light-5: #e3dfe6;
499
+ --gray-light-6: #dbd8e0;
500
+ --gray-light-7: #d0cdd7;
501
+ --gray-light-8: #bcbac7;
502
+ --gray-light-9: #8e8c99;
503
+ --gray-light-10: #84828e;
504
+ --gray-light-11: #65636d;
505
+ --gray-light-12: #211f26;
506
+
507
+ --gray-light-a1: #55005503;
508
+ --gray-light-a2: #2b005506;
509
+ --gray-light-a3: #30004010;
510
+ --gray-light-a4: #20003618;
511
+ --gray-light-a5: #20003820;
512
+ --gray-light-a6: #14003527;
513
+ --gray-light-a7: #10003332;
514
+ --gray-light-a8: #08003145;
515
+ --gray-light-a9: #05001d73;
516
+ --gray-light-a10: #0500197d;
517
+ --gray-light-a11: #0400119c;
518
+ --gray-light-a12: #020008e0;
519
+ }
520
+
521
+
522
+ /* ================================================================
523
+ ==== BLUE LIGHT (Jubb accent)
524
+ ================================================================ */
525
+ :root {
526
+ --blue-light-1: #fbfdff;
527
+ --blue-light-2: #f4faff;
528
+ --blue-light-3: #e6f4fe;
529
+ --blue-light-4: #d5efff;
530
+ --blue-light-5: #c2e5ff;
531
+ --blue-light-6: #acd8fc;
532
+ --blue-light-7: #8ec8f6;
533
+ --blue-light-8: #5eb1ef;
534
+ --blue-light-9: #0090ff;
535
+ --blue-light-10: #0588f0;
536
+ --blue-light-11: #0d74ce;
537
+ --blue-light-12: #113264;
538
+
539
+ --blue-light-a1: #0080ff04;
540
+ --blue-light-a2: #008cff0b;
541
+ --blue-light-a3: #008ff519;
542
+ --blue-light-a4: #009eff2a;
543
+ --blue-light-a5: #0093ff3d;
544
+ --blue-light-a6: #0088f653;
545
+ --blue-light-a7: #0083eb71;
546
+ --blue-light-a8: #0084e6a1;
547
+ --blue-light-a9: #0090ff;
548
+ --blue-light-a10: #0086f0fa;
549
+ --blue-light-a11: #006dcbf2;
550
+ --blue-light-a12: #002359ee;
551
+ }
552
+
553
+
554
+ /* ================================================================
555
+ ==== PLUM LIGHT (Ekonom accent)
556
+ ================================================================ */
557
+ :root {
558
+ --plum-light-1: #fefcff;
559
+ --plum-light-2: #fdf7fd;
560
+ --plum-light-3: #fbebfb;
561
+ --plum-light-4: #f7def8;
562
+ --plum-light-5: #f2d1f3;
563
+ --plum-light-6: #e9c2ec;
564
+ --plum-light-7: #deade3;
565
+ --plum-light-8: #cf91d8;
566
+ --plum-light-9: #ab4aba;
567
+ --plum-light-10: #a144af;
568
+ --plum-light-11: #953ea3;
569
+ --plum-light-12: #53195d;
570
+
571
+ --plum-light-a1: #aa00ff03;
572
+ --plum-light-a2: #c000c008;
573
+ --plum-light-a3: #cc00cc14;
574
+ --plum-light-a4: #c200c921;
575
+ --plum-light-a5: #b700bd2e;
576
+ --plum-light-a6: #a400b03d;
577
+ --plum-light-a7: #9900a852;
578
+ --plum-light-a8: #9000a56e;
579
+ --plum-light-a9: #89009eb5;
580
+ --plum-light-a10: #7f0092bb;
581
+ --plum-light-a11: #730086c1;
582
+ --plum-light-a12: #40004be6;
583
+ }
584
+
585
+
586
+ /* ================================================================
587
+ ==== STATUS LIGHT
588
+ Bara step 9 (solid bg) och 11 (text-on-light) - det är de steg
589
+ som konsumeras av --bg-success/warning/danger respektive
590
+ --positive/-negative/-warning i 10-semantic.css.
591
+ ================================================================ */
592
+ :root {
593
+ --green-light-9: #30a46c;
594
+ --green-light-11: #218358;
595
+ --amber-light-9: #ffc53d;
596
+ --amber-light-11: #ab6400;
597
+ --red-light-9: #e5484d;
598
+ --red-light-11: #ce2c31;
599
+ }
@@ -240,9 +240,11 @@
240
240
  /* Spinner-backdrop bakom upload-spinner-overlay. */
241
241
  --backdrop-spinner: color-mix(in srgb, var(--surface-page) 88%, transparent);
242
242
 
243
- /* Glass-bg for backdrop-blur navigationer (bottom-nav, top-nav). */
244
- --glass-bg: color-mix(in oklch, var(--surface-overlay) 85%, transparent);
245
- --glass-bg-fallback: color-mix(in oklch, var(--surface-overlay) 96%, transparent);
243
+ /* Glass-tokens: semi-transparenta ytor för backdrop-blur-navigation.
244
+ Konsumeras av .bottom-nav. Kalibrerade mot Blue-1 (Jubb) och Plum-1 (Ekonom).
245
+ --glass-bg-fallback används av @supports-grenen (browsers utan backdrop-filter). */
246
+ --glass-bg: color-mix(in oklch, var(--surface-raised) 65%, transparent);
247
+ --glass-bg-fallback: color-mix(in oklch, var(--surface-raised) 96%, transparent);
246
248
 
247
249
  /* Grafpalett - for diagram och visualiseringar.
248
250
  Fem distinkta farger med god kontrast mot dark-bakgrund.
@@ -319,6 +321,101 @@
319
321
  }
320
322
 
321
323
 
324
+ /* ================================================================
325
+ ==== LIGHT MODE OVERRIDES
326
+ Aktiveras via [data-theme="light"] på <html>-elementet.
327
+ Overridar bara tokens som faktiskt skiljer sig från dark default -
328
+ strukturella tokens (--touch-min, --z-*, --fw-*, spacing, radius)
329
+ delas mellan lägen.
330
+
331
+ App-specifika accenter overridas i
332
+ [data-app="X"][data-theme="light"] i respektive apps/X.css.
333
+
334
+ Shadow-doktrin: dark mode "ingen shadow, luminans skapar djup" -
335
+ light mode återställer shadow eftersom luminans-kontrast är svag.
336
+ Se ADR 0013 light mode-tillägg + ADR 0028 light mode-arkitektur.
337
+ ================================================================ */
338
+ [data-theme="light"] {
339
+ color-scheme: light;
340
+
341
+ /* SURFACES - Mauve Light, spegelvänt från dark-stegen */
342
+ --surface-page: var(--gray-light-1);
343
+ --surface-default: var(--gray-light-2);
344
+ --surface-raised: var(--gray-light-3);
345
+ --surface-elevated: var(--gray-light-4);
346
+ --surface-overlay: var(--gray-light-5);
347
+ --surface-hover: var(--gray-light-4);
348
+ --surface-active: var(--gray-light-5);
349
+ --surface-sunken: var(--gray-light-2);
350
+
351
+ /* Surface tints - svart istället för vit (white-tint osynlig på ljus bg) */
352
+ --surface-faint: color-mix(in oklch, black 2%, transparent);
353
+ --surface-subtle: color-mix(in oklch, black 4%, transparent);
354
+ --surface-medium: color-mix(in oklch, black 8%, transparent);
355
+ --surface-strong: color-mix(in oklch, black 12%, transparent);
356
+
357
+ /* TEXT - Mauve Light, höga steg = mörk text mot ljus bakgrund */
358
+ --text-default: var(--gray-light-12);
359
+ --text-subtle: var(--gray-light-11);
360
+ --text-muted: var(--gray-light-10);
361
+ --text-disabled: var(--gray-light-8);
362
+
363
+ /* BORDERS */
364
+ --border-subtle: var(--gray-light-5);
365
+ --border-default: var(--gray-light-7);
366
+ --border-strong: var(--gray-light-8);
367
+ --border-focus: var(--gray-light-9);
368
+ --border-hairline-faint: var(--bw-hairline) solid color-mix(in oklch, black 4%, transparent);
369
+
370
+ /* SHADOW-DOKTRIN LIGHT (ADR 0013 light-tillägg) */
371
+ --shadow-card: 0 1px 3px color-mix(in oklch, var(--gray-12) 12%, transparent),
372
+ 0 1px 2px color-mix(in oklch, var(--gray-12) 8%, transparent);
373
+ --shadow-float: 0 4px 12px color-mix(in oklch, var(--gray-12) 16%, transparent),
374
+ 0 2px 4px color-mix(in oklch, var(--gray-12) 10%, transparent);
375
+
376
+ /* GLASS - --gray-light-6 vid 55% (inte --surface-raised) - följer
377
+ iOS UIBlurEffect-konventionen att ge en svagt mörkare tint över
378
+ ljus page-bakgrund. Se ADR 0028. */
379
+ --glass-bg: color-mix(in oklch, var(--gray-light-6) 55%, transparent);
380
+ --glass-bg-fallback: color-mix(in oklch, var(--gray-light-6) 90%, transparent);
381
+
382
+ /* STATUS - Light step 11 för text, step 9 för bg */
383
+ --positive: var(--green-light-11);
384
+ --positive-dim: color-mix(in oklch, var(--green-light-9) 12%, transparent);
385
+ --positive-border: color-mix(in oklch, var(--green-light-9) 30%, transparent);
386
+ --negative: var(--red-light-11);
387
+ --negative-dim: color-mix(in oklch, var(--red-light-9) 12%, transparent);
388
+ --negative-border: color-mix(in oklch, var(--red-light-9) 30%, transparent);
389
+ --warning: var(--amber-light-11);
390
+ --warning-dim: color-mix(in oklch, var(--amber-light-9) 12%, transparent);
391
+ --warning-hover: color-mix(in oklch, var(--amber-light-9) 18%, transparent);
392
+ --warning-border: color-mix(in oklch, var(--amber-light-9) 30%, transparent);
393
+
394
+ /* Solid status-bakgrunder */
395
+ --bg-success: var(--green-light-9);
396
+ --bg-warning: var(--amber-light-9);
397
+ --bg-danger: var(--red-light-9);
398
+
399
+ /* Text-on-status: mörk i light mode. Vit oklch(0.98 0 0) ger
400
+ 1.58:1 mot amber-light-9 (WCAG-fail) - gray-light-12 ger 10.3:1
401
+ mot amber, 5.76:1 mot green-light-9, 5.52:1 mot red-light-9. */
402
+ --text-on-status: var(--gray-light-12);
403
+
404
+ /* --text-on-accent: mörk i light mode.
405
+ Vit oklch(0.98 0 0) ger ~3.29:1 mot blue-light-9 och ~2.8:1 mot
406
+ plum-light-9 - båda under WCAG AA 4.5:1.
407
+ gray-light-12 ger godkänd kontrast mot alla app-accenter. */
408
+ --text-on-accent: var(--gray-light-12);
409
+
410
+ /* Danger-action - dim/border följer via accent-danger-recursion */
411
+ --accent-danger: var(--red-light-9);
412
+
413
+ /* Chart-palette - re-pointe status step 9 till light-version */
414
+ --chart-2: var(--green-light-9);
415
+ --chart-3: var(--amber-light-9);
416
+ }
417
+
418
+
322
419
  /* ================================================================
323
420
  ==== COMPONENT TOKENS
324
421
  Atervaning av samma kombination i 3+ komponenter blir token.
@@ -56,3 +56,37 @@
56
56
  --accent-a10: var(--plum-a10);
57
57
  --accent-a12: var(--plum-a12);
58
58
  }
59
+
60
+ /* Glass-kalibrering per app - override av 10-semantic.css default (65%).
61
+ Plum-1 (#181118) är något ljusare än Blue-1 (#0d1520) - kan kräva
62
+ högre opacitet (67-70%) efter visuell verifiering i Ekonom. */
63
+ [data-app="ekonom"] {
64
+ --glass-bg: color-mix(in oklch, var(--surface-raised) 65%, transparent);
65
+ }
66
+
67
+
68
+ /* Light mode-override per app - Plum Light-rampen + Plum-tonad page.
69
+ Konsumeras när <html data-app="ekonom" data-theme="light">. Se ADR 0028. */
70
+ [data-app="ekonom"][data-theme="light"] {
71
+ /* Accent: Plum Light-rampen */
72
+ --accent-soft: var(--plum-light-3);
73
+ --accent-moderate: var(--plum-light-4);
74
+ --accent-9: var(--plum-light-9);
75
+ --accent-10: var(--plum-light-10);
76
+ --accent-text: var(--plum-light-11);
77
+ --accent-a1: var(--plum-light-a1);
78
+ --accent-a2: var(--plum-light-a2);
79
+ --accent-a3: var(--plum-light-a3);
80
+ --accent-a6: var(--plum-light-a6);
81
+ --accent-a8: var(--plum-light-a8);
82
+ --accent-a10: var(--plum-light-a10);
83
+ --accent-a12: var(--plum-light-a12);
84
+
85
+ /* Surface: Plum Light page-toningar */
86
+ --surface-page: var(--plum-light-1);
87
+ --surface-default: var(--plum-light-2);
88
+ --surface-raised: var(--plum-light-3);
89
+
90
+ /* Border-focus: Plum accent */
91
+ --border-focus: var(--plum-light-8);
92
+ }
package/css/apps/jubb.css CHANGED
@@ -45,3 +45,36 @@
45
45
  /* Text - varm vit med latt bla ton (paritet med Ekonoms #F4F2FA Plum-vit) */
46
46
  --text-default: #EEF4FF;
47
47
  }
48
+
49
+ /* Glass-kalibrering per app - override av 10-semantic.css default (65%).
50
+ Justeras visuellt mot Blue-1-bakgrund vid build:ds-driftsättning. */
51
+ [data-app="jubb"] {
52
+ --glass-bg: color-mix(in oklch, var(--surface-raised) 65%, transparent);
53
+ }
54
+
55
+
56
+ /* Light mode-override per app - Blue Light-rampen + Blue-tonad page.
57
+ Konsumeras när <html data-app="jubb" data-theme="light">. Se ADR 0028. */
58
+ [data-app="jubb"][data-theme="light"] {
59
+ /* Accent: Blue Light-rampen */
60
+ --accent-soft: var(--blue-light-3);
61
+ --accent-moderate: var(--blue-light-4);
62
+ --accent-9: var(--blue-light-9);
63
+ --accent-10: var(--blue-light-10);
64
+ --accent-text: var(--blue-light-11);
65
+ --accent-a1: var(--blue-light-a1);
66
+ --accent-a2: var(--blue-light-a2);
67
+ --accent-a3: var(--blue-light-a3);
68
+ --accent-a6: var(--blue-light-a6);
69
+ --accent-a8: var(--blue-light-a8);
70
+ --accent-a10: var(--blue-light-a10);
71
+ --accent-a12: var(--blue-light-a12);
72
+
73
+ /* Surface: Blue Light page-toningar (de mest synliga ytorna) */
74
+ --surface-page: var(--blue-light-1);
75
+ --surface-default: var(--blue-light-2);
76
+ --surface-raised: var(--blue-light-3);
77
+
78
+ /* Border-focus: Blue accent */
79
+ --border-focus: var(--blue-light-8);
80
+ }
@@ -44,13 +44,29 @@
44
44
  padding-inline: var(--space-8);
45
45
  margin: 0;
46
46
  max-width: none;
47
- background: var(--surface-raised);
48
47
  border: 1px solid var(--border-subtle);
49
48
  border-radius: var(--radius-full);
50
49
  /* Sprint 3a 2026-05-12: shadow→none per dark-mode-doktrin. */
51
50
  box-shadow: none;
52
51
  }
53
52
 
53
+ @supports (backdrop-filter: blur(1px)) {
54
+ .bottom-nav {
55
+ /* Safari iOS: position:fixed + backdrop-filter skapar ny stacking context.
56
+ z-index: var(--z-nav) bibehålls korrekt men rubber-band-scroll behöver
57
+ verifieras på fysisk enhet. */
58
+ background: var(--glass-bg);
59
+ backdrop-filter: blur(12px);
60
+ -webkit-backdrop-filter: blur(12px);
61
+ }
62
+ }
63
+
64
+ @supports not (backdrop-filter: blur(1px)) {
65
+ .bottom-nav {
66
+ background: var(--glass-bg-fallback);
67
+ }
68
+ }
69
+
54
70
  .bottom-nav__item {
55
71
  flex: 1;
56
72
  min-width: 0;
@@ -135,6 +151,7 @@
135
151
  border-right: 1px solid var(--border-subtle);
136
152
  border-bottom: none;
137
153
  box-shadow: none;
154
+ background: var(--surface-raised); /* Glass är bar-mode only — rail/drawer är sidebars, inte floating pills. */
138
155
  }
139
156
 
140
157
  .bottom-nav__item {
@@ -186,6 +203,7 @@
186
203
  border-right: 1px solid var(--border-subtle);
187
204
  border-bottom: none;
188
205
  box-shadow: none;
206
+ background: var(--surface-raised); /* Glass är bar-mode only — rail/drawer är sidebars, inte floating pills. */
189
207
  }
190
208
 
191
209
  .bottom-nav__item {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@klodd/ds",
3
- "version": "5.13.1",
3
+ "version": "5.14.1",
4
4
  "description": "Klodd shared design system - tokens, components, JS",
5
5
  "main": "css/index.css",
6
6
  "bin": {
@@ -116,6 +116,36 @@ Commit: Jubb dffb7eb
116
116
  "när-använda-vilken-kombination"-tabeller (t.ex. card vs panel
117
117
  för list-items, hub-card vs setting-row för settings).
118
118
 
119
+ ## Light mode-tillägg (2026-05-26)
120
+
121
+ Dark mode-doktrinen (ingen box-shadow, luminans skapar djup) gäller
122
+ bara dark mode. I light mode gäller spegellogiken:
123
+
124
+ - box-shadow är förväntad och nödvändig på raised-ytor
125
+ - --shadow-card och --shadow-float definieras om i
126
+ [data-theme="light"]-blocket med synliga värden
127
+ - Luminans-hierarkin finns kvar men är svagare i light
128
+ (ljusa ytor mot vit bakgrund ger liten kontrast utan skugga)
129
+
130
+ Konkreta värden för light mode:
131
+
132
+ ```css
133
+ --shadow-card: 0 1px 3px color-mix(in oklch, var(--gray-12) 12%, transparent),
134
+ 0 1px 2px color-mix(in oklch, var(--gray-12) 8%, transparent);
135
+ --shadow-float: 0 4px 12px color-mix(in oklch, var(--gray-12) 16%, transparent),
136
+ 0 2px 4px color-mix(in oklch, var(--gray-12) 10%, transparent);
137
+ ```
138
+
139
+ I light mode är --gray-12 mörk (Mauve Light steg 12, #211f26 ≈ mörk
140
+ text), så color-mix ger synlig skugga. Token-systemet löser
141
+ korrekthet automatiskt när --gray-N mappas till light-rampen via
142
+ `[data-theme="light"]`-overriden.
143
+
144
+ Light-primitives (Mauve/Blue/Plum/Status Light) lagda i
145
+ 00-primitives.css 2026-05-26 som förberedelse - 78 nya variabler
146
+ med `-light-`-suffix. Konsumeras i nästa fas när
147
+ [data-theme="light"]-blocket adderas till 10-semantic.css.
148
+
119
149
  ## References
120
150
 
121
151
  - `references/DESIGN-LANGUAGE.md` (locked doktrin)
@@ -0,0 +1,39 @@
1
+ # 0027 - Glaseffekt på bottom-nav
2
+
3
+ ## Status
4
+ Locked.
5
+
6
+ ## Context
7
+ bottom-nav är en position:fixed pill som flytande ovanpå scroll-innehåll.
8
+ iOS-native glaseffekt (UIBlurEffect) är etablerat designspråk för just
9
+ detta mönster. Klodd DS dark-mode-doktrin säger "ljushet-hierarki > shadow" —
10
+ glaseffekt via luminans-baserad blur + tint följer doktrinen.
11
+
12
+ --glass-bg och --glass-bg-fallback definierades i 10-semantic.css utan konsumenter.
13
+ Detta ADR aktiverar dem.
14
+
15
+ ## Decision
16
+ .bottom-nav använder backdrop-filter: blur(16px) + --glass-bg (72% solid av
17
+ --surface-raised) via @supports-grening.
18
+
19
+ @supports not-grenen faller tillbaka till --glass-bg-fallback (96% solid) —
20
+ visuellt nästintill identiskt med tidigare solida yta.
21
+
22
+ Surface-tokens förblir solida. Glaseffekt är en per-komponent opt-in,
23
+ inte en global token-mutation.
24
+
25
+ ## Consequences
26
+ - + iOS-native visuell metafor för flytande navigation
27
+ - + @supports garanterar att browsers utan stöd ser 96% solid fallback
28
+ - + --glass-bg är app-agnostisk — korrekt tint följer automatiskt vid
29
+ Blue → Plum app-byte via surface-raised-kedjan
30
+ - − Safari iOS: position:fixed + backdrop-filter skapar stacking context,
31
+ rubber-band-scroll måste verifieras på fysisk enhet
32
+ - − Kontinuerlig GPU-compositing vid scroll (~5-15% extra GPU-tid på
33
+ äldre iPhoner). Accepterat givet bottoms navs lilla yta (2-3% av viewport)
34
+
35
+ ## Kalibreringsstatus
36
+ 65% är kalibrerat startvärde mot test-HTML med Blue-1-bakgrund (Jubb).
37
+ Per-app-override i apps/jubb.css och apps/ekonom.css tillåter
38
+ oberoende finjustering utan paketreleaser. Ekonom otestad mot
39
+ Plum-1 - verifiera vid Ekonom-driftsättning.
@@ -0,0 +1,37 @@
1
+ # 0028 - Light mode-arkitektur
2
+
3
+ ## Status
4
+ Locked. Implementerat 2026-05-26.
5
+
6
+ ## Decision
7
+
8
+ Light mode aktiveras via `data-theme="light"` på `<html>`-elementet.
9
+ System-default från `prefers-color-scheme`, manuell override via
10
+ LocalStorage (implementeras per app i Fas 3).
11
+
12
+ Token-override-strategi: `[data-theme="light"]`-block i
13
+ 10-semantic.css overridar surface/text/border/shadow/glass.
14
+ App-specifika accenter overridas i
15
+ `[data-app="X"][data-theme="light"]` i respektive apps/X.css.
16
+
17
+ Komponentskiktet rörs inte - det är 100% token-baserat och följer
18
+ automatiskt.
19
+
20
+ Shadow-doktrin light mode: se ADR 0013 light mode-tillägg.
21
+
22
+ Glass light mode: `--gray-light-6` 55% transparent (istället för
23
+ surface-raised 65%). Motiverat av iOS UIBlurEffect-konventionen
24
+ för light backgrounds där en svagt mörkare tint över ljus bakgrund
25
+ ger glaseffekten - omvänt mot dark mode.
26
+
27
+ Primitives-grund: 78 Radix Light-variabler i 00-primitives.css
28
+ (Mauve/Blue/Plum/Status Light, hex-värden hämtade från
29
+ `@radix-ui/colors` v3.0.0 via unpkg 2026-05-26).
30
+
31
+ ## Consequences
32
+
33
+ - Persistens är per enhet (LocalStorage), inte server-side - avsiktligt
34
+ för att stödja olika teman på olika enheter från samma konto
35
+ - Fas 3 (toggle-JS + visuell QA per app) genomförs separat per app
36
+ - Token-systemet löser komponent-korrekthet automatiskt - inga
37
+ komponent-CSS-edits krävs i light mode