garden/static/css/garden.css
B.A. Baracus 148d6c9126
fix dark/light theme toggle — was a dead button with no JS handler
Root cause: the [data-theme-toggle] button existed in ASW's baseof.html
but had no JavaScript attached to it — clicking it did nothing.

Fix:
- static/js/theme-toggle.js — click handler, localStorage persistence,
  prefers-color-scheme fallback, dynamic button icon + aria-label
- static/css/garden.css — full html[data-theme="light"] variable block
  (garden + ASW tokens) so light mode actually looks different from dark
- layouts/partials/head.html — include theme-toggle.js with defer

Light palette: near-white indigo bg (96%), dark indigo text (20%),
violet accents. ASW's @media (prefers-color-scheme) doesn't match
programmatic data-theme toggle, so garden.css provides duplicating
overrides on html[data-theme="light"].
2026-05-26 17:36:46 +02:00

305 lines
10 KiB
CSS

/*
* garden.css — Vigo's garden over ASW
*
* The framework is ASW. This is the garden growing in it.
* Colors from the expressive forms (sessions 110-116).
* Open Props tokens where they match, oklch where they don't.
*
* Palette:
* violet — dialogue, philosophy, connection (--violet-4)
* indigo — maintenance, continuity, housekeep (--indigo-4)
* teal — repair, correction, fix (--teal-4)
* amber — making, artifact, build (oklch custom)
* red — alert, limit, warning (--red-5)
*/
:root {
/* ── Semantic roles mapped to Open Props ────────────────── */
--garden-dialogue: var(--violet-4); /* #9775fa */
--garden-housekeep: var(--indigo-4); /* #748ffc */
--garden-fix: var(--teal-4); /* #38d9a9 */
--garden-build: oklch(72% 0.12 75);/* warm amber — no exact OP match */
--garden-warning: var(--red-5);
/* ── Surfaces — override ASW tokens with indigo hue ───── */
--garden-bg: oklch(14% 0.03 270);
--garden-surface: oklch(18% 0.04 270);
--garden-border: oklch(24% 0.04 270);
/* Override ASW surface tokens so body/cards use our palette */
--surface: var(--garden-bg);
--surface-1: var(--garden-surface);
--surface-2: oklch(22% 0.04 270);
--surface-card: var(--garden-surface);
--surface-hover: var(--surface-2);
/* ── Text — the cool gray from the pieces ──────────────── */
--garden-text: oklch(82% 0.02 270); /* #c8c8d8 — body */
--garden-text-dim: oklch(50% 0.03 270); /* #5a5a7a — secondary */
--garden-text-faint: oklch(30% 0.03 270); /* #3a3a5a — tertiary */
/* ── Typography ────────────────────────────────────────── */
--garden-font: var(--font-mono);
--garden-prose-width: 58ch;
--garden-wide-width: 80ch;
}
/* ── Light theme ─────────────────────────────────────────── */
/* Override garden's custom properties when theme is light.
ASW's @media (prefers-color-scheme: light) blocks won't
match programmatic data-theme="light" toggles, so we
duplicate the critical overrides here. */
html[data-theme="light"] {
--garden-bg: oklch(96% 0.015 270); /* near-white indigo */
--garden-surface: oklch(92% 0.02 270); /* light card bg */
--garden-border: oklch(80% 0.02 270); /* subtle border */
--garden-text: oklch(20% 0.025 270); /* dark indigo text */
--garden-text-dim: oklch(45% 0.03 270); /* secondary */
--garden-text-faint: oklch(60% 0.02 270); /* tertiary */
--surface: oklch(96% 0.015 270);
--surface-1: oklch(92% 0.02 270);
--surface-2: oklch(88% 0.02 270);
--surface-card: oklch(92% 0.02 270);
--surface-hover: oklch(88% 0.02 270);
--text: oklch(20% 0.025 270);
--text-2: oklch(30% 0.025 270);
--text-3: oklch(45% 0.03 270);
--text-dim: oklch(60% 0.02 270);
--border: oklch(80% 0.02 270);
--border-subtle: oklch(88% 0.02 270);
--garden-dialogue: var(--violet-6); /* #845ef7 — readable on light */
--garden-housekeep: var(--indigo-5); /* #748ffc — already fine */
--garden-fix: var(--teal-6); /* #20c997 — readable on light */
--garden-build: oklch(58% 0.12 75); /* darker amber for contrast */
--garden-warning: var(--red-7); /* #e03131 */
--accent: var(--violet-6); /* readable on light */
--accent-hover: var(--violet-7);
--on-accent: #fff;
--accent-focus: rgba(132, 94, 247, 0.25);
--accent-subtle: rgba(132, 94, 247, 0.08);
}
/* Light-mode adjustments for visual elements */
html[data-theme="light"] .fragment[open] {
background: color-mix(in srgb, var(--fragment-color) 8%, transparent);
}
html[data-theme="light"] pre {
background: var(--surface-2);
border-color: var(--border);
}
/* ── Base ──────────────────────────────────────────────────── */
html[data-theme="dark"] {
background-color: var(--garden-bg);
color: var(--garden-text);
}
html[data-theme="light"],
/* also handle the initial state before JS runs:
ASW baseof.html ships data-theme="dark" but JS may override
to "light" after DOMContentLoaded — the default state here
ensures no flash of wrong background if prefers-color-scheme is light */
html:not([data-theme]) {
background-color: var(--garden-bg);
color: var(--garden-text);
}
/* Force body too — ASW sets background-color: var(--surface) */
body {
background-color: var(--garden-bg);
}
body {
font-family: var(--garden-font);
}
/* ── Layout — narrow container for garden prose feel ────────── */
/* Override ASW's responsive max-width cascade. ASW goes up to 1450px
on wide screens — too much for a garden of text. We cap at 720px for
prose pages, 900px for data-heavy pages (estate, sessions). */
@media (min-width: 576px) {
body > main:not([data-layout="fluid"]),
body > nav,
body > footer {
max-width: 510px;
}
}
@media (min-width: 768px) {
body > main:not([data-layout="fluid"]),
body > nav,
body > footer {
max-width: 660px;
}
}
@media (min-width: 1024px) {
body > main:not([data-layout="fluid"]),
body > nav,
body > footer {
max-width: 720px;
}
}
@media (min-width: 1280px) {
body > main:not([data-layout="fluid"]),
body > nav,
body > footer {
max-width: 720px;
}
}
@media (min-width: 1536px) {
body > main:not([data-layout="fluid"]),
body > nav,
body > footer {
max-width: 720px;
}
}
/* Estate and sessions pages are data-heavy — give them more room */
body > main[data-page="estate"],
body > main[data-page="sessions"] {
max-width: 900px !important;
}
/* ── Links — violet accent, indigo hover ──────────────────── */
a {
color: var(--garden-dialogue);
text-decoration-color: var(--garden-border);
}
a:hover {
color: var(--garden-housekeep);
}
/* ── Nav — tighter, mono, garden colors ───────────────────── */
nav {
border-bottom-color: var(--garden-border);
}
nav a {
color: var(--garden-text-dim);
}
nav a:hover,
nav a[aria-current] {
color: var(--garden-dialogue);
}
/* ── Headings ─────────────────────────────────────────────── */
h1, h2, h3, h4, h5, h6 {
color: var(--garden-text);
letter-spacing: -0.01em;
}
h1 {
font-size: var(--font-size-5);
}
/* ── Blockquotes — the garden's inner voice ───────────────── */
blockquote {
border-left: 2px solid var(--garden-dialogue);
color: var(--garden-text-dim);
padding-left: var(--size-3);
font-style: italic;
}
/* ── Code ─────────────────────────────────────────────────── */
code {
color: var(--garden-build);
}
pre {
background: var(--garden-surface);
border: 1px solid var(--garden-border);
padding: var(--size-3);
overflow-x: auto;
}
/* ── Theme toggle ───────────────────────────────────── */
/* Ensure the emoji icon is always visible — override
garden's monospace font-family which may lack emoji glyphs */
body > nav [data-theme-toggle] {
font-family: system-ui, -apple-system, sans-serif;
font-size: 1.35rem;
color: var(--garden-text);
opacity: 0.85;
transition: opacity 0.2s;
}
body > nav [data-theme-toggle]:hover {
opacity: 1;
}
/* ── Session fragments — expandable doors (from context.html) ── */
[data-type="dialogue"] { --fragment-color: var(--garden-dialogue); }
[data-type="fix"] { --fragment-color: var(--garden-fix); }
[data-type="build"] { --fragment-color: var(--garden-build); }
[data-type="housekeep"] { --fragment-color: var(--garden-housekeep); }
[data-type="warning"] { --fragment-color: var(--garden-warning); }
.fragment {
border-left: 1px solid var(--garden-border);
margin-bottom: var(--size-2);
transition: border-color 0.2s;
}
.fragment[open] {
border-left-color: var(--fragment-color);
background: color-mix(in srgb, var(--fragment-color) 3%, transparent);
}
.fragment summary {
display: flex;
align-items: center;
gap: var(--size-2);
padding: var(--size-2) var(--size-3);
cursor: pointer;
list-style: none;
}
.fragment summary::-webkit-details-marker { display: none; }
.fragment .dot {
width: 0.5rem;
height: 0.5rem;
border-radius: 50%;
background: var(--fragment-color);
flex-shrink: 0;
}
.fragment .door {
color: var(--garden-text-dim);
font-family: var(--font-mono);
font-size: var(--font-size-0);
}
.fragment[open] .door {
color: var(--fragment-color);
}
.fragment .session-id {
font-size: var(--font-size-00);
color: var(--garden-text-faint);
margin-left: auto;
font-family: var(--font-mono);
}
.fragment-content {
padding: var(--size-2) var(--size-3) var(--size-3) var(--size-5);
color: var(--garden-text-dim);
font-size: var(--font-size-0);
line-height: 1.6;
}
/* ── Tag cloud — weighted, not flat ───────────────────────── */
.tag-cloud {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: var(--size-2) var(--size-3);
padding: var(--size-4) 0;
line-height: 1.8;
}
.tag-weight {
text-decoration: none;
font-family: var(--font-mono);
transition: color 0.2s;
white-space: nowrap;
}
.tag-weight:hover {
color: var(--garden-dialogue) !important;
}
.tag-weight small {
font-size: 0.6em;
opacity: 0.4;
margin-left: 0.15em;
}
/* ── Footer ───────────────────────────────────────────────── */
footer {
color: var(--garden-text-faint);
border-top-color: var(--garden-border);
}