@toybreaker/fiko 0.6.2 → 0.7.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.
@@ -6,9 +6,11 @@
6
6
  * Injected into the `theme` layer:
7
7
  * @import url(./branding/roles.css) layer(theme);
8
8
  *
9
- * Permanent light mode dark mode intentionally excluded.
9
+ * Light is the default. Dark follows system preference automatically.
10
+ * Manual override: set data-theme="dark" or data-theme="light" on <html>.
10
11
  */
11
12
 
13
+ /* ── light mode (default) ──────────────────────────────── */
12
14
  :root {
13
15
  /* ── SURFACE & TEXT ───────────────────────────────── */
14
16
  --surface: var(--light);
@@ -17,6 +19,7 @@
17
19
  --surface_inverseT25: var(--darkT25);
18
20
  --text: var(--dark);
19
21
  --textT50: var(--darkT50);
22
+ --border_color: color-mix(in oklch, var(--dark), transparent 75%);
20
23
 
21
24
  /* ── LOGO ─────────────────────────────────────────── */
22
25
  --logo: var(--dark);
@@ -28,10 +31,39 @@
28
31
 
29
32
  /* ── CTA — interactive (buttons, links, actions) ──── */
30
33
  --cta: var(--brand);
31
- --cta_text: var(--light);
34
+ --cta_text: var(--dark);
32
35
  --cta_hover: var(--brand_lighter);
33
36
 
34
- /* ── BORDERS & FOCUS ──────────────────────────────── */
35
- --border_color: var(--dark);
36
- --focus_color: var(--brand);
37
+ /* ── FOCUS ────────────────────────────────────────── */
38
+ --focus_color: var(--brand);
39
+ }
40
+
41
+ /* ── dark mode — system preference ─────────────────────── */
42
+ /* Respects system setting unless data-theme="light" forces light */
43
+ @media (prefers-color-scheme: dark) {
44
+ :root:not([data-theme="light"]) {
45
+ --surface: var(--dark);
46
+ --surface_inverse: var(--light);
47
+ --surfaceT25: var(--darkT25);
48
+ --surface_inverseT25: var(--lightT25);
49
+ --text: var(--light);
50
+ --textT50: var(--lightT50);
51
+ --border_color: color-mix(in oklch, var(--light), transparent 75%);
52
+ --logo: var(--light);
53
+ --cta_text: var(--light);
54
+ }
55
+ }
56
+
57
+ /* ── dark mode — explicit override ─────────────────────── */
58
+ /* data-theme="dark" on <html> forces dark regardless of system */
59
+ :root[data-theme="dark"] {
60
+ --surface: var(--dark);
61
+ --surface_inverse: var(--light);
62
+ --surfaceT25: var(--darkT25);
63
+ --surface_inverseT25: var(--lightT25);
64
+ --text: var(--light);
65
+ --textT50: var(--lightT50);
66
+ --border_color: color-mix(in oklch, var(--light), transparent 75%);
67
+ --logo: var(--light);
68
+ --cta_text: var(--light);
37
69
  }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * FIKO — light/dark theme toggle
3
+ *
4
+ * Cycles: system → light → dark → system
5
+ * Persists choice to localStorage.
6
+ * Works with the data-theme pattern in branding/roles.css.
7
+ *
8
+ * Usage
9
+ * ─────
10
+ * 1. Add one or more toggle buttons anywhere in your HTML:
11
+ *
12
+ * <button class="theme_toggle theme_toggle_labeled" aria-label="Toggle colour theme">light-mode</button>
13
+ *
14
+ * 2. Drop this script at the end of <body> (or as a module):
15
+ *
16
+ * <script src="theme-toggle.js"></script>
17
+ *
18
+ * How it works
19
+ * ────────────
20
+ * - "system" → removes data-theme from <html>; OS preference governs (via roles.css media query)
21
+ * - "light" → sets data-theme="light" on <html>; forces light regardless of OS
22
+ * - "dark" → sets data-theme="dark" on <html>; forces dark regardless of OS
23
+ *
24
+ * Customise the icons by changing the ICONS object below.
25
+ */
26
+
27
+ (function () {
28
+ const STORAGE_KEY = 'fiko_theme';
29
+ const CYCLE = ['system', 'light', 'dark'];
30
+ const LABELS = { system: 'sys-mode', light: 'light-mode', dark: 'dark-mode' };
31
+
32
+ const root = document.documentElement;
33
+
34
+ function getTheme() {
35
+ return root.dataset.theme || 'system';
36
+ }
37
+
38
+ /* Shows click intention (next state) as a word label. */
39
+ function setLabel(btn, t) {
40
+ const next = CYCLE[(CYCLE.indexOf(t) + 1) % CYCLE.length];
41
+ btn.textContent = LABELS[next];
42
+ btn.title = 'Switch to ' + next;
43
+ }
44
+
45
+ function applyTheme(t) {
46
+ if (t === 'system') {
47
+ delete root.dataset.theme;
48
+ } else {
49
+ root.dataset.theme = t;
50
+ }
51
+ document.querySelectorAll('.theme_toggle').forEach(btn => setLabel(btn, t));
52
+ try { localStorage.setItem(STORAGE_KEY, t); } catch (_) {}
53
+ }
54
+
55
+ /* restore saved preference immediately to avoid flash */
56
+ try {
57
+ const saved = localStorage.getItem(STORAGE_KEY);
58
+ if (saved && CYCLE.includes(saved)) {
59
+ applyTheme(saved);
60
+ } else {
61
+ document.querySelectorAll('.theme_toggle').forEach(btn => setLabel(btn, 'system'));
62
+ }
63
+ } catch (_) {
64
+ document.querySelectorAll('.theme_toggle').forEach(btn => setLabel(btn, 'system'));
65
+ }
66
+
67
+ /* wire all toggle buttons */
68
+ document.querySelectorAll('.theme_toggle').forEach(btn => {
69
+ btn.addEventListener('click', () => {
70
+ const next = CYCLE[(CYCLE.indexOf(getTheme()) + 1) % CYCLE.length];
71
+ applyTheme(next);
72
+ });
73
+ });
74
+ })();