1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/* Runs before first paint: apply the saved theme, accent, font and pane state so nothing flashes. */
/* Windows, for the scrollbars app.css draws there. Outside the try below:
it reads no storage, and a page with storage blocked still runs on Windows. */
if (/Windows/.test(navigator.userAgent)) document.documentElement.dataset.os = "win";
try {
/* After a reset, nothing saved is read: the page that reset dropped the
keys, but was still running as it left and may have written one back. */
if (sessionStorage.getItem("snyvi.reset")) {
sessionStorage.removeItem("snyvi.reset");
Object.keys(localStorage).filter(function (k) { return k.indexOf("snyvi.") === 0; }).forEach(function (k) { localStorage.removeItem(k); });
}
/* Before 1.4.0 the theme was one key, "light" | "dark" | "", and "" meant
the system's. It is now which theme to follow, and the two slots below
say what "light" and "dark" mean. */
var old = localStorage.getItem("snyvi.theme");
if (old !== null) {
if (old === "light" || old === "dark") localStorage.setItem("snyvi.theme.follow", old);
localStorage.removeItem("snyvi.theme");
}
var d = document.documentElement;
var ac = localStorage.getItem("snyvi.accent");
if (ac) d.dataset.accent = ac;
var f = localStorage.getItem("snyvi.font");
if (f) d.dataset.font = f;
// The quiet mascot (docs/DESIGN.md §2.5), kept with the other looks.
if (localStorage.getItem("snyvi.mascot") === "quiet") d.dataset.mascot = "quiet";
// Under 760 px the sidebar is only ever its rail (app.js keeps this true).
if (localStorage.getItem("snyvi.side") === "0" || matchMedia("(max-width: 760px)").matches) d.dataset.side = "0";
if (localStorage.getItem("snyvi.rail") === "0") d.dataset.rail = "0";
if (localStorage.getItem("snyvi.wide") === "1") d.dataset.wide = "1";
if (localStorage.getItem("snyvi.wrap") === "1") d.dataset.wrap = "1";
var sw = +localStorage.getItem("snyvi.side-w"), rw = +localStorage.getItem("snyvi.rail-w");
if (sw) d.style.setProperty("--side-w", sw + "px");
if (rw) d.style.setProperty("--rail-w", rw + "px");
} catch (e) {}
/* The theme, resolved here and nowhere else. Three keys: your light theme,
your dark theme, and which of the two to follow -- "" is the system's
choice, read from the media query once now and again whenever it moves.
`data-theme` is always a concrete name, so the stylesheet carries each
palette once, with no media block to keep in step, and "is it dark?" has
one answer for every script: the `color-scheme` the theme's block set.
Outside the try above on purpose: with storage unreadable there is still
a theme, the system's, in the defaults. */
window.snyviTheme = (function () {
var d = document.documentElement, sys = matchMedia("(prefers-color-scheme: dark)");
var get = function (k, or) { try { return localStorage.getItem(k) || or; } catch (e) { return or; } };
/* First paint has Paper and Ink and no other theme: the rest are in
themes.css, which the page fetches once it is idle. Until it is in, a
theme from there is drawn from the copy of its block the app kept for
that slot -- written as `:root[data-theme=…]`, which outranks Paper's
:root wherever it sits in the page -- and with no copy (a first launch,
storage unreadable, a copy thrown out) the slot's own side stands in:
Paper for light, Ink for dark, never a white frame on a dark system.
The copies are only a head start; app.js checks them against the sheet
when it lands and keeps them current. */
var loaded = false, copy = document.createElement("style");
copy.id = "theme-copy";
copy.textContent = get("snyvi.theme.css.light", "") + get("snyvi.theme.css.dark", "");
document.head.appendChild(copy);
var apply = function () {
/* Contrast is the dark slot's default under a system asking for more
contrast: that is a signal like prefers-color-scheme and is honoured
the same way, until the slot is chosen. */
var light = get("snyvi.theme.light", "paper");
var dark = get("snyvi.theme.dark", matchMedia("(prefers-contrast: more)").matches ? "contrast" : "ink");
var follow = get("snyvi.theme.follow", "") || (sys.matches ? "dark" : "light");
var t = follow === "dark" ? dark : light;
if (!loaded && t !== "paper" && t !== "ink" && copy.textContent.indexOf(':root[data-theme="' + t + '"]') < 0) t = follow === "dark" ? "ink" : "paper";
d.dataset.theme = t;
return t;
};
apply();
sys.addEventListener("change", apply);
/* A token as a colour a script can hand to a canvas, a shell or Mermaid.
Read off `:root` as text, --accent is `light-dark(#…, #…)` and --accent-bg
a color-mix(): a custom property keeps its expression until an element
uses it. So a probe uses it, on the element asked for (a desk wears its
own accent in phase 4) or the root. The answer is said one way: a
browser gives a mixed colour as `color(srgb …)` and a plain one as
`rgb()`, and a shell prompt wants six hex digits while Mermaid's own
colour maths reads neither form; so an opaque colour comes back as
#rrggbb and one with alpha as rgba(). */
var colour = function (name, on) {
var p = document.createElement("i");
p.style.cssText = "position:absolute;visibility:hidden;color:var(" + name + ")";
(on || d).appendChild(p);
var c = getComputedStyle(p).color, m, v;
p.remove();
if ((m = /^color\(srgb ([\d.]+) ([\d.]+) ([\d.]+)(?: \/ ([\d.]+))?\)$/.exec(c))) v = [m[1] * 255, m[2] * 255, m[3] * 255, m[4] == null ? 1 : +m[4]];
else if ((m = /^rgba?\(([\d.]+), ([\d.]+), ([\d.]+)(?:, ([\d.]+))?\)$/.exec(c))) v = [+m[1], +m[2], +m[3], m[4] == null ? 1 : +m[4]];
else return c;
var h = function (n) { return ("0" + Math.round(n).toString(16)).slice(-2); };
return v[3] < 1 ? "rgba(" + Math.round(v[0]) + ", " + Math.round(v[1]) + ", " + Math.round(v[2]) + ", " + v[3] + ")" : "#" + h(v[0]) + h(v[1]) + h(v[2]);
};
/* themes.css is in: every theme can be drawn from it, so the copies step
aside and the saved choice is applied as it is. */
var ready = function () { loaded = true; copy.remove(); return apply(); };
return { apply: apply, ready: ready, system: sys, colour: colour, isDark: function () { return getComputedStyle(d).colorScheme === "dark"; } };
})();