:root {
--bg: #f9f9f9;
--bg-alt: #f4f4f4;
--fg: #333;
--fg-soft: #666;
--fg-muted: #888;
--surface: #e1e1e1;
--surface-hover: #ccc;
--card: #fff;
--border: #ccc;
--border-soft: #eee;
--accent: #4a90e2;
--accent-hover: #357bd8;
--accent-fg: #fff;
--accent-tint: #eef4fc;
--disabled-bg: #f0f0f0;
--disabled-fg: #c8c8c8;
--shadow: rgba(0, 0, 0, 0.1);
--ok-bg: #e6f4ea;
--ok-fg: #1e7a3d;
--ok-border: #b7e0c4;
--err-bg: #fdecea;
--err-fg: #b3261e;
--err-border: #f5c6c0;
--danger: #e0533a;
--danger-hover: #c8442d;
color-scheme: light;
/* Shape, type and backdrop defaults; the data-* blocks below
override them. Defaults match how the site looked before the
settings existed, so an untouched browser sees no change. */
--font-body: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
--text-scale: 1;
--radius-sm: 6px;
--radius-md: 8px;
--radius-lg: 12px;
--backdrop: none;
--backdrop-size: auto;
--backdrop-repeat: no-repeat;
}
:root[data-theme="dark"] {
--bg: #15171b;
--bg-alt: #1a1d22;
--fg: #e6e8ec;
--fg-soft: #b3bac2;
--fg-muted: #99a1ab;
--surface: #262a31;
--surface-hover: #323740;
--card: #1e2128;
--border: #383e47;
--border-soft: #2c313a;
--accent: #5b9ff0;
--accent-hover: #7db3f5;
--accent-fg: #0e1116;
--accent-tint: #1f2a38;
--disabled-bg: #1b1e23;
--disabled-fg: #464d57;
--shadow: rgba(0, 0, 0, 0.5);
--ok-bg: #14301f;
--ok-fg: #7fd4a0;
--ok-border: #245f3a;
--err-bg: #3a1a17;
--err-fg: #f2a19a;
--err-border: #6e2b24;
--danger: #e0533a;
--danger-hover: #f06b52;
color-scheme: dark;
}
/* Named styles. These repaint the neutrals only; the accent tokens
belong to the palettes below. Light variants come after the dark
base block so they win at equal specificity; the dark variants
carry the extra [data-theme="dark"] and win over both. */
:root[data-preset="modern"] {
--bg: #f6f7f9;
--bg-alt: #eef1f5;
--fg: #16181d;
--fg-soft: #4b5563;
--fg-muted: #7b828d;
--surface: #eaeef3;
--surface-hover: #dde3ea;
--card: #ffffff;
--border: #dfe4ea;
--border-soft: #eef1f5;
--shadow: rgba(16, 24, 40, 0.08);
}
:root[data-preset="minimal"] {
--bg: #ffffff;
--bg-alt: #ffffff;
--fg: #111111;
--fg-soft: #555555;
--fg-muted: #8a8a8a;
--surface: #f5f5f5;
--surface-hover: #ebebeb;
--card: #ffffff;
--border: #e4e4e4;
--border-soft: #f0f0f0;
--disabled-bg: #fafafa;
--disabled-fg: #dcdcdc;
--shadow: rgba(0, 0, 0, 0.04);
}
:root[data-preset="retro"] {
--bg: #f6ecd8;
--bg-alt: #efe2c8;
--fg: #3f2d1e;
--fg-soft: #6b5238;
--fg-muted: #8b7355;
--surface: #eadcc0;
--surface-hover: #e0cda9;
--card: #fdf6e6;
--border: #d5c09a;
--border-soft: #e7d9be;
--disabled-bg: #eee3cd;
--disabled-fg: #c3ae8b;
--shadow: rgba(93, 64, 31, 0.18);
}
:root[data-preset="terminal"] {
--bg: #eef2ec;
--bg-alt: #e5ebe3;
--fg: #16281a;
--fg-soft: #35543a;
--fg-muted: #5d7a62;
--surface: #e0e8de;
--surface-hover: #d3ded1;
--card: #f6faf5;
--border: #bfd0bd;
--border-soft: #dbe4d9;
--disabled-bg: #e7ece6;
--disabled-fg: #adbcab;
--shadow: rgba(20, 50, 25, 0.12);
}
:root[data-preset="playful"] {
--bg: #fff6fa;
--bg-alt: #ffeef5;
--fg: #3a2530;
--fg-soft: #6d4a5c;
--fg-muted: #9a7686;
--surface: #ffe4ef;
--surface-hover: #ffd3e5;
--card: #ffffff;
--border: #f6cede;
--border-soft: #ffe8f1;
--disabled-bg: #fdeff5;
--disabled-fg: #e7c3d3;
--shadow: rgba(190, 24, 93, 0.12);
}
:root[data-preset="newsprint"] {
--bg: #faf8f2;
--bg-alt: #f2efe6;
--fg: #14120d;
--fg-soft: #45403a;
--fg-muted: #6f6961;
--surface: #eeeae0;
--surface-hover: #e3ded1;
--card: #fffdf8;
--border: #d7d1c3;
--border-soft: #eae5da;
--disabled-bg: #f4f1e8;
--disabled-fg: #cdc7b8;
--shadow: rgba(20, 18, 13, 0.1);
}
:root[data-theme="dark"][data-preset="modern"] {
--bg: #0e1014;
--bg-alt: #14171d;
--fg: #eef1f6;
--fg-soft: #aeb6c2;
--fg-muted: #8b939f;
--surface: #1b1f26;
--surface-hover: #262b34;
--card: #14171d;
--border: #2a2f38;
--border-soft: #1f242b;
--shadow: rgba(0, 0, 0, 0.6);
}
:root[data-theme="dark"][data-preset="minimal"] {
--bg: #0d0d0d;
--bg-alt: #0d0d0d;
--fg: #ededed;
--fg-soft: #a8a8a8;
--fg-muted: #7d7d7d;
--surface: #191919;
--surface-hover: #242424;
--card: #131313;
--border: #292929;
--border-soft: #1e1e1e;
--disabled-bg: #141414;
--disabled-fg: #3a3a3a;
--shadow: rgba(0, 0, 0, 0.6);
}
:root[data-theme="dark"][data-preset="retro"] {
--bg: #191209;
--bg-alt: #211809;
--fg: #f2e2c4;
--fg-soft: #cbb692;
--fg-muted: #a08e6e;
--surface: #2a1f10;
--surface-hover: #382a16;
--card: #221809;
--border: #4a3820;
--border-soft: #33260f;
--disabled-bg: #1f170b;
--disabled-fg: #4d3f28;
--shadow: rgba(0, 0, 0, 0.6);
}
:root[data-theme="dark"][data-preset="terminal"] {
--bg: #04070a;
--bg-alt: #070c10;
--fg: #b6f7c1;
--fg-soft: #7ed497;
--fg-muted: #59a874;
--surface: #0c1712;
--surface-hover: #14251c;
--card: #08120e;
--border: #1d3a2a;
--border-soft: #12241a;
--disabled-bg: #080f0c;
--disabled-fg: #274a37;
--shadow: rgba(0, 0, 0, 0.75);
}
:root[data-theme="dark"][data-preset="playful"] {
--bg: #170f14;
--bg-alt: #1d131a;
--fg: #f7e6ee;
--fg-soft: #d3aec0;
--fg-muted: #a9899a;
--surface: #2a1a24;
--surface-hover: #382330;
--card: #1f141b;
--border: #45293a;
--border-soft: #2c1c26;
--disabled-bg: #1c1218;
--disabled-fg: #4b3341;
--shadow: rgba(0, 0, 0, 0.55);
}
:root[data-theme="dark"][data-preset="newsprint"] {
--bg: #121211;
--bg-alt: #171715;
--fg: #f2efe7;
--fg-soft: #bdb8ad;
--fg-muted: #8f8a80;
--surface: #1e1e1c;
--surface-hover: #2a2a27;
--card: #191917;
--border: #34332f;
--border-soft: #232321;
--disabled-bg: #161615;
--disabled-fg: #3d3c38;
--shadow: rgba(0, 0, 0, 0.6);
}
/* Accents. Same ordering rule. These never collide with the preset
blocks above — presets own the neutrals, palettes own the accent —
so which of the two comes last does not matter. */
:root[data-accent="blue"] {
--accent: #4a90e2; --accent-hover: #357bd8;
--accent-tint: #eef4fc; --accent-fg: #ffffff;
}
:root[data-accent="teal"] {
--accent: #0f766e; --accent-hover: #115e59;
--accent-tint: #e3f4f1; --accent-fg: #ffffff;
}
:root[data-accent="emerald"] {
--accent: #15803d; --accent-hover: #166534;
--accent-tint: #e7f6ec; --accent-fg: #ffffff;
}
:root[data-accent="violet"] {
--accent: #7c5cd6; --accent-hover: #6748c0;
--accent-tint: #f1edfd; --accent-fg: #ffffff;
}
:root[data-accent="rose"] {
--accent: #be185d; --accent-hover: #9d174d;
--accent-tint: #fce7f0; --accent-fg: #ffffff;
}
:root[data-accent="amber"] {
--accent: #b45309; --accent-hover: #92400e;
--accent-tint: #fdf1e0; --accent-fg: #ffffff;
}
:root[data-accent="graphite"] {
--accent: #4b5563; --accent-hover: #374151;
--accent-tint: #eef0f3; --accent-fg: #ffffff;
}
:root[data-theme="dark"][data-accent="blue"] {
--accent: #5b9ff0; --accent-hover: #7db3f5;
--accent-tint: #1f2a38; --accent-fg: #0e1116;
}
:root[data-theme="dark"][data-accent="teal"] {
--accent: #2dd4bf; --accent-hover: #5eead4;
--accent-tint: #10302d; --accent-fg: #04211e;
}
:root[data-theme="dark"][data-accent="emerald"] {
--accent: #4ade80; --accent-hover: #86efac;
--accent-tint: #13301f; --accent-fg: #062012;
}
:root[data-theme="dark"][data-accent="violet"] {
--accent: #a78bfa; --accent-hover: #c4b5fd;
--accent-tint: #241f3a; --accent-fg: #140f24;
}
:root[data-theme="dark"][data-accent="rose"] {
--accent: #f472a0; --accent-hover: #f9a8c4;
--accent-tint: #361c26; --accent-fg: #1c0f16;
}
:root[data-theme="dark"][data-accent="amber"] {
--accent: #fbbf4c; --accent-hover: #fcd383;
--accent-tint: #33270f; --accent-fg: #1c1405;
}
:root[data-theme="dark"][data-accent="graphite"] {
--accent: #9aa4b2; --accent-hover: #b8c1cc;
--accent-tint: #262b33; --accent-fg: #12151a;
}
/* Typefaces. All system stacks — the app is installable and has to look
the same with no network, so nothing is fetched. */
:root[data-font="neutral"] { --font-body: ui-sans-serif, system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif; }
:root[data-font="serif"]   { --font-body: Georgia, "Iowan Old Style", "Times New Roman", Times, serif; }
:root[data-font="rounded"] { --font-body: ui-rounded, "SF Pro Rounded", "Segoe UI Variable Display", Nunito, "Trebuchet MS", system-ui, sans-serif; }
:root[data-font="mono"]    { --font-body: ui-monospace, SFMono-Regular, "Cascadia Mono", Menlo, Consolas, "Liberation Mono", monospace; }
/* Text size scales the root font, so every rem-based size on every page
moves with it. The switcher itself is sized in px and stays put. */
:root[data-text="s"]  { --text-scale: 0.9; }
:root[data-text="l"]  { --text-scale: 1.15; }
:root[data-text="xl"] { --text-scale: 1.3; }
html { font-size: calc(100% * var(--text-scale)); }
:root[data-corners="sharp"] { --radius-sm: 0; --radius-md: 2px; --radius-lg: 4px; }
:root[data-corners="round"] { --radius-sm: 14px; --radius-md: 20px; --radius-lg: 26px; }
/* Backdrops. Painted on <body>, which every page gives a background
colour, so it sits behind the content and above the flat fill.
Pages must set background-color (not the background shorthand) or
they will wipe this out. */
:root[data-backdrop="glow"] {
--backdrop: radial-gradient(70rem 34rem at 50% -18%, var(--accent-tint), transparent 68%);
}
:root[data-backdrop="aurora"] {
--backdrop:
radial-gradient(48rem 26rem at 8% -8%, var(--accent-tint), transparent 62%),
radial-gradient(42rem 24rem at 96% 6%, var(--surface), transparent 62%);
}
:root[data-backdrop="dots"] {
--backdrop: radial-gradient(var(--border) 1px, transparent 1px);
--backdrop-size: 18px 18px;
--backdrop-repeat: repeat;
}
:root[data-backdrop="grid"] {
--backdrop:
linear-gradient(var(--border-soft) 1px, transparent 1px),
linear-gradient(90deg, var(--border-soft) 1px, transparent 1px);
--backdrop-size: 28px 28px;
--backdrop-repeat: repeat;
}
body {
background-image: var(--backdrop);
background-size: var(--backdrop-size);
background-repeat: var(--backdrop-repeat);
background-attachment: fixed;
background-position: center top;
transition: background-color 0.25s ease, color 0.25s ease;
}
/* Small polish that costs nothing and applies everywhere. */
::selection { background-color: var(--accent); color: var(--accent-fg); }
:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
border-radius: 2px;
}
/* Tapping a link on a phone used to leave it stuck in its :hover
colour after coming back (issue #8). Hover styling now lives behind
@media (hover: hover) on every page; this kills the grey flash that
some mobile browsers paint on top. */
a, button, input[type="submit"] { -webkit-tap-highlight-color: transparent; }
/* "System" follows the OS setting, "Full" opts out of it, "Reduced"
forces it on for people whose OS has no such switch. */
@media (prefers-reduced-motion: reduce) {
:root[data-motion="auto"] *,
:root[data-motion="auto"] *::before,
:root[data-motion="auto"] *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
:root[data-motion="reduced"] *,
:root[data-motion="reduced"] *::before,
:root[data-motion="reduced"] *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
/* ---- the control itself: light/dark pill + settings panel ---- */
.pl-theme-bar {
position: fixed;
top: 0.6rem;
right: 0.6rem;
z-index: 60;
display: flex;
align-items: flex-start;
gap: 0.35rem;
}
.theme-switcher,
.pl-gear-wrap {
display: inline-flex;
border: 1px solid var(--border);
border-radius: 6px;
overflow: hidden;
background-color: var(--card);
box-shadow: 0 1px 3px var(--shadow);
}
.theme-btn {
display: flex;
align-items: center;
justify-content: center;
width: 30px;
height: 26px;
padding: 0;
border: none;
background-color: transparent;
color: var(--fg-muted);
cursor: pointer;
transition: background-color 0.2s ease, color 0.2s ease;
}
.theme-btn + .theme-btn {
border-left: 1px solid var(--border);
}
.theme-btn.is-active {
background-color: var(--accent);
color: var(--accent-fg);
}
.theme-btn svg {
width: 14px;
height: 14px;
display: block;
}
.pl-gear[aria-expanded="true"] {
background-color: var(--accent);
color: var(--accent-fg);
}
.pl-settings {
position: fixed;
top: 2.9rem;
right: 0.6rem;
z-index: 60;
width: min(320px, calc(100vw - 1.2rem));
max-height: min(72vh, calc(100vh - 4rem));
overflow-y: auto;
padding: 0.9rem;
box-sizing: border-box;
background-color: var(--card);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
box-shadow: 0 8px 26px var(--shadow);
color: var(--fg);
/* px, not rem: the panel must not resize itself while you are
using it to change the text size. */
font-family: var(--font-body);
font-size: 13px;
line-height: 1.4;
text-align: left;
}
.pl-settings[hidden] { display: none; }
.pl-settings-title {
margin: 0 0 0.7rem;
font-size: 13px;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--fg-muted);
}
.pl-row + .pl-row { margin-top: 0.75rem; }
.pl-row-label {
display: block;
margin-bottom: 0.35rem;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.05em;
text-transform: uppercase;
color: var(--fg-soft);
}
.pl-opts {
display: flex;
flex-wrap: wrap;
gap: 0.3rem;
}
.pl-opt {
flex: 1 1 auto;
min-width: 0;
padding: 0.34rem 0.5rem;
font-family: inherit;
font-size: 12px;
line-height: 1.2;
white-space: nowrap;
border: 1px solid var(--border);
border-radius: 6px;
background-color: transparent;
color: var(--fg-soft);
cursor: pointer;
transition: background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.pl-opt.is-active {
background-color: var(--accent);
border-color: var(--accent);
color: var(--accent-fg);
}
/* Accent row: a dot split light/dark so you can see both variants. */
.pl-opt.pl-swatch {
flex: 0 0 auto;
display: inline-flex;
align-items: center;
gap: 0.35rem;
padding: 0.28rem 0.5rem 0.28rem 0.35rem;
}
.pl-dot {
width: 14px;
height: 14px;
border-radius: 50%;
border: 1px solid var(--border);
flex: 0 0 auto;
}
.pl-swatch[data-value="blue"] .pl-dot {
background-image: linear-gradient(135deg, #4a90e2 0 50%, #5b9ff0 50% 100%);
}
.pl-swatch[data-value="teal"] .pl-dot {
background-image: linear-gradient(135deg, #0f766e 0 50%, #2dd4bf 50% 100%);
}
.pl-swatch[data-value="emerald"] .pl-dot {
background-image: linear-gradient(135deg, #15803d 0 50%, #4ade80 50% 100%);
}
.pl-swatch[data-value="violet"] .pl-dot {
background-image: linear-gradient(135deg, #7c5cd6 0 50%, #a78bfa 50% 100%);
}
.pl-swatch[data-value="rose"] .pl-dot {
background-image: linear-gradient(135deg, #be185d 0 50%, #f472a0 50% 100%);
}
.pl-swatch[data-value="amber"] .pl-dot {
background-image: linear-gradient(135deg, #b45309 0 50%, #fbbf4c 50% 100%);
}
.pl-swatch[data-value="graphite"] .pl-dot {
background-image: linear-gradient(135deg, #4b5563 0 50%, #9aa4b2 50% 100%);
}
/* Style row: each button previews its own skin, so the active one is
marked with a ring rather than the usual accent fill (which would
paint over the preview). These come last so the per-preset colours
beat .pl-opt.is-active at equal specificity. */
.pl-presets {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(84px, 1fr));
gap: 0.3rem;
}
.pl-preset {
padding: 0.45rem 0.4rem;
text-align: center;
}
.pl-preset.is-active {
box-shadow: 0 0 0 2px var(--accent);
}
.pl-preset[data-value="classic"] {
background-color: #f9f9f9; color: #333333; border-color: #cccccc;
}
.pl-preset[data-value="modern"] {
background-color: #f6f7f9; color: #16181d; border-color: #dfe4ea;
}
.pl-preset[data-value="minimal"] {
background-color: #ffffff; color: #111111; border-color: #e4e4e4;
}
.pl-preset[data-value="retro"] {
background-color: #f6ecd8; color: #3f2d1e; border-color: #d5c09a;
}
.pl-preset[data-value="terminal"] {
background-color: #eef2ec; color: #16281a; border-color: #bfd0bd;
}
.pl-preset[data-value="playful"] {
background-color: #fff6fa; color: #3a2530; border-color: #f6cede;
}
.pl-preset[data-value="newsprint"] {
background-color: #faf8f2; color: #14120d; border-color: #d7d1c3;
}
:root[data-theme="dark"] .pl-preset[data-value="classic"] {
background-color: #15171b; color: #e6e8ec; border-color: #383e47;
}
:root[data-theme="dark"] .pl-preset[data-value="modern"] {
background-color: #0e1014; color: #eef1f6; border-color: #2a2f38;
}
:root[data-theme="dark"] .pl-preset[data-value="minimal"] {
background-color: #0d0d0d; color: #ededed; border-color: #292929;
}
:root[data-theme="dark"] .pl-preset[data-value="retro"] {
background-color: #191209; color: #f2e2c4; border-color: #4a3820;
}
:root[data-theme="dark"] .pl-preset[data-value="terminal"] {
background-color: #04070a; color: #b6f7c1; border-color: #1d3a2a;
}
:root[data-theme="dark"] .pl-preset[data-value="playful"] {
background-color: #170f14; color: #f7e6ee; border-color: #45293a;
}
:root[data-theme="dark"] .pl-preset[data-value="newsprint"] {
background-color: #121211; color: #f2efe7; border-color: #34332f;
}
.pl-reset {
display: block;
width: 100%;
margin-top: 0.9rem;
padding: 0.4rem;
font-family: inherit;
font-size: 12px;
border: 1px solid var(--border);
border-radius: 6px;
background-color: transparent;
color: var(--fg-soft);
cursor: pointer;
}
@media (hover: hover) {
.theme-btn:hover { background-color: var(--accent-tint); color: var(--fg); }
.theme-btn.is-active:hover,
.pl-gear[aria-expanded="true"]:hover { background-color: var(--accent-hover); color: var(--accent-fg); }
/* The style buttons preview their own skin, so hover must not
repaint them — it moves the ring instead. */
.pl-opt:not(.pl-preset):hover { background-color: var(--accent-tint); color: var(--fg); }
.pl-opt.is-active:not(.pl-preset):hover { background-color: var(--accent-hover); color: var(--accent-fg); }
.pl-preset:hover { box-shadow: 0 0 0 2px var(--accent-tint); }
.pl-preset.is-active:hover { box-shadow: 0 0 0 2px var(--accent); }
.pl-reset:hover { background-color: var(--accent-tint); color: var(--fg); }
}
@media print {
.pl-theme-bar, .pl-settings { display: none !important; }
body { background-image: none !important; }
}
