/* ==========================================================================
   Pildipank: app.css
   Calm, neutral UI where photos are the hero. Mobile-first, light/dark via
   prefers-color-scheme, keyboard accessible. Self-contained: no external
   fonts, no CDNs, no data: URIs; every url() points to /static/img/.
   Class vocabulary and markup patterns: docs/UI.md.

   Contents
     1. Tokens (palettes, modes)      8. Notices, badges, cards, empty
     2. Reset and base elements       9. Thumbnail grid and bulk select
     3. Layout: header, nav, footer  10. Folder tiles
     4. Page structure, breadcrumb   11. Image view, lightbox
     5. Buttons                      12. Votes and comments
     6. Forms                        13. Upload: dropzone and progress
     7. Tables and pager             14. Utilities, motion, print
                                     15. Colour themes: dots, style page
                                     16. Timeline ("Kõik pildid")
   ========================================================================== */

/* 1. Tokens ---------------------------------------------------------------- */

:root {
  color-scheme: light dark;

  /* Fixed swatches for the colour-theme dots (section 15): each palette's accent with a thin
     ring of its line-strong. Defined here, not in the palette blocks, so every dot shows its own
     palette whichever palette is active. */
  --swatch-salvei-roos: #b2d2aa;
  --swatch-salvei-roos-ring: #d1c8c7;
  --swatch-lillakas: #b8b8ed;
  --swatch-lillakas-ring: #cac9d3;
  --swatch-hommikurand: #a5cdeb;
  --swatch-hommikurand-ring: #cfccc2;
  --swatch-pojeng: #f2b8c9;
  --swatch-pojeng-ring: #d7c6cd;
  --swatch-virsik: #f7c4a5;
  --swatch-virsik-ring: #d6cabf;

  --shadow-sm: 0 1px 2px rgba(31, 29, 26, 0.06);
  --shadow-lg: 0 14px 34px -10px rgba(31, 29, 26, 0.22), 0 2px 6px rgba(31, 29, 26, 0.08);

  /* Type */
  --font-sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans",
    "Liberation Sans", sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;

  /* Spacing scale (4px base) */
  --s-1: 0.25rem;
  --s-2: 0.5rem;
  --s-3: 0.75rem;
  --s-4: 1rem;
  --s-5: 1.5rem;
  --s-6: 2rem;
  --s-7: 3rem;
  --s-8: 4rem;

  /* Shape and size */
  --radius-sm: 4px;
  --radius: 10px;
  --radius-lg: 14px;
  --radius-thumb: 6px;
  --radius-pill: 999px;
  --theater-bg: #111111;    /* the image page's photo stage, the same in light and dark */
  --tap: 2.75rem;           /* 44px minimum tap target (mobile-first) */
  --gutter: 1rem;           /* 16px side gutter on phones */
  --content-max: 96rem;
  --thumb-min: 6.5rem;      /* three columns on a 360px phone */
  --thumb-gap: 0.25rem;
  --thumb-ratio: 1 / 1;
}

/* Colour palettes. Five calm pastel themes, each with the same 30 colour tokens in a light and
   a dark block (the mode follows prefers-color-scheme). A block applies to ANY element with
   data-palette="<id>": layout.eta puts the user's choice on <html>, and the style page
   (/welcome/style) gives each preview card its own palette. Bare :root (no or an unknown
   attribute) is "salvei-roos". :root and [data-palette] have the same specificity, so the
   ORDER matters: salvei-roos first, then the others (lillakas, hommikurand, pojeng, virsik),
   light before dark.
   --accent is a pastel FILL (buttons, badges, the brand mark) with the dark --accent-fg on it.
   It never carries text or thin indicators on a light surface: links, selected and current
   states use --accent-strong, focus-like borders and the progress bar use --focus.
   Allowed theme ids live in src/lib/themes.js (THEME_IDS). */

/* Salvei ja roos (hele) */
:root,
[data-palette="salvei-roos"] {
  --bg: #fbf5f5;
  --surface: #fefcfb;
  --surface-2: #f4ecec;
  --fg: #282120;
  --fg-soft: #4e4544;
  --muted: #706665;
  --line: #e8e1e0;
  --line-strong: #d1c8c7;
  --field-bg: #fefcfb;
  --field-border: #998f8e;
  --row-hover: #fdf8f7;
  --stage: #eee8e7;

  --accent: #b2d2aa;
  --accent-hover: #a0c398;
  --accent-fg: #1f2a1c;
  --accent-bg: #e4f1e1;
  --accent-strong: #405f39;
  --accent-veil: rgba(112, 145, 105, 0.22);
  --ring: rgba(112, 145, 105, 0.32);
  --focus: #53734c;

  --danger: #9a444a;
  --danger-fg: #fffafa;
  --danger-bg: #feeded;
  --danger-line: #f0c5c5;
  --ok: #1f6954;
  --ok-bg: #e5f6f0;
  --ok-line: #b8dacd;
  --warn: #7c591a;
  --warn-bg: #fff2de;
  --warn-line: #ead0a8;
}

/* Lillakas õhtu (hele) */
[data-palette="lillakas"] {
  --bg: #f6f6fb;
  --surface: #fdfcff;
  --surface-2: #ededf4;
  --fg: #21202a;
  --fg-soft: #474652;
  --muted: #63626e;
  --line: #e1e0e8;
  --line-strong: #cac9d3;
  --field-bg: #fdfcff;
  --field-border: #868492;
  --row-hover: #f3f3f9;
  --stage: #e6e5ed;

  --accent: #b8b8ed;
  --accent-hover: #abaae6;
  --accent-fg: #24233c;
  --accent-bg: #ebebfe;
  --accent-strong: #56528e;
  --accent-veil: rgba(152, 150, 218, 0.28);
  --ring: rgba(152, 150, 218, 0.40);
  --focus: #6c68a9;

  --danger: #9c433f;
  --danger-fg: #fffdfc;
  --danger-bg: #ffedeb;
  --danger-line: #f0c5c1;
  --ok: #3a6744;
  --ok-bg: #e8f6ea;
  --ok-line: #bddac2;
  --warn: #7e5324;
  --warn-bg: #ffefde;
  --warn-line: #f1cdab;
}

/* Hommikurand (hele) */
[data-palette="hommikurand"] {
  --bg: #f9f7ef;
  --surface: #fdfdfa;
  --surface-2: #f1efe5;
  --fg: #1b2025;
  --fg-soft: #43484e;
  --muted: #5e646a;
  --line: #e5e3da;
  --line-strong: #cfccc2;
  --field-bg: #fefdfb;
  --field-border: #808b94;
  --row-hover: #f6f4eb;
  --stage: #e8e6df;

  --accent: #a5cdeb;
  --accent-hover: #90c0e2;
  --accent-fg: #122334;
  --accent-bg: #e0effa;
  --accent-strong: #275985;
  --accent-veil: rgba(165, 205, 235, 0.35);
  --ring: rgba(165, 205, 235, 0.55);
  --focus: #3d76a9;

  --danger: #a43b38;
  --danger-fg: #fefdfb;
  --danger-bg: #ffedeb;
  --danger-line: #f4c8c4;
  --ok: #346c42;
  --ok-bg: #e8f6ea;
  --ok-line: #bedec3;
  --warn: #845922;
  --warn-bg: #fdf2dd;
  --warn-line: #ead5ab;
}

/* Pojeng (hele) */
[data-palette="pojeng"] {
  --bg: #fcf4f6;
  --surface: #fffbfc;
  --surface-2: #f6e9ed;
  --fg: #2a1f23;
  --fg-soft: #50434a;
  --muted: #72646b;
  --line: #ecdfe4;
  --line-strong: #d7c6cd;
  --field-bg: #fffbfc;
  --field-border: #9a8a91;
  --row-hover: #fcf3f6;
  --stage: #f0e4e8;

  --accent: #f2b8c9;
  --accent-hover: #eca6bb;
  --accent-fg: #3d1a26;
  --accent-bg: #fde8ee;
  --accent-strong: #97385a;
  --accent-veil: rgba(226, 140, 168, 0.25);
  --ring: rgba(226, 140, 168, 0.4);
  --focus: #b0537a;

  --danger: #a0362f;
  --danger-fg: #fffafa;
  --danger-bg: #ffecea;
  --danger-line: #f2c4bf;
  --ok: #3a6744;
  --ok-bg: #e8f6ea;
  --ok-line: #bddac2;
  --warn: #7e5324;
  --warn-bg: #ffefde;
  --warn-line: #f1cdab;
}

/* Virsikuõis (hele) */
[data-palette="virsik"] {
  --bg: #fcf6f1;
  --surface: #fffcf9;
  --surface-2: #f5ece4;
  --fg: #2a211c;
  --fg-soft: #504640;
  --muted: #726760;
  --line: #ebe2da;
  --line-strong: #d6cabf;
  --field-bg: #fffcf9;
  --field-border: #998c82;
  --row-hover: #fbf4ee;
  --stage: #efe6de;

  --accent: #f7c4a5;
  --accent-hover: #f2b391;
  --accent-fg: #3d2112;
  --accent-bg: #feeadd;
  --accent-strong: #94461f;
  --accent-veil: rgba(240, 160, 120, 0.28);
  --ring: rgba(240, 160, 120, 0.42);
  --focus: #b5613a;

  --danger: #a3363d;
  --danger-fg: #fffafa;
  --danger-bg: #ffeceb;
  --danger-line: #f3c6c3;
  --ok: #346c42;
  --ok-bg: #e8f6ea;
  --ok-line: #bedec3;
  --warn: #845922;
  --warn-bg: #fdf2dd;
  --warn-line: #ead5ab;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-mode="light"]) {
    color-scheme: dark;
    --swatch-salvei-roos: #a4c19d;
    --swatch-salvei-roos-ring: #59504f;
    --swatch-lillakas: #b2b2e3;
    --swatch-lillakas-ring: #4e4d58;
    --swatch-hommikurand: #95bdda;
    --swatch-hommikurand-ring: #525047;
    --swatch-pojeng: #e6a9bc;
    --swatch-pojeng-ring: #5c4e54;
    --swatch-virsik: #eab595;
    --swatch-virsik-ring: #5a5049;

    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
    --shadow-lg: 0 16px 40px -12px rgba(0, 0, 0, 0.7), 0 2px 6px rgba(0, 0, 0, 0.4);
  }

  /* Salvei ja roos (tume) */
  :root:not([data-mode="light"]),
  :root:not([data-mode="light"])[data-palette="salvei-roos"],
  :root:not([data-mode="light"]) [data-palette="salvei-roos"] {
    --bg: #241f1e;
    --surface: #2d2727;
    --surface-2: #383130;
    --fg: #ede6e5;
    --fg-soft: #d1c8c7;
    --muted: #b2a8a7;
    --line: #403938;
    --line-strong: #59504f;
    --field-bg: #1f1a1a;
    --field-border: #817877;
    --row-hover: #322c2b;
    --stage: #161312;

    --accent: #a4c19d;
    --accent-hover: #b2cdac;
    --accent-fg: #192217;
    --accent-bg: #2d392b;
    --accent-strong: #b8d4b2;
    --accent-veil: rgba(164, 193, 157, 0.2);
    --ring: rgba(164, 193, 157, 0.32);
    --focus: #b2cdac;

    --danger: #e0a1a2;
    --danger-fg: #2c191a;
    --danger-bg: #3e2a2b;
    --danger-line: #664243;
    --ok: #91c9b4;
    --ok-bg: #21322c;
    --ok-line: #36544a;
    --warn: #dcbc8a;
    --warn-bg: #392f1f;
    --warn-line: #5f4c30;
  }

  /* Lillakas õhtu (tume) */
  :root:not([data-mode="light"])[data-palette="lillakas"],
  :root:not([data-mode="light"]) [data-palette="lillakas"] {
    --bg: #201f27;
    --surface: #27262f;
    --surface-2: #31303a;
    --fg: #e9e8f0;
    --fg-soft: #cac9d3;
    --muted: #a9a8b4;
    --line: #393841;
    --line-strong: #4e4d58;
    --field-bg: #1b1b22;
    --field-border: #747380;
    --row-hover: #2c2b35;
    --stage: #141318;

    --accent: #b2b2e3;
    --accent-hover: #bfbfec;
    --accent-fg: #1b1a2d;
    --accent-bg: #333349;
    --accent-strong: #c6c6ee;
    --accent-veil: rgba(178, 178, 227, 0.22);
    --ring: rgba(178, 178, 227, 0.35);
    --focus: #b8b8ea;

    --danger: #e9a29c;
    --danger-fg: #301715;
    --danger-bg: #402624;
    --danger-line: #6a403c;
    --ok: #9ccca4;
    --ok-bg: #213023;
    --ok-line: #3b5440;
    --warn: #e9ba8d;
    --warn-bg: #382b1e;
    --warn-line: #664d35;
  }

  /* Hommikurand (tume) */
  :root:not([data-mode="light"])[data-palette="hommikurand"],
  :root:not([data-mode="light"]) [data-palette="hommikurand"] {
    --bg: #22201b;
    --surface: #292822;
    --surface-2: #33322b;
    --fg: #edebe4;
    --fg-soft: #d0cec7;
    --muted: #adaba3;
    --line: #3b3932;
    --line-strong: #525047;
    --field-bg: #1e1d18;
    --field-border: #7d7a70;
    --row-hover: #2d2b25;
    --stage: #161511;

    --accent: #95bdda;
    --accent-hover: #a5cae5;
    --accent-fg: #0e1c28;
    --accent-bg: #243846;
    --accent-strong: #abd0eb;
    --accent-veil: rgba(149, 189, 218, 0.22);
    --ring: rgba(149, 189, 218, 0.38);
    --focus: #98c4e4;

    --danger: #e6a49e;
    --danger-fg: #2d1513;
    --danger-bg: #402927;
    --danger-line: #694542;
    --ok: #99cda3;
    --ok-bg: #233226;
    --ok-line: #3b5440;
    --warn: #dfc48b;
    --warn-bg: #372f1f;
    --warn-line: #5e5135;
  }

  /* Pojeng (tume) */
  :root:not([data-mode="light"])[data-palette="pojeng"],
  :root:not([data-mode="light"]) [data-palette="pojeng"] {
    --bg: #2a1f25;
    --surface: #2f262a;
    --surface-2: #3a3035;
    --fg: #f0e6ea;
    --fg-soft: #d6c8ce;
    --muted: #b5a6ad;
    --line: #42373c;
    --line-strong: #5c4e54;
    --field-bg: #201a1d;
    --field-border: #85767d;
    --row-hover: #342a2f;
    --stage: #171214;

    --accent: #e6a9bc;
    --accent-hover: #eebacb;
    --accent-fg: #2a1219;
    --accent-bg: #432a33;
    --accent-strong: #f2bfcf;
    --accent-veil: rgba(230, 169, 188, 0.22);
    --ring: rgba(230, 169, 188, 0.36);
    --focus: #eab4c5;

    --danger: #f0a392;
    --danger-fg: #301715;
    --danger-bg: #402624;
    --danger-line: #6a403c;
    --ok: #9ccca4;
    --ok-bg: #213023;
    --ok-line: #3b5440;
    --warn: #e9ba8d;
    --warn-bg: #382b1e;
    --warn-line: #664d35;
  }

  /* Virsikuõis (tume) */
  :root:not([data-mode="light"])[data-palette="virsik"],
  :root:not([data-mode="light"]) [data-palette="virsik"] {
    --bg: #2a211a;
    --surface: #2e2824;
    --surface-2: #39322d;
    --fg: #efe8e2;
    --fg-soft: #d4cac2;
    --muted: #b3a89f;
    --line: #413933;
    --line-strong: #5a5049;
    --field-bg: #1f1b18;
    --field-border: #82786f;
    --row-hover: #322b26;
    --stage: #171310;

    --accent: #eab595;
    --accent-hover: #f0c3a8;
    --accent-fg: #2a160b;
    --accent-bg: #45322a;
    --accent-strong: #f4c6a9;
    --accent-veil: rgba(234, 181, 149, 0.22);
    --ring: rgba(234, 181, 149, 0.36);
    --focus: #efbd9e;

    --danger: #eba0a8;
    --danger-fg: #2d1513;
    --danger-bg: #402927;
    --danger-line: #694542;
    --ok: #99cda3;
    --ok-bg: #233226;
    --ok-line: #3b5440;
    --warn: #dfc48b;
    --warn-bg: #372f1f;
    --warn-line: #5e5135;
  }
}

/* Forced dark (<html data-mode="dark">, a signed-in user's choice: plan-2.0a §2b). The SAME
   declarations as the media block above, which itself skips <html data-mode="light">: forced
   light simply keeps the light blocks. Each palette selector comes twice, on <html> itself and on
   any element inside it (the style page's preview cards). test/design-mode.test.js checks that
   every rule here has an identical twin in the media block and vice versa. */
:root[data-mode="dark"] {
  color-scheme: dark;
  --swatch-salvei-roos: #a4c19d;
  --swatch-salvei-roos-ring: #59504f;
  --swatch-lillakas: #b2b2e3;
  --swatch-lillakas-ring: #4e4d58;
  --swatch-hommikurand: #95bdda;
  --swatch-hommikurand-ring: #525047;
  --swatch-pojeng: #e6a9bc;
  --swatch-pojeng-ring: #5c4e54;
  --swatch-virsik: #eab595;
  --swatch-virsik-ring: #5a5049;

  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
  --shadow-lg: 0 16px 40px -12px rgba(0, 0, 0, 0.7), 0 2px 6px rgba(0, 0, 0, 0.4);
}

/* Salvei ja roos (tume) */
:root[data-mode="dark"],
:root[data-mode="dark"][data-palette="salvei-roos"],
:root[data-mode="dark"] [data-palette="salvei-roos"] {
  --bg: #241f1e;
  --surface: #2d2727;
  --surface-2: #383130;
  --fg: #ede6e5;
  --fg-soft: #d1c8c7;
  --muted: #b2a8a7;
  --line: #403938;
  --line-strong: #59504f;
  --field-bg: #1f1a1a;
  --field-border: #817877;
  --row-hover: #322c2b;
  --stage: #161312;

  --accent: #a4c19d;
  --accent-hover: #b2cdac;
  --accent-fg: #192217;
  --accent-bg: #2d392b;
  --accent-strong: #b8d4b2;
  --accent-veil: rgba(164, 193, 157, 0.2);
  --ring: rgba(164, 193, 157, 0.32);
  --focus: #b2cdac;

  --danger: #e0a1a2;
  --danger-fg: #2c191a;
  --danger-bg: #3e2a2b;
  --danger-line: #664243;
  --ok: #91c9b4;
  --ok-bg: #21322c;
  --ok-line: #36544a;
  --warn: #dcbc8a;
  --warn-bg: #392f1f;
  --warn-line: #5f4c30;
}

/* Lillakas õhtu (tume) */
:root[data-mode="dark"][data-palette="lillakas"],
:root[data-mode="dark"] [data-palette="lillakas"] {
  --bg: #201f27;
  --surface: #27262f;
  --surface-2: #31303a;
  --fg: #e9e8f0;
  --fg-soft: #cac9d3;
  --muted: #a9a8b4;
  --line: #393841;
  --line-strong: #4e4d58;
  --field-bg: #1b1b22;
  --field-border: #747380;
  --row-hover: #2c2b35;
  --stage: #141318;

  --accent: #b2b2e3;
  --accent-hover: #bfbfec;
  --accent-fg: #1b1a2d;
  --accent-bg: #333349;
  --accent-strong: #c6c6ee;
  --accent-veil: rgba(178, 178, 227, 0.22);
  --ring: rgba(178, 178, 227, 0.35);
  --focus: #b8b8ea;

  --danger: #e9a29c;
  --danger-fg: #301715;
  --danger-bg: #402624;
  --danger-line: #6a403c;
  --ok: #9ccca4;
  --ok-bg: #213023;
  --ok-line: #3b5440;
  --warn: #e9ba8d;
  --warn-bg: #382b1e;
  --warn-line: #664d35;
}

/* Hommikurand (tume) */
:root[data-mode="dark"][data-palette="hommikurand"],
:root[data-mode="dark"] [data-palette="hommikurand"] {
  --bg: #22201b;
  --surface: #292822;
  --surface-2: #33322b;
  --fg: #edebe4;
  --fg-soft: #d0cec7;
  --muted: #adaba3;
  --line: #3b3932;
  --line-strong: #525047;
  --field-bg: #1e1d18;
  --field-border: #7d7a70;
  --row-hover: #2d2b25;
  --stage: #161511;

  --accent: #95bdda;
  --accent-hover: #a5cae5;
  --accent-fg: #0e1c28;
  --accent-bg: #243846;
  --accent-strong: #abd0eb;
  --accent-veil: rgba(149, 189, 218, 0.22);
  --ring: rgba(149, 189, 218, 0.38);
  --focus: #98c4e4;

  --danger: #e6a49e;
  --danger-fg: #2d1513;
  --danger-bg: #402927;
  --danger-line: #694542;
  --ok: #99cda3;
  --ok-bg: #233226;
  --ok-line: #3b5440;
  --warn: #dfc48b;
  --warn-bg: #372f1f;
  --warn-line: #5e5135;
}

/* Pojeng (tume) */
:root[data-mode="dark"][data-palette="pojeng"],
:root[data-mode="dark"] [data-palette="pojeng"] {
  --bg: #2a1f25;
  --surface: #2f262a;
  --surface-2: #3a3035;
  --fg: #f0e6ea;
  --fg-soft: #d6c8ce;
  --muted: #b5a6ad;
  --line: #42373c;
  --line-strong: #5c4e54;
  --field-bg: #201a1d;
  --field-border: #85767d;
  --row-hover: #342a2f;
  --stage: #171214;

  --accent: #e6a9bc;
  --accent-hover: #eebacb;
  --accent-fg: #2a1219;
  --accent-bg: #432a33;
  --accent-strong: #f2bfcf;
  --accent-veil: rgba(230, 169, 188, 0.22);
  --ring: rgba(230, 169, 188, 0.36);
  --focus: #eab4c5;

  --danger: #f0a392;
  --danger-fg: #301715;
  --danger-bg: #402624;
  --danger-line: #6a403c;
  --ok: #9ccca4;
  --ok-bg: #213023;
  --ok-line: #3b5440;
  --warn: #e9ba8d;
  --warn-bg: #382b1e;
  --warn-line: #664d35;
}

/* Virsikuõis (tume) */
:root[data-mode="dark"][data-palette="virsik"],
:root[data-mode="dark"] [data-palette="virsik"] {
  --bg: #2a211a;
  --surface: #2e2824;
  --surface-2: #39322d;
  --fg: #efe8e2;
  --fg-soft: #d4cac2;
  --muted: #b3a89f;
  --line: #413933;
  --line-strong: #5a5049;
  --field-bg: #1f1b18;
  --field-border: #82786f;
  --row-hover: #322b26;
  --stage: #171310;

  --accent: #eab595;
  --accent-hover: #f0c3a8;
  --accent-fg: #2a160b;
  --accent-bg: #45322a;
  --accent-strong: #f4c6a9;
  --accent-veil: rgba(234, 181, 149, 0.22);
  --ring: rgba(234, 181, 149, 0.36);
  --focus: #efbd9e;

  --danger: #eba0a8;
  --danger-fg: #2d1513;
  --danger-bg: #402927;
  --danger-line: #694542;
  --ok: #99cda3;
  --ok-bg: #233226;
  --ok-line: #3b5440;
  --warn: #dfc48b;
  --warn-bg: #372f1f;
  --warn-line: #5e5135;
}

/* Forced light: the light blocks above already apply; this keeps the browser's own colours
   (form controls, scrollbars) light on a dark device as well. */
:root[data-mode="light"] { color-scheme: light; }

@media (min-width: 40rem) {
  :root {
    --gutter: 1.5rem;
    --thumb-min: 10rem;
    --thumb-gap: 0.5rem;
  }
}

@media (min-width: 64rem) {
  :root {
    --gutter: 2rem;
    --thumb-min: 12rem;
    --thumb-gap: 0.75rem;
  }
}

/* Precise pointers on large screens get slightly tighter controls. */
@media (min-width: 64rem) and (pointer: fine) {
  :root { --tap: 2.5rem; }
}

/* 2. Reset and base elements ---------------------------------------------- */

*, *::before, *::after { box-sizing: border-box; }

[hidden] { display: none !important; }

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  scroll-padding-top: var(--s-4);
  /* Classic scrollbars: the page keeps its width when a modal locks the scroll (and between
     short and long pages); overlay scrollbars are unaffected. */
  scrollbar-gutter: stable;
}

body {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  min-height: 100dvh;
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-sans);
  font-size: 1rem;
  line-height: 1.55;
  overflow-wrap: break-word;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  -webkit-tap-highlight-color: transparent;
}

h1, h2, h3, h4 {
  margin: 0 0 var(--s-3);
  color: var(--fg);
  font-weight: 650;
  line-height: 1.2;
  letter-spacing: -0.012em;
  overflow-wrap: anywhere;
}

h1 { font-size: clamp(1.5rem, 1.25rem + 1vw, 2rem); letter-spacing: -0.02em; }
h2 { font-size: 1.25rem; }
h3 { font-size: 1.0625rem; }
h4 { font-size: 1rem; }

p, ul, ol, dl, figure, blockquote, pre, table { margin: 0 0 var(--s-4); }

a {
  color: var(--accent-strong);
  text-decoration-line: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
}

a:hover { text-decoration-thickness: 2px; }

img, svg, video, canvas { display: block; max-width: 100%; }
img { height: auto; }

hr {
  height: 0;
  margin: var(--s-6) 0;
  border: 0;
  border-top: 1px solid var(--line);
}

strong, b { font-weight: 650; }
small { font-size: 0.875rem; }

code, kbd, samp, pre { font-family: var(--font-mono); font-size: 0.875em; }
code, kbd {
  padding: 0.1em 0.35em;
  border-radius: 4px;
  background: var(--surface-2);
}
pre {
  max-width: 100%;
  padding: var(--s-3) var(--s-4);
  overflow-x: auto;
  border-radius: var(--radius);
  background: var(--surface-2);
}

::selection { background: var(--accent-veil); }

/* One focus language everywhere: a crisp accent ring on keyboard focus only. */
:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}
:focus:not(:focus-visible) { outline: none; }

/* 3. Layout: header, nav, footer ------------------------------------------ */

.skip-link {
  position: absolute;
  top: -10rem;
  left: var(--gutter);
  z-index: 200;
  padding: var(--s-3) var(--s-4);
  border-radius: var(--radius-sm);
  background: var(--accent);
  color: var(--accent-fg);
  font-weight: 600;
  text-decoration: none;
}
.skip-link:focus { top: var(--s-2); }

.site-header {
  border-bottom: 1px solid var(--line);
  background: var(--bg);
}

.site-header-inner,
.site-footer-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  width: 100%;
  max-width: var(--content-max);
  margin-inline: auto;
  padding-inline: max(var(--gutter), env(safe-area-inset-left)) max(var(--gutter), env(safe-area-inset-right));
}

.site-header-inner {
  column-gap: var(--s-2);
  min-height: 3.5rem;
  padding-block: var(--s-1);
}
@media (min-width: 40rem) {
  .site-header-inner { column-gap: var(--s-4); }
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.625rem;
  min-height: var(--tap);
  margin-right: auto;
  color: var(--fg);
  font-size: 1.0625rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  text-decoration: none;
}
.brand:hover { color: var(--fg); }
.brand-mark { flex: none; width: 1.75rem; height: 1.75rem; }
.brand-name { white-space: nowrap; }
.brand-mark-bg { fill: var(--accent); }
.brand-mark-fg { fill: var(--accent-fg); }
/* The narrowest phones: the brand is the mark only (and the Beta badge on a labelled instance,
   which needs more room), so the whole header stays one row. The name stays for screen readers. */
@media (max-width: 22.49rem) {
  .brand-name { position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip-path: inset(50%); }
}
@media (max-width: 24.99rem) {
  .brand:has(.badge) .brand-name { position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip-path: inset(50%); }
}

/* The current environment (keskkond) right after the brand, "Pildipank / Töö", a slash in
   --line-strong like the breadcrumb. One environment: its name. More: a <details data-menu> whose
   toggle is the name with a chevron (a ghost button, --radius-sm) and whose panel is the user
   menu's card, anchored on the left. Phones (below 40rem): the brand is the mark only, so the
   environment's name has the room; it ellipsizes before it wraps the header. */
.brand:has(+ .env-switch) { margin-right: 0; }
.env-switch {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--s-2);
  min-width: 0;
  margin-right: auto;
}
.env-switch::before {
  content: "/";
  content: "/" / "";
  color: var(--line-strong);
  font-size: 1.125rem;
}
.env-current,
.env-menu-toggle {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  min-width: 0;
  min-height: var(--tap);
  color: var(--fg);
  font-size: 0.9375rem;
  font-weight: 600;
}
.env-name {
  min-width: 0;
  max-width: 9rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
@media (min-width: 40rem) {
  .env-name { max-width: 16rem; }
}
.env-menu { min-width: 0; }
.env-menu-toggle {
  padding: 0 0.625rem;
  margin-left: -0.625rem;
  border-radius: var(--radius-sm);
  list-style: none;
  cursor: pointer;
  user-select: none;
  transition: background-color 0.15s ease;
}
.env-menu-toggle::-webkit-details-marker { display: none; }
.env-menu-toggle::marker { content: ""; }
.env-menu-toggle::after {
  content: "";
  flex: none;
  width: 0.4375rem;
  height: 0.4375rem;
  margin-top: -0.25rem;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg);
  opacity: 0.6;
  transition: transform 0.15s ease;
}
.env-menu-toggle:hover,
.env-menu[open] > .env-menu-toggle { background: var(--surface-2); }
.env-menu[open] > .env-menu-toggle::after { margin-top: 0.25rem; transform: rotate(225deg); }
.env-menu-panel {
  position: absolute;
  top: calc(100% + 0.375rem);
  left: -0.625rem;
  z-index: 50;
  display: grid;
  width: 17.5rem;
  max-width: calc(100vw - 2 * var(--gutter));
  margin: 0;
  padding: 0.375rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface);
  box-shadow: var(--shadow-lg);
  font-size: 0.875rem;
}
.env-menu-label {
  margin: 0;
  padding: 0.375rem 0.625rem 0.5rem;
  color: var(--muted);
  font-size: 0.8125rem;
  font-weight: 550;
}
.env-menu-list {
  display: grid;
  gap: 0.375rem;
  margin: 0;
  padding: 0;
  list-style: none;
}
.env-menu-item {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  width: 100%;
  min-height: var(--tap);
  margin: 0;
  padding: 0 0.625rem;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--fg);
  font: inherit;
  text-align: left;
}
button.env-menu-item { cursor: pointer; transition: background-color 0.15s ease; }
button.env-menu-item:hover,
button.env-menu-item:focus-visible { background: var(--surface-2); }
button.env-menu-item:focus-visible { outline-offset: -2px; }
.env-menu-item[aria-current="true"] {
  background: var(--accent-bg);
  color: var(--accent-strong);
  font-weight: 550;
}
.env-menu-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.env-menu-role {
  flex: none;
  color: var(--muted);
  font-size: 0.8125rem;
  font-weight: 400;
}
.env-menu-item[aria-current="true"] .env-menu-role { color: inherit; }
.env-menu-check {
  flex: none;
  width: 1rem;
  height: 1rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}
/* The header stays one row with the switcher in it: the row does not wrap, and the environment's
   name (min-width 0) gives way first, down to an ellipsis, so the nav keeps its place. */
.site-header-inner:has(.env-switch) { flex-wrap: nowrap; }
.site-header-inner:has(.env-switch) > .brand { flex: none; }
@media (max-width: 39.99rem) {
  .site-header-inner:has(.env-switch) .brand-name { position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip-path: inset(50%); }
  /* The mark, then the name: no slash, and the toggle's padding is its own room. */
  .env-switch::before { content: none; }
  .env-menu-toggle { margin-left: 0; padding: 0 0.5rem; }
}

/* The nav sits next to the brand: three icon links, the colour-theme button and light/dark (from
   64rem) and the user menu, one row on every width down to 320px (owner, 2026-10-15). */
.site-nav {
  display: flex;
  flex: 1 1 auto;
  align-items: center;
  justify-content: flex-end;
  gap: var(--s-1);
}
@media (min-width: 40rem) {
  .site-nav { gap: var(--s-2); }
}

.site-nav ul {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-1);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* The main items: square icon links (44px targets, --radius-sm), the label hidden in the link
   and in its title. Phones: the targets sit side by side; from 40rem 4px apart. */
.site-nav .nav-primary {
  flex-wrap: nowrap;
  gap: 0;
}
@media (min-width: 40rem) {
  .site-nav .nav-primary { gap: var(--s-1); }
}
/* Wide screens: a little more room before the colour-theme button, so the icons read as their own group. */
@media (min-width: 64rem) {
  .site-nav .nav-primary { margin-right: var(--s-3); }
}

.nav-primary a {
  position: relative;
  display: grid;
  place-items: center;
  width: var(--tap);
  height: var(--tap);
  border-radius: var(--radius-sm);
  color: var(--fg-soft);
  text-decoration: none;
  transition: background-color 0.15s ease, color 0.15s ease;
}
.nav-primary a:hover { background: var(--surface-2); color: var(--fg); }
.nav-icon {
  width: 1.25rem;
  height: 1.25rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}
/* The current item: a short --accent-strong bar under the icon. */
.nav-primary a[aria-current="page"] { color: var(--fg); }
.nav-primary a[aria-current="page"]::after {
  content: "";
  position: absolute;
  bottom: 0.3125rem;
  left: calc(50% - 0.625rem);
  width: 1.25rem;
  height: 2px;
  border-radius: 1px;
  background: var(--accent-strong);
}

/* User menu: a <details> disclosure, works without JavaScript. */
.nav-menu { position: relative; }

/* Phones: the avatar alone in a 44px square, like the icon links; the chevron from 40rem and
   the name from 64rem. */
.nav-menu-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  min-height: var(--tap);
  padding: 0.1875rem;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--surface);
  color: var(--fg);
  font-size: 0.9375rem;
  font-weight: 550;
  list-style: none;
  cursor: pointer;
  user-select: none;
  transition: background-color 0.15s ease, border-color 0.15s ease;
}
.nav-menu-toggle::-webkit-details-marker { display: none; }
.nav-menu-toggle::marker { content: ""; }
.nav-menu-toggle::after {
  content: "";
  display: none;
  width: 0.4375rem;
  height: 0.4375rem;
  margin: -0.25rem 0.125rem 0 0.125rem;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg);
  opacity: 0.6;
  transition: transform 0.15s ease;
}
@media (min-width: 40rem) {
  .nav-menu-toggle { padding-right: 0.625rem; }
  .nav-menu-toggle::after { display: block; }
}
.nav-menu-toggle:hover { background: var(--surface-2); border-color: var(--line-strong); }
.nav-menu[open] > .nav-menu-toggle { background: var(--surface-2); border-color: var(--line-strong); }
.nav-menu[open] > .nav-menu-toggle::after { margin-top: 0.25rem; transform: rotate(225deg); }

.nav-menu:has([aria-current="page"]) > .nav-menu-toggle { border-color: var(--accent-strong); }

.nav-menu-name {
  display: none;
  max-width: 12rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The name shows from 64rem (avatar only below). */
@media (min-width: 64rem) {
  .nav-menu-name { display: inline; }
}

.avatar {
  display: inline-grid;
  flex: none;
  place-items: center;
  width: 2.25rem;
  height: 2.25rem;
  border-radius: 50%;
  background: var(--accent-bg);
  color: var(--accent-strong);
  font-size: 0.9375rem;
  font-weight: 650;
  line-height: 1;
}

/* The menu card (owner 2026-10-15): an identity header (avatar, name, e-mail), the links with
   16px stroke icons, the "Värviteema" and "Režiim" rows (section 15), then "Logi välja"; a
   hairline (.nav-menu-sep) starts each group. One inline padding (--menu-pad) for every row, so
   the avatar, the icons and the row labels share one left edge; rows are --tap tall (44px; 40px
   on wide screens with a precise pointer) with a --radius-sm tint on hover and keyboard focus. */
.site-nav .nav-menu-panel {
  --menu-row: var(--tap);
  --menu-pad: 0.625rem;
  position: absolute;
  top: calc(100% + 0.375rem);
  right: 0;
  z-index: 50;
  display: grid;
  gap: 0.125rem;
  width: 17.5rem;
  max-width: calc(100vw - 2 * var(--gutter));
  padding: 0.375rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface);
  box-shadow: var(--shadow-lg);
  font-size: 0.875rem;
}
.nav-menu-sep {
  margin-top: 0.25rem;
  padding-top: 0.375rem;
  border-top: 1px solid var(--line);
}

.nav-menu-id {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.375rem var(--menu-pad) 0.5rem;
}
.nav-menu-id-text {
  display: grid;
  min-width: 0;
  line-height: 1.35;
}
.nav-menu-id-name,
.nav-menu-id-email {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.nav-menu-id-name {
  color: var(--fg);
  font-size: 0.875rem;
  font-weight: 550;
}
.nav-menu-id-email {
  color: var(--muted);
  font-size: 0.8125rem;
}

.nav-menu-panel .nav-menu-item {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  width: 100%;
  min-height: var(--menu-row);
  margin: 0;
  padding: 0 var(--menu-pad);
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--fg);
  font-family: inherit;
  font-size: 0.875rem;
  font-weight: 400;
  line-height: 1.3;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 0.15s ease;
}
.nav-menu-panel .nav-menu-item:hover,
.nav-menu-panel .nav-menu-item:focus-visible { background: var(--surface-2); }
.nav-menu-panel .nav-menu-item:focus-visible { outline-offset: -2px; }
.nav-menu-item .nav-icon {
  flex: none;
  width: 1rem;
  height: 1rem;
  color: var(--muted);
  stroke-width: 1.8;
}
.nav-menu-panel .nav-menu-item[aria-current="page"] {
  background: var(--accent-bg);
  color: var(--accent-strong);
  font-weight: 550;
}
.nav-menu-item[aria-current="page"] .nav-icon { color: inherit; }

.nav-logout { margin: 0; }

.site-main {
  flex: 1 0 auto;
  width: 100%;
  max-width: var(--content-max);
  margin-inline: auto;
  padding: var(--s-5) max(var(--gutter), env(safe-area-inset-right)) var(--s-8) max(var(--gutter), env(safe-area-inset-left));
}
.site-main:focus { outline: none; }
.site-main > .notice { margin-bottom: var(--s-5); }

.is-anon .site-main { padding-top: clamp(var(--s-5), 6vh, var(--s-8)); }
.is-anon .site-main > .page,
.is-anon .site-main > .notice {
  max-width: 32rem;
  margin-inline: auto;
}

.site-footer {
  border-top: 1px solid var(--line);
  color: var(--muted);
  font-size: 0.875rem;
}
.site-footer-inner {
  gap: var(--s-2) var(--s-5);
  padding-top: var(--s-5);
  padding-bottom: max(var(--s-5), env(safe-area-inset-bottom));
}
.site-footer a {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  color: var(--muted);
}
.site-footer a:hover { color: var(--fg); }
.site-footer a[aria-current="page"] { color: var(--fg); }

/* 4. Page structure, breadcrumb ------------------------------------------- */

.page {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-content: start;
  gap: var(--s-5);
  min-width: 0;
}
.page > * { margin-block: 0; }
/* Components that carry their own outer margin elsewhere rely on the grid gap here. */
.page > :is(.notice, .pager, .field, .table, .empty) { margin-block: 0; }

/* A vertical stack of blocks inside a page section (folder page "Pildid", "Alamkaustad"):
   heading, toolbar, grid and pager sit --s-3 apart. */
.stack {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-content: start;
  gap: var(--s-3);
  min-width: 0;
}
.stack > *,
.stack > :is(.notice, .pager, .field, .table, .empty) { margin-block: 0; }

.page-narrow {
  width: 100%;
  max-width: 32rem;
  margin-inline: auto;
}

.page-header {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--s-3) var(--s-4);
}
.page-header > h1 {
  flex: 1 1 16rem;
  min-width: 0;
  margin: 0;
}
/* The title with small actions right after it (folder page: "Nimeta ümber", "Teisalda"). The
   actions sit in one .page-title-actions group that never breaks: next to a short name, or
   together on the next line; the .actions group stays separate. The title never gets narrower
   than that group (min-content), and its 20rem basis holds a short name plus the group, so the
   .actions wrap to their own row first. It anchors the .popover-panels, so a panel opens right
   under the title and its buttons. */
.page-title {
  position: relative;
  display: flex;
  flex: 1 1 20rem;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2);
  min-width: min-content;
}
/* The group hangs into its ghost buttons' 0.75rem padding, so on a line of its own the first
   icon lines up with the title text and the focus ring still fits in a 16px gutter; the h1's
   extra 0.75rem keeps the space when they share a line. */
.page-title > h1 {
  flex: 0 1 auto;
  min-width: 0;
  margin: 0 calc(var(--s-2) + 0.75rem) 0 0;
}
.page-title-actions {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: var(--s-2);
  margin-inline-start: -0.75rem;
}
.page-title-actions > details > summary.btn { padding-inline: 0.75rem; }
.page-header > :not(h1):not(.actions):not(.page-title) {
  flex-basis: 100%;
  margin: 0;
  order: 1;
}
.page-header > p,
.page-header > .muted { color: var(--muted); }

.breadcrumb {
  color: var(--muted);
  font-size: 0.875rem;
}
.breadcrumb ol {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0 0.375rem;
  margin: 0;
  padding: 0;
  list-style: none;
}
.breadcrumb li {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  min-width: 0;
  overflow-wrap: anywhere;
}
.breadcrumb li + li::before {
  content: "/";
  content: "/" / "";
  color: var(--line-strong);
}
.breadcrumb a {
  display: inline-block;
  padding-block: 0.375rem;
  color: var(--muted);
  text-decoration: none;
}
.breadcrumb a:hover { color: var(--fg); text-decoration: underline; }
.breadcrumb [aria-current="page"] {
  color: var(--fg);
  font-weight: 550;
}

.actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2);
}
.actions form {
  display: inline-flex;
  margin: 0;
}
.actions > details[open] { flex: 1 1 100%; }
.actions > details > form { display: grid; }

.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
}
.toolbar form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2);
  margin: 0;
}
.toolbar label { color: var(--muted); font-size: 0.875rem; }
.toolbar select,
.toolbar input:not([type="checkbox"]):not([type="radio"]) { width: auto; }
/* A folder list can hold long paths: the select stays a control, not a whole row (a fixed
   cap: a percentage would not limit its intrinsic width, so the group would take a full row). */
.toolbar select {
  max-width: 16rem;
  text-overflow: ellipsis;
}

.prose { max-width: 42rem; }
.prose h2 { margin-top: var(--s-6); }
.prose > :first-child { margin-top: 0; }
.prose li + li { margin-top: var(--s-1); }

.split {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s-6);
}
@media (min-width: 64rem) {
  .split {
    grid-template-columns: minmax(0, 1fr) minmax(18rem, 24rem);
    align-items: start;
  }
}

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 17rem), 1fr));
  gap: var(--s-4);
}

/* 5. Buttons --------------------------------------------------------------- */

.btn,
.pager a,
.pager button {
  --btn-bg: var(--surface);
  --btn-fg: var(--fg);
  --btn-border: var(--line-strong);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  min-height: var(--tap);
  padding: 0.5rem 1rem;
  border: 1px solid var(--btn-border);
  border-radius: var(--radius-sm);
  background: var(--btn-bg);
  color: var(--btn-fg);
  font: inherit;
  font-size: 0.9375rem;
  font-weight: 550;
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  user-select: none;
  transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease, transform 0.05s ease;
}
/* Hover rules skip disabled buttons (a disabled "Kustuta" must not fill red under the pointer);
   :where() keeps each rule's specificity, so the variants below still win over .btn:hover. */
.btn:hover:where(:not(:disabled)),
.pager a:hover,
.pager button:hover:where(:not(:disabled)) { --btn-bg: var(--surface-2); }
.btn:active,
.pager a:active,
.pager button:active { transform: translateY(1px); }

.btn-primary {
  --btn-bg: var(--accent);
  --btn-fg: var(--accent-fg);
  --btn-border: var(--accent);
}
.btn-primary:hover:where(:not(:disabled)) {
  --btn-bg: var(--accent-hover);
  --btn-border: var(--accent-hover);
}

.btn-danger {
  --btn-fg: var(--danger);
  --btn-border: var(--danger-line);
}
.btn-danger:hover:where(:not(:disabled)) {
  --btn-bg: var(--danger);
  --btn-fg: var(--danger-fg);
  --btn-border: var(--danger);
}

.btn-ghost {
  --btn-bg: transparent;
  --btn-border: transparent;
  --btn-fg: var(--fg-soft);
}
.btn-ghost:hover:where(:not(:disabled)) {
  --btn-bg: var(--surface-2);
  --btn-fg: var(--fg);
}
.btn-ghost.btn-danger { --btn-fg: var(--danger); }
.btn-ghost.btn-danger:hover:where(:not(:disabled)) {
  --btn-bg: var(--danger-bg);
  --btn-fg: var(--danger);
}

/* A small glyph before the label (mask, coloured by the text): .icon-pencil, .icon-folder-move. */
.icon-pencil { --icon: url("/static/img/icon-pencil.svg"); }
.icon-folder-move { --icon: url("/static/img/icon-folder-move.svg"); }
.icon-pencil::before,
.icon-folder-move::before {
  content: "";
  flex: none;
  width: 1.125rem;
  height: 1.125rem;
  background: currentColor;
  -webkit-mask: var(--icon) center / contain no-repeat;
  mask: var(--icon) center / contain no-repeat;
}

/* An icon-only button (.btn-icon, a square --tap target; the label is visually hidden text and
   the title): a 20px stroke glyph drawn like .nav-icon. The image page's download and delete. */
.btn-icon {
  width: var(--tap);
  padding: 0;
}
.btn-glyph {
  flex: none;
  width: 1.25rem;
  height: 1.25rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* <summary class="btn"> (disclosure used as a button): no marker. */
summary.btn { list-style: none; }
summary.btn::-webkit-details-marker { display: none; }
summary.btn::marker { content: ""; }
details[open] > summary.btn { --btn-bg: var(--surface-2); }

.btn:disabled,
.btn[aria-disabled="true"],
.pager button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  transform: none;
}
.btn[aria-disabled="true"] { pointer-events: none; }
.btn[aria-busy="true"] { cursor: progress; opacity: 0.75; }

/* 6. Forms ----------------------------------------------------------------- */

input, select, textarea, button { font: inherit; color: inherit; }
button { cursor: pointer; }

:where(input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="hidden"]):not([type="submit"]):not([type="button"]):not([type="reset"]):not([type="range"]):not([type="color"]), select, textarea) {
  max-width: 100%;
  min-height: var(--tap);
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--field-border);
  border-radius: var(--radius-sm);
  background-color: var(--field-bg);
  color: var(--fg);
  font-size: 1rem; /* 16px keeps iOS from zooming on focus */
  line-height: 1.4;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

:where(input, select, textarea)::placeholder { color: var(--muted); opacity: 1; }

:where(input, select, textarea):focus-visible {
  outline: none;
  border-color: var(--focus);
  box-shadow: 0 0 0 3px var(--ring);
}

:where(input, select, textarea):disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

textarea {
  display: block;
  min-height: 7.5rem;
  resize: vertical;
  line-height: 1.5;
}

select {
  padding-right: 2.25rem;
  -webkit-appearance: none;
  appearance: none;
  background-image:
    linear-gradient(45deg, transparent 50%, var(--muted) 50%),
    linear-gradient(135deg, var(--muted) 50%, transparent 50%);
  background-position: calc(100% - 1.125rem) 50%, calc(100% - 0.8125rem) 50%;
  background-size: 0.3125rem 0.3125rem, 0.3125rem 0.3125rem;
  background-repeat: no-repeat;
  cursor: pointer;
}
select[multiple] { padding-right: 0.75rem; background-image: none; }

input[type="checkbox"],
input[type="radio"] {
  flex: none;
  width: 1.125rem;
  height: 1.125rem;
  margin: 0;
  accent-color: var(--accent-strong);
  cursor: pointer;
}

input[type="file"] {
  max-width: 100%;
  color: var(--muted);
  font-size: 0.9375rem;
}
input[type="file"]::file-selector-button {
  min-height: var(--tap);
  margin-right: var(--s-3);
  padding: 0.5rem 1rem;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--fg);
  font: inherit;
  font-weight: 550;
  cursor: pointer;
}
input[type="file"]::file-selector-button:hover { background: var(--surface-2); }

.form {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s-4);
  max-width: 36rem;
  margin: 0;
}
.form > button,
.form > .btn,
.form > .actions { justify-self: start; }
.form :where(input:not([type="checkbox"]):not([type="radio"]):not([type="file"]), select, textarea) { width: 100%; }

/* Search: the field, the uploader filter and "Otsi" on one row from 40rem; the hint under it. */
.search-form { gap: var(--s-2); max-width: 52rem; }
.search-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s-3);
}
.search-row > .field { margin: 0; }
.search-row > .btn { justify-self: start; }
.search-form > .muted { margin: 0; font-size: 0.875rem; }
@media (min-width: 40rem) {
  .search-row {
    grid-template-columns: minmax(0, 1fr) minmax(0, 14rem) auto;
    align-items: end;
  }
}

@media (max-width: 39.99rem) {
  .form > button,
  .form > .btn { justify-self: stretch; }
}

.field {
  display: grid;
  gap: 0.375rem;
  min-width: 0;
  margin: 0 0 var(--s-4);
}
.form > .field { margin: 0; }
.field > label,
.field > .label {
  color: var(--fg);
  font-size: 0.9375rem;
  font-weight: 550;
}
.field :where(input:not([type="checkbox"]):not([type="radio"]):not([type="file"]), select, textarea) { width: 100%; }
.field small,
.field .muted,
.field .hint {
  color: var(--muted);
  font-size: 0.875rem;
}

/* A compact inline form: .field groups of about 20rem and a button on one row, controls
   bottom-aligned; each group takes its own line on phones ("Minu pildid" new-folder row). */
.field-row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--s-3);
  margin: 0;
}
.field-row > .field {
  flex: 0 1 20rem;
  margin: 0;
}

/* A checkbox or radio with its label on one line. */
.field > label:has(> input[type="checkbox"]),
.field > label:has(> input[type="radio"]),
.check {
  display: inline-flex;
  align-items: center;
  gap: 0.625rem;
  min-height: var(--tap);
  font-weight: 500;
  cursor: pointer;
}

:where(label:has(> input[type="checkbox"]), label:has(> input[type="radio"])) {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  min-height: var(--tap);
  cursor: pointer;
}

.field-error {
  display: flex;
  align-items: baseline;
  gap: 0.375rem;
  margin: 0;
  color: var(--danger);
  font-size: 0.875rem;
  font-weight: 500;
}
.field-error::before {
  content: "!";
  content: "!" / "";
  display: inline-grid;
  flex: none;
  place-items: center;
  width: 1rem;
  height: 1rem;
  border-radius: 50%;
  background: var(--danger);
  color: var(--danger-fg);
  font-size: 0.6875rem;
  font-weight: 700;
  line-height: 1;
  transform: translateY(0.125rem);
}

:where(input, select, textarea)[aria-invalid="true"] {
  border-color: var(--danger);
}
:where(input, select, textarea)[aria-invalid="true"]:focus-visible {
  box-shadow: 0 0 0 3px var(--danger-line);
}
.field:has(.field-error:not([hidden])) :where(input, select, textarea) { border-color: var(--danger); }

fieldset {
  min-width: 0;
  margin: 0;
  padding: var(--s-4);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
legend {
  padding: 0 var(--s-1);
  font-weight: 600;
}

details > summary { cursor: pointer; }

/* Spacing contract (docs/UI.md): two controls never touch. A container of several controls is
   flex or grid with a gap (.actions, .toolbar, .form, .field, .field-row, .pager, .stack,
   .page, ...); test/ui-spacing.test.js checks every template against that list and
   scripts/ui/spacing-lint.js measures a rendered page. The one flow rule: a disclosure opened by
   a button keeps what it reveals below the button. */
details > summary.btn ~ * { margin-top: var(--s-2); }
details.card > summary {
  margin: calc(-1 * var(--s-1)) 0;
  padding: var(--s-1) 0;
  font-weight: 600;
}
details.card[open] > summary { margin-bottom: var(--s-3); }

/* 7. Tables and pager ------------------------------------------------------ */

.table {
  width: 100%;
  margin: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  border-collapse: separate;
  border-spacing: 0;
  background: var(--surface);
  font-size: 0.9375rem;
  font-variant-numeric: tabular-nums;
}
.table caption {
  padding: 0 0 var(--s-2);
  caption-side: top;
  color: var(--muted);
  font-size: 0.875rem;
  text-align: left;
}
.table th,
.table td {
  padding: 0.625rem 0.875rem;
  border-bottom: 1px solid var(--line);
  text-align: left;
  vertical-align: top;
  overflow-wrap: anywhere;
}
.table thead th {
  background: var(--surface-2);
  color: var(--muted);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  vertical-align: bottom;
}
.table thead th:first-child { border-top-left-radius: calc(var(--radius-lg) - 1px); }
.table thead th:last-child { border-top-right-radius: calc(var(--radius-lg) - 1px); }
.table tbody tr:last-child > * { border-bottom: 0; }
.table tbody tr:hover { background: var(--row-hover); }
.table .num,
.table td.num,
.table th.num { text-align: right; }
.table form { display: inline-flex; margin: 0; }
.table time,
.table .num { white-space: nowrap; }
.table code { white-space: pre-wrap; }

@media (max-width: 39.99rem) {
  .table th,
  .table td { padding: 0.5rem 0.625rem; }
}

.table-wrap {
  max-width: 100%;
  overflow-x: auto;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  -webkit-overflow-scrolling: touch;
}
.table-wrap > .table {
  border: 0;
  border-radius: 0;
}
.table-wrap > .table th,
.table-wrap > .table td { overflow-wrap: normal; }

/* Stacked table (.table.table-stack, usually in a .table-wrap): below 40rem each row is a small
   card and each cell shows its column name from data-label above the value; a cell without
   data-label (the actions) has no label. The header row stays for screen readers only. */
@media (max-width: 39.99rem) {
  .table-wrap:has(> .table-stack) {
    overflow: visible;
    border: 0;
    border-radius: 0;
  }
  .table.table-stack {
    display: block;
    border: 0;
    background: none;
  }
  .table-stack > thead {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
  .table-stack > tbody { display: grid; gap: var(--s-3); }
  .table-stack > tbody > tr {
    display: grid;
    gap: var(--s-2);
    padding: var(--s-3) var(--s-4);
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    background: var(--surface);
  }
  .table.table-stack > tbody > tr > td {
    display: block;
    padding: 0;
    border: 0;
    text-align: start;
    overflow-wrap: anywhere;
  }
  .table-stack > tbody > tr > td[data-label]::before {
    content: attr(data-label);
    display: block;
    color: var(--muted);
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
  }
}

.pager {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  margin: var(--s-6) 0 0;
  padding: 0;
  list-style: none;
}
.pager > a:only-child,
.pager > button:only-child { min-width: 12rem; }
.pager [aria-current="page"] {
  --btn-bg: var(--accent-bg);
  --btn-fg: var(--accent-strong);
  --btn-border: transparent;
}
.pager .muted { font-size: 0.875rem; }

/* 8. Notices, badges, cards, empty ---------------------------------------- */

.notice {
  display: flex;
  align-items: flex-start;
  gap: var(--s-3);
  margin: 0 0 var(--s-4);
  padding: var(--s-3) var(--s-4);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--fg);
  line-height: 1.5;
}
.notice::before {
  content: "";
  flex: none;
  width: 0.5rem;
  height: 0.5rem;
  margin-top: 0.5em;
  border-radius: 50%;
  background: var(--muted);
}
.notice a { color: inherit; font-weight: 600; }
.notice-ok {
  border-color: var(--ok-line);
  background: var(--ok-bg);
}
.notice-ok::before { background: var(--ok); }
.notice-error {
  border-color: var(--danger-line);
  background: var(--danger-bg);
}
.notice-error::before { background: var(--danger); }
.notice-warn {
  border-color: var(--warn-line);
  background: var(--warn-bg);
}
.notice-warn::before { background: var(--warn); }
/* A notice with one action at its end: "Pilt viidi prügikasti." + "Võta tagasi" (a ghost button
   in the notice's text colour; hover fills it and frames it with the notice's line colour). The
   button keeps its full --tap height but reaches into the notice's padding (its margin box is one
   1.5rem text line), so the notice is as tall as a plain one. */
.notice-action {
  flex-wrap: wrap;
  align-items: center;
  row-gap: var(--s-2);
}
.notice-action::before { margin-top: 0; }
.notice-action > form { margin-inline-start: auto; }
.notice-action .btn {
  --btn-fg: var(--fg);
  margin-block: calc((1.5rem - var(--tap)) / 2);
  padding: 0.375rem 0.75rem;
  font-weight: 600;
}
.notice-action .btn:hover:where(:not(:disabled)) {
  --btn-bg: var(--surface);
  --btn-border: var(--ok-line);
}

.muted { color: var(--muted); }

.badge {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.0625rem 0.5rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  background: var(--surface-2);
  color: var(--fg-soft);
  font-size: 0.75rem;
  font-weight: 550;
  line-height: 1.5;
  white-space: nowrap;
  vertical-align: 0.125em;
}
.badge-ok {
  border-color: var(--ok-line);
  background: var(--ok-bg);
  color: var(--ok);
}
.badge-error {
  border-color: var(--danger-line);
  background: var(--danger-bg);
  color: var(--danger);
}
.badge-warn {
  border-color: var(--warn-line);
  background: var(--warn-bg);
  color: var(--warn);
}
.badge-accent {
  border-color: transparent;
  background: var(--accent-bg);
  color: var(--accent-strong);
}

.card {
  min-width: 0;
  padding: var(--s-5);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
}
.card > :first-child { margin-top: 0; }
.card > :last-child { margin-bottom: 0; }
/* .table has no margin (for grids); in a card's flow it keeps a paragraph's space below it, so a
   history table never touches the link or the next table under it. */
.card > :is(.table, .table-wrap):not(:last-child) { margin-bottom: var(--s-4); }
.card > h2,
.card > h3 { font-size: 1.0625rem; }
.card .form { max-width: none; }

@media (max-width: 39.99rem) {
  .card { padding: var(--s-4); }
}

/* Popover menu: <details class="popover" data-menu data-inline-form> in a .page-title, with a
   summary.btn and a .popover-panel holding a small form. The panel floats right under the title
   and its buttons, anchored to .page-title (not to the button), so it never runs off a phone
   screen; it covers the content below it. data-menu closes it on an outside click or Escape (one
   open at a time). */
.popover-panel {
  position: absolute;
  top: 100%;
  left: 0;
  z-index: 40;
  width: min(100%, 24rem);
  padding: var(--s-4);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
  color: var(--fg);
  box-shadow: var(--shadow-lg);
}
.popover-panel .form { max-width: none; }

/* The folder page's "⋯" menu (details.folder-menu[data-menu], batch C): the user menu's card
   (the .site-nav .nav-menu-panel box, its .nav-menu-item rows), anchored to the header's right edge
   so it stays on a phone screen. "Nimeta ümber" and "Teisalda" are details[data-inline-form]
   rows that open their small form in place; "Kustuta kaust" is the last row, in danger red. */
.page-folder > .page-header { position: relative; }
/* Not a form that opens in its row (.actions > details[open]) or below its summary.btn: the
   panel floats, the button stays where it is. */
.actions > details.folder-menu[open] { flex: none; }
.folder-menu > .folder-menu-panel { margin: 0; }
.folder-menu-panel {
  --menu-row: var(--tap);
  --menu-pad: 0.625rem;
  position: absolute;
  top: calc(100% + 0.375rem);
  right: 0;
  z-index: 50;
  display: grid;
  gap: 0.125rem;
  width: 17.5rem;
  max-width: calc(100vw - 2 * var(--gutter));
  padding: 0.375rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface);
  box-shadow: var(--shadow-lg);
  font-size: 0.875rem;
  list-style: none;
}
.folder-menu-panel summary.nav-menu-item { list-style: none; }
.folder-menu-panel summary.nav-menu-item::-webkit-details-marker { display: none; }
.folder-menu-panel details[open] > summary.nav-menu-item { background: var(--surface-2); }
.folder-menu-panel .form {
  display: grid; /* not the .actions form row it sits in */
  max-width: none;
  padding: var(--s-2) var(--menu-pad) var(--s-3);
}
.nav-menu-panel .nav-menu-danger { color: var(--danger); }
.nav-menu-danger .nav-icon { color: inherit; }

/* Selection bar (folder page, batch C): under the grid in form#bulk. bulk.js shows it only while
   something is selected; it then sticks to the bottom of the window while the grid is in view.
   Without JS it is always there. Phones: the count, "Vali kõik" and ✕ on the first row, the
   target folder and the two buttons on the second. */
.bulk-form {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s-4);
}
.bulk-bar {
  position: sticky;
  bottom: var(--s-2);
  z-index: 20;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-1) var(--s-2) var(--s-1) var(--s-3);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-lg);
}
.bulk-count {
  margin-inline-end: var(--s-2);
  font-weight: 600;
  white-space: nowrap;
}
.bulk-bar select {
  width: auto;
  max-width: 16rem;
  text-overflow: ellipsis;
}
.bulk-clear { margin-inline-start: auto; }
@media (max-width: 39.99rem) {
  .bulk-count,
  .bulk-bar > .check { order: -2; }
  .bulk-clear { order: -1; }
  .bulk-bar select {
    flex: 1 1 0;
    min-width: 0;
    max-width: none;
  }
}

/* Trash (batch C): one compact row per entry: a 56px thumbnail of its first image (a folder
   glyph for a folder), the title and one muted line, then "Taasta". No table, so a phone row
   is one short line pair and never scrolls sideways. */
.trash-list {
  display: grid;
  gap: var(--s-2);
  margin: 0;
  padding: 0;
  list-style: none;
}
.trash-item {
  display: grid;
  grid-template-columns: 3.5rem minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
}
.trash-thumb {
  display: grid;
  place-items: center;
  width: 3.5rem;
  height: 3.5rem;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--muted);
  object-fit: cover;
}
.trash-thumb-folder .nav-icon {
  width: 1.75rem;
  height: 1.75rem;
}
.trash-text { min-width: 0; }
.trash-title,
.trash-meta { margin: 0; }
.trash-title {
  overflow: hidden;
  font-weight: 550;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.trash-meta {
  color: var(--muted);
  font-size: 0.8125rem;
}
.trash-item form { margin: 0; }

/* Dialog: <dialog class="dialog" closedby="any"> opened modal (showModal(), or a button with
   command="show-modal"): top layer, the page behind it inert, Escape and a click on the backdrop
   close it. The UA styles centre it; never set display or position on it here (an author display
   would show the closed dialog). Its .form lays it out. */
dialog.dialog:not([open]) { display: none; }
.dialog {
  width: min(calc(100% - 2 * var(--gutter)), 30rem);
  max-width: 100%;
  max-height: calc(100% - 2 * var(--gutter));
  margin: auto; /* .page > * would pin it to the top */
  padding: var(--s-5);
  overflow: auto;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
  color: var(--fg);
  box-shadow: var(--shadow-lg);
}
.dialog::backdrop { background: rgba(31, 29, 26, 0.45); }
body:has(dialog.dialog[open]) { overflow: hidden; }
.dialog .form { max-width: none; }
.dialog h2 { margin: 0; font-size: 1.125rem; }
.dialog .form > p { margin: 0; } /* the .form gap spaces it */
.dialog .form > .actions {
  justify-self: stretch;
  justify-content: flex-end;
}
/* The text to type (the folder name), shown on its own line. */
.dialog .field kbd {
  display: block;
  width: fit-content;
  max-width: 100%;
  margin-top: var(--s-1);
  padding: 0.25rem 0.5rem;
  font-size: 1rem;
  font-weight: 650;
  overflow-wrap: anywhere;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-mode="light"]) .dialog::backdrop { background: rgba(0, 0, 0, 0.62); }
}
:root[data-mode="dark"] .dialog::backdrop { background: rgba(0, 0, 0, 0.62); }
@media (max-width: 39.99rem) {
  .dialog { padding: var(--s-4); }
}

.empty {
  display: grid;
  justify-items: center;
  gap: var(--s-3);
  margin: 0;
  padding: var(--s-7) var(--s-4);
  border: 1px dashed var(--line-strong);
  border-radius: var(--radius-lg);
  color: var(--muted);
  text-align: center;
}
.empty::before {
  content: "";
  width: 2.75rem;
  height: 2.75rem;
  background: currentColor;
  opacity: 0.55;
  -webkit-mask: url("/static/img/icon-empty.svg") center / contain no-repeat;
  mask: url("/static/img/icon-empty.svg") center / contain no-repeat;
}

/* 9. Thumbnail grid and bulk select --------------------------------------- */

.grid-thumbs {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--thumb-min), 1fr));
  gap: var(--thumb-gap);
  margin: 0;
  padding: 0;
  list-style: none;
}

.thumb {
  position: relative;
  min-width: 0;
  margin: 0;
}

.thumb > a,
a.thumb {
  position: relative;
  display: block;
  overflow: hidden;
  aspect-ratio: var(--thumb-ratio);
  border-radius: var(--radius-thumb);
  background: var(--surface-2);
  color: inherit;
  text-decoration: none;
}

.thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.35s ease, opacity 0.2s ease;
}

/* Text placed after the image link (score, uploader, date) stays on one quiet line. */
.thumb > :not(a):not(.thumb-select) {
  display: block;
  margin: 0.375rem 0 0;
  overflow: hidden;
  color: var(--muted);
  font-size: 0.8125rem;
  text-overflow: ellipsis;
  white-space: nowrap;
}

@media (hover: hover) {
  .thumb > a:hover img,
  a.thumb:hover img { transform: scale(1.035); }
}

.thumb-select {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 2;
  display: grid;
  place-items: center;
  width: var(--tap);
  height: var(--tap);
  cursor: pointer;
}
.thumb-select input {
  width: 1.25rem;
  height: 1.25rem;
  border-radius: 4px;
  box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.9), 0 1px 4px rgba(0, 0, 0, 0.45);
}

/* Selected thumbnail: focus-coloured frame and accent veil. */
.thumb:has(.thumb-select input:checked) > a::after {
  content: "";
  position: absolute;
  inset: 0;
  border: 3px solid var(--focus);
  border-radius: inherit;
  background: var(--accent-veil);
  pointer-events: none;
}

/* With a mouse, unchecked boxes appear on hover/focus or once anything is selected. */
@media (hover: hover) and (pointer: fine) {
  .thumb-select { opacity: 0; transition: opacity 0.15s ease; }
  .thumb:hover .thumb-select,
  .thumb:focus-within .thumb-select { opacity: 1; }
  .grid-thumbs:has(.thumb-select input:checked) .thumb-select { opacity: 1; }
}

/* 10. Folder tiles --------------------------------------------------------- */

.folder-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 9.5rem), 1fr));
  gap: var(--s-3);
  margin: 0;
  padding: 0;
  list-style: none;
}
@media (min-width: 40rem) {
  .folder-tiles {
    grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
    gap: var(--s-4);
  }
}

.folder-tile { min-width: 0; margin: 0; }

/* The tile box. The "new folder" tile (.folder-tile-new) shares it: closed, it is one more
   folder in the row (its <summary> is laid out like a folder link). */
.folder-tile > a,
.folder-tile-new > details > summary {
  position: relative;
  isolation: isolate;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: 0.125rem;
  overflow: hidden;
  aspect-ratio: 4 / 3;
  padding: var(--s-3) var(--s-4);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface-2);
  color: var(--fg);
  text-decoration: none;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

/* No cover image yet: a quiet folder glyph. */
.folder-tile > a::before,
.folder-tile-new > details > summary::before {
  content: "";
  position: absolute;
  top: var(--s-4);
  left: var(--s-4);
  width: 2rem;
  height: 2rem;
  background: var(--muted);
  opacity: 0.6;
  -webkit-mask: url("/static/img/icon-folder.svg") center / contain no-repeat;
  mask: url("/static/img/icon-folder.svg") center / contain no-repeat;
}

/* A responsive tile wraps its <img> in a <picture> (src/lib/picture.js): the picture box steps
   aside, so the img lays out exactly as a direct child of the link. */
.thumb picture,
.folder-tile picture,
.tl-tile picture { display: contents; }

.folder-tile img {
  position: absolute;
  inset: 0;
  z-index: -2;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.4s ease;
}

/* With a cover: name overlaid on a soft scrim. */
.folder-tile > a:has(img) {
  border-color: transparent;
  background: var(--stage);
  color: #ffffff;
}
.folder-tile > a:has(img)::before { content: none; }
.folder-tile > a:has(img)::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.72) 0%, rgba(0, 0, 0, 0.3) 42%, rgba(0, 0, 0, 0) 68%);
}
.folder-tile > a:has(img) .muted { color: rgba(255, 255, 255, 0.82); }

.folder-tile-name {
  display: -webkit-box;
  overflow: hidden;
  font-size: 1rem;
  font-weight: 600;
  line-height: 1.25;
  overflow-wrap: anywhere;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}
.folder-tile .muted {
  font-size: 0.8125rem;
  font-variant-numeric: tabular-nums;
}

@media (hover: hover) {
  .folder-tile > a:hover,
  .folder-tile-new > details:not([open]) > summary:hover { border-color: var(--line-strong); box-shadow: var(--shadow-lg); }
  .folder-tile > a:hover img { transform: scale(1.04); }
}

/* "New folder" tile, last in the grid. Closed, it looks like a sibling folder: the folder glyph
   with a "+" cut in, "Uus kaust" where a name goes. Open (<details>), the same tile holds a small
   form; it may grow taller than its row, never wider. */
.folder-tile-new > details > summary {
  list-style: none;
  cursor: pointer;
}
.folder-tile-new > details > summary::-webkit-details-marker { display: none; }
.folder-tile-new > details > summary::marker { content: ""; }
.folder-tile-new > details > summary::before {
  -webkit-mask-image: url("/static/img/icon-folder-plus.svg");
  mask-image: url("/static/img/icon-folder-plus.svg");
  transition: background-color 0.15s ease, opacity 0.15s ease;
}
.folder-tile-new > details:not([open]) > summary:is(:hover, :focus-visible)::before {
  background: var(--accent);
  opacity: 1;
}

.folder-tile-new > details[open] {
  min-height: 100%;
  padding: var(--s-2) var(--s-3) var(--s-3);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-lg);
  background: var(--surface-2);
  box-shadow: var(--shadow-sm);
}
.folder-tile-new > details[open] > summary {
  flex-direction: row;
  align-items: center;
  justify-content: flex-start;
  gap: 0.375rem;
  aspect-ratio: auto;
  min-height: var(--tap);
  margin-bottom: var(--s-1);
  padding: 0;
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  box-shadow: none;
}
.folder-tile-new > details[open] > summary::before {
  position: static;
  flex: none;
  width: 1.25rem;
  height: 1.25rem;
}
.folder-tile-new > details[open] > summary .muted { display: none; }
/* Open, the header row ends in a close mark (the summary closes the tile). */
.folder-tile-new > details[open] > summary::after {
  content: "\00d7";
  content: "\00d7" / "";
  flex: none;
  margin-left: auto;
  color: var(--muted);
  font-size: 1.5rem;
  font-weight: 400;
  line-height: 1;
}
.folder-tile-new > details[open] > summary:hover::after { color: var(--fg); }
.folder-tile-new .form { gap: var(--s-2); }
.folder-tile-new .field > label {
  color: var(--muted);
  font-size: 0.8125rem;
}
.folder-tile-new .form > .btn { justify-self: stretch; }

/* 11. Image view, lightbox ------------------------------------------------- */

figure.card {
  display: grid;
  place-items: center;
  min-height: 12rem;
  padding: 0;
  overflow: hidden;
  background: var(--stage);
}
figure.card > a { display: block; max-width: 100%; }
figure.card > img,
figure.card > a > img {
  width: auto;
  max-width: 100%;
  height: auto;
  max-height: 80vh;
  max-height: 80dvh;
  object-fit: contain;
}
@media (max-width: 39.99rem) {
  .page > figure.card {
    margin-inline: calc(-1 * var(--gutter));
    border-width: 1px 0;
    border-radius: 0;
  }
}

.photo {
  display: grid;
  place-items: center;
  min-height: 12rem;
  margin: 0;
  overflow: hidden;
  border-radius: var(--radius-lg);
  background: var(--stage);
}
.photo img {
  width: auto;
  max-width: 100%;
  height: auto;
  max-height: 80vh;
  max-height: 80dvh;
  object-fit: contain;
}
.photo figcaption {
  justify-self: stretch;
  padding: var(--s-2) var(--s-4);
  color: var(--muted);
  font-size: 0.875rem;
}
.photo > a { display: contents; }
@media (max-width: 39.99rem) {
  .photo {
    margin-inline: calc(-1 * var(--gutter));
    border-radius: 0;
  }
}

.photo-nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-2);
}

.meta,
.card > dl,
.page > dl {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: 0.375rem var(--s-4);
  margin: 0;
  font-size: 0.9375rem;
}
.meta dt,
.card > dl > dt,
.page > dl > dt { color: var(--muted); }
.meta dd,
.card > dl > dd,
.page > dl > dd { margin: 0; overflow-wrap: anywhere; }
@media (max-width: 24rem) {
  .meta,
  .card > dl,
  .page > dl { grid-template-columns: minmax(0, 1fr); }
  .meta dd,
  .card > dl > dd,
  .page > dl > dd { margin-bottom: var(--s-2); }
}

/* Image page (views/images/view.eta; owner 2026-10-15, like Facebook), in both contexts. From
   64rem two columns fill the viewport under the header: the photo on a near-black stage (the
   same in light and dark mode, --theater-bg) and a 24rem panel with its own scroll. Narrower,
   the stage spans the width on top and the panel follows in the page flow. The page drops the
   main padding and width cap; a notice above it gets its gutters back. */
.site-main:has(> [data-image-page]) {
  max-width: none;
  padding: 0;
}
.site-main:has(> [data-image-page]) > .notice {
  margin: var(--s-3) max(var(--gutter), env(safe-area-inset-right)) var(--s-3) max(var(--gutter), env(safe-area-inset-left));
}
.page[data-image-page] {
  --site-header-h: calc(3.5rem + 1px);
  gap: 0;
}
/* The header spans the window here (owner 2026-10-16): the logo at the left gutter, the user
   menu at the right one, which from 64rem is the panel's own inline padding. */
body:has(> .site-main > [data-image-page]) .site-header-inner { max-width: none; }

/* The stage: the photo centred, at most its natural size (never upscaled), as large as fits.
   A click on it opens the lightbox. */
.theater-stage {
  position: relative;
  display: grid;
  place-items: center;
  min-width: 0;
  min-height: 14rem;
  margin: 0;
  background: var(--theater-bg);
}
.theater-stage > a {
  display: grid;
  place-items: center;
  min-width: 0;
  max-width: 100%;
}
.theater-stage img {
  display: block;
  width: auto;
  min-width: 0;
  max-width: 100%;
  height: auto;
  max-height: 72vh;
  max-height: 72dvh;
  object-fit: contain;
}

/* Prev/next: round 44px buttons on the stage's left and right edges, vertically centred, real
   links above the lightbox link (a click on the photo between them opens the viewer). A
   translucent dark disc with a light chevron reads on any photo. With a mouse they appear on
   hover or keyboard focus; on touch screens they always show. The wrapper only names them (nav
   landmark) and lets clicks through. */
.img-navs {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
}
.img-nav {
  position: absolute;
  top: 50%;
  display: grid;
  place-items: center;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.55);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.22);
  color: #ffffff;
  text-decoration: none;
  opacity: 0;
  pointer-events: auto;
  transform: translateY(-50%);
  transition: opacity 0.2s ease, background-color 0.15s ease;
}
.img-nav-prev { left: var(--s-3); }
.img-nav-next { right: var(--s-3); }
.img-nav::before {
  content: "";
  width: 0.75rem;
  height: 0.75rem;
  border-bottom: 2.5px solid currentColor;
  border-left: 2.5px solid currentColor;
  transition: transform 0.2s ease;
}
.img-nav-prev::before { margin-left: 0.25rem; transform: rotate(45deg); }
.img-nav-next::before { margin-right: 0.25rem; transform: rotate(-135deg); }
.theater-stage:hover .img-nav,
.img-nav:focus-visible { opacity: 1; }
.img-nav:hover { background: rgba(0, 0, 0, 0.75); }
.img-nav-prev:hover::before { transform: translateX(-2px) rotate(45deg); }
.img-nav-next:hover::before { transform: translateX(2px) rotate(-135deg); }
.img-nav:focus-visible {
  outline: 2px solid #ffffff;
  outline-offset: 2px;
  box-shadow: 0 0 0 4px rgba(0, 0, 0, 0.6);
}
@media (hover: none), (pointer: coarse) {
  .img-nav { opacity: 1; }
}
/* Close: the viewer's .lightbox-close disc at the stage's top-left (two crossed bars, the name is
   aria-label and title), always shown; a link to the back link's target (the timeline tile or the
   folder). Escape does the same (image.js). */
.img-close {
  position: absolute;
  top: var(--s-3);
  left: var(--s-3);
  z-index: 2;
  display: grid;
  place-items: center;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.55);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.22);
  color: #ffffff;
  text-decoration: none;
  transition: background-color 0.15s ease;
}
.img-close::before,
.img-close::after {
  content: "";
  grid-area: 1 / 1;
  width: 1.125rem;
  height: 2.5px;
  background: currentColor;
  transform: rotate(45deg);
}
.img-close::after { transform: rotate(-45deg); }
.img-close:hover { background: rgba(0, 0, 0, 0.75); }
.img-close:focus-visible {
  outline: 2px solid #ffffff;
  outline-offset: 2px;
  box-shadow: 0 0 0 4px rgba(0, 0, 0, 0.6);
}

/* The panel: the back link with download and delete, the post (title, owner and date, caption,
   stats, actions), the comments, then "Üksikasjad" and the history. Theme surface colours. */
.theater-panel {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-content: start;
  gap: var(--s-4);
  min-width: 0;
  padding: var(--s-2) max(var(--gutter), env(safe-area-inset-right)) var(--s-6) max(var(--gutter), env(safe-area-inset-left));
  background: var(--surface);
}
.theater-panel > * { margin: 0; }

@media (min-width: 64rem) {
  /* One row that takes the whole height (.page's align-content: start would size it to the
     panel's content and leave an empty band under a short panel). */
  .page[data-image-page] {
    grid-template-columns: minmax(0, 1fr) 24rem;
    grid-template-rows: minmax(0, 1fr);
    height: calc(100vh - var(--site-header-h));
    height: calc(100dvh - var(--site-header-h));
  }
  .theater-stage img {
    max-height: calc(100vh - var(--site-header-h));
    max-height: calc(100dvh - var(--site-header-h));
  }
  .theater-panel {
    min-height: 0;
    overflow-y: auto;
    padding-inline: var(--s-4);
    border-left: 1px solid var(--line);
  }
  body:has(> .site-main > [data-image-page]) .site-header-inner {
    padding-inline: max(var(--s-4), env(safe-area-inset-left)) max(var(--s-4), env(safe-area-inset-right));
  }
  /* The stage and the panel are exactly the window: no footer under them, no page scroll. */
  body:has(> .site-main > [data-image-page]) > .site-footer { display: none; }
}

/* Top row: "← Kõik pildid" or the breadcrumb on the left, the icon buttons on the right. */
.theater-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-2);
  min-width: 0;
}
.theater-bar > :first-child { flex: 1 1 auto; min-width: 0; }
.theater-bar > .actions { flex: none; }
.tl-back {
  margin: 0;
  font-size: 0.875rem;
}
.tl-back a {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  min-height: var(--tap);
  color: var(--muted);
  text-decoration: none;
}
.tl-back a:hover { color: var(--fg); text-decoration: underline; }

/* The post: rows spaced by its gap (a button in the caption never meets the action row). */
.post {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s-3);
  min-width: 0;
}
.post > * { margin: 0; }
.post-head {
  position: relative;
  display: grid;
  gap: var(--s-3);
  min-width: 0;
}
.post-head > h1 {
  margin: 0;
  font-size: 1.25rem;
  line-height: 1.3;
  overflow-wrap: anywhere;
}

/* The owner's pencil (one form for the title and the description). With a title or a
   description it is an icon in the head's top-right corner, centred on the first row (the
   title, else the byline's avatar), and stays there while the form is open under the byline;
   with neither it is "Lisa pealkiri või kirjeldus" under the byline. While the form is open the
   caption it edits is not shown twice. */
.post-head:has(> .post-edit-icon) > :is(h1, .post-byline) {
  padding-inline-end: calc(var(--tap) + var(--s-2));
}
.post-edit-icon:not([open]),
.post-edit-icon[open] > summary {
  position: absolute;
  top: calc((1.625rem - var(--tap)) / 2);
  right: 0;
}
.post-head:has(> h1.visually-hidden) > .post-edit-icon:not([open]),
.post-head:has(> h1.visually-hidden) > .post-edit-icon[open] > summary {
  top: calc((2.25rem - var(--tap)) / 2);
}
.post-edit-add > summary.btn {
  margin-inline-start: calc(-1 * var(--s-2));
  padding-inline: var(--s-2);
}
.post-edit-icon > summary.btn ~ form { margin-top: 0; } /* the head's gap is above it */
.post:has(.post-edit[open]) > .post-caption { display: none; }
.post-byline {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  min-width: 0;
}
.post-byline > div { min-width: 0; }
.post-byline p { margin: 0; }
.post-owner {
  font-size: 0.9375rem;
  font-weight: 600;
  overflow-wrap: anywhere;
}
.img-date {
  color: var(--muted);
  font-size: 0.8125rem;
}
.img-date > time { white-space: nowrap; } /* "16. oktoober 2026" never splits */

/* The caption: the description as body text under the owner and date. */
.post-caption { overflow-wrap: anywhere; }
.post-caption > p { margin: 0; }

/* Stats: the score chip (+4 / 0 / -2), "4 poolt · 1 vastu", and the comment count on the right.
   Each part is hidden at zero (no votes, no comments); with both hidden the row goes too. */
.post-stats:not(:has(> :not([hidden]))) { display: none; }
.post-stats {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-2) var(--s-4);
  color: var(--muted);
  font-size: 0.875rem;
  scroll-margin-top: var(--s-4);
}
.post-score {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
}
.post-score > .vote-score {
  display: inline-grid;
  place-items: center;
  min-width: 2.25rem;
  height: 1.625rem;
  padding: 0 0.375rem;
  border-radius: var(--radius-sm);
  background: var(--accent-bg);
  color: var(--accent-strong);
  font-size: 0.9375rem;
  font-weight: 700;
}
.post-score > .vote-score[data-sign="zero"] { background: var(--surface-2); color: var(--fg-soft); }
.post-score > .vote-score[data-sign="neg"] { background: var(--danger-bg); color: var(--danger); }
.post-stats a {
  color: var(--muted);
  text-decoration: none;
}
.post-stats a:hover { color: var(--fg); text-decoration: underline; }

/* Actions: three equal, moderately square buttons between hairlines, like Facebook's row
   ("▲ Meeldib", "▼ Ei meeldi", "Kommenteeri"). The vote is pressed in the accent tint (up) or
   the danger tint (down). One's own photo has no action row (no votes; the comment box is
   right below). */
.post-actions {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--s-2);
  padding-block: var(--s-1);
  border-block: 1px solid var(--line);
}
.post-actions > .post-action {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.375rem;
  min-width: 0;
  min-height: var(--tap);
  padding: 0 var(--s-1);
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--fg-soft);
  font: inherit;
  font-size: 0.875rem;
  font-weight: 600;
  line-height: 1.2;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}
.post-actions > .post-action:hover:where(:not(:disabled)) { background: var(--surface-2); color: var(--fg); }
.post-actions > .vote-up[aria-pressed="true"] { background: var(--accent-bg); color: var(--accent-strong); }
.post-actions > .vote-down[aria-pressed="true"] { background: var(--danger-bg); color: var(--danger); }
.post-actions > .post-action:disabled {
  background: transparent;
  color: var(--fg-soft);
  opacity: 0.5;
  cursor: not-allowed;
}
.post-glyph { font-size: 0.75rem; }
.icon-comment { --icon: url("/static/img/icon-comment.svg"); }
.icon-comment::before {
  content: "";
  flex: none;
  width: 1rem;
  height: 1rem;
  background: currentColor;
  -webkit-mask: var(--icon) center / contain no-repeat;
  mask: var(--icon) center / contain no-repeat;
}
/* The narrowest phones (down to 320px): the three labels fit without the speech-bubble glyph. */
@media (max-width: 25rem) {
  .post-actions > .icon-comment::before { display: none; }
}

/* Comments in the panel: soft bubbles; the composer comes last. With no comments only the
   composer shows (the heading is hidden, the empty list takes no row). */
.comments:not(:has(li, form)),
.comments > ol:not(:has(> li)) { display: none; }
.comments > h2:focus { outline: none; }
.theater-panel .comments > h2 { font-size: 1rem; }
.theater-panel .comment {
  padding: var(--s-2) var(--s-3);
  border-color: transparent;
  border-radius: var(--radius);
  background: var(--surface-2);
}
.theater-panel .comment:has(> [data-comment-deleted]) {
  border-color: var(--line);
  background: transparent;
}

/* "Üksikasjad" and the admin history: collapsed rows under a hairline. */
.img-more {
  padding-top: var(--s-1);
  border-top: 1px solid var(--line);
  font-size: 0.9375rem;
}
.img-more > summary {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  min-height: var(--tap);
  color: var(--fg-soft);
  font-weight: 600;
  list-style: none;
}
.img-more > summary::-webkit-details-marker { display: none; }
.img-more > summary::before {
  content: "";
  width: 0.5rem;
  height: 0.5rem;
  margin: 0 0.125rem;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(-45deg);
  transition: transform 0.15s ease;
}
.img-more[open] > summary::before { transform: rotate(45deg); }
.img-more > summary:hover { color: var(--fg); }
.img-more > * { margin-block: 0; }
.img-more > * + * { margin-top: var(--s-3); }
.img-more .table { font-size: 0.8125rem; }

.pre-line,
.description,
.comment-body {
  white-space: pre-line;
  overflow-wrap: anywhere;
}

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: grid;
  grid-template-rows: minmax(0, 1fr) auto;
  place-items: center;
  width: 100%;
  max-width: none;
  height: 100%;
  max-height: none;
  margin: 0;
  padding: max(var(--s-2), env(safe-area-inset-top)) max(var(--s-2), env(safe-area-inset-right)) max(var(--s-2), env(safe-area-inset-bottom)) max(var(--s-2), env(safe-area-inset-left));
  border: 0;
  background: rgba(12, 11, 10, 0.96);
  color: rgba(255, 255, 255, 0.92);
  overscroll-behavior: contain;
}
dialog.lightbox:not([open]) { display: none; }
/* With the close disc (top right, its bottom edge at 3.5rem) the photo keeps clear of it, and the
   bottom gets the same room so the photo sits in the middle of the window, top to bottom. */
.lightbox:has(> .lightbox-close) {
  padding-top: max(var(--s-8), env(safe-area-inset-top));
  padding-bottom: max(var(--s-8), env(safe-area-inset-bottom));
}
.lightbox::backdrop { background: rgba(12, 11, 10, 0.96); }

.lightbox img {
  grid-row: 1;
  width: auto;
  max-width: 100%;
  height: auto;
  max-height: 100%;
  object-fit: contain;
  user-select: none;
  -webkit-user-drag: none;
}

/* The viewer's controls look like the stage's arrows (.img-nav): round 44px translucent dark
   discs with a light ring, always shown. Prev/next sit on the left and right edges, vertically
   centred (over the photo when it fills the width); close is at the top right. The glyphs are
   CSS (a chevron, two crossed bars); the names are aria-label and title. */
.lightbox-close,
.lightbox-prev,
.lightbox-next {
  position: absolute;
  z-index: 1;
  display: grid;
  place-items: center;
  width: 2.75rem;
  height: 2.75rem;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.55);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.22);
  color: #ffffff;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 0.15s ease;
}
.lightbox-close:hover,
.lightbox-prev:hover,
.lightbox-next:hover { background: rgba(255, 255, 255, 0.16); }
.lightbox-close {
  top: max(var(--s-3), env(safe-area-inset-top));
  right: max(var(--s-3), env(safe-area-inset-right));
}
.lightbox-close::before,
.lightbox-close::after {
  content: "";
  grid-area: 1 / 1;
  width: 1.125rem;
  height: 2.5px;
  background: currentColor;
  transform: rotate(45deg);
}
.lightbox-close::after { transform: rotate(-45deg); }
.lightbox-prev,
.lightbox-next {
  top: 50%;
  transform: translateY(-50%);
}
.lightbox-prev { left: max(var(--s-3), env(safe-area-inset-left)); }
.lightbox-next { right: max(var(--s-3), env(safe-area-inset-right)); }
.lightbox-prev::before,
.lightbox-next::before {
  content: "";
  width: 0.75rem;
  height: 0.75rem;
  border-bottom: 2.5px solid currentColor;
  border-left: 2.5px solid currentColor;
  transition: transform 0.2s ease;
}
.lightbox-prev::before { margin-left: 0.25rem; transform: rotate(45deg); }
.lightbox-next::before { margin-right: 0.25rem; transform: rotate(-135deg); }
.lightbox-prev:hover::before { transform: translateX(-2px) rotate(45deg); }
.lightbox-next:hover::before { transform: translateX(2px) rotate(-135deg); }

.lightbox-caption {
  grid-row: 2;
  max-width: 60rem;
  margin: 0;
  padding: var(--s-3) var(--s-4);
  color: rgba(255, 255, 255, 0.82);
  font-size: 0.9375rem;
  text-align: center;
  white-space: pre-line;
}
.lightbox :focus-visible { outline-color: #ffffff; outline-offset: 2px; }

body:has(dialog.lightbox[open]) { overflow: hidden; }
body:has(div.lightbox:not([hidden])) { overflow: hidden; }
/* The near-opaque viewer covers the whole window, the scrollbar's place too (a fixed box never
   covers a reserved gutter); a shift of the page behind it does not show. */
html:has(dialog.lightbox[open], div.lightbox:not([hidden])) { scrollbar-gutter: auto; }

/* 12. Votes and comments --------------------------------------------------- */

/* .vote wraps the vote control plus optional hint text. The pill is the <form> holding the
   buttons and the score, or .vote itself when the buttons sit directly inside it. */
.vote {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2) var(--s-3);
  margin: 0;
}
.vote > form {
  display: inline-flex;
  align-items: center;
  gap: 0.125rem;
  margin: 0;
  padding: 0.1875rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
}
.vote:not(:has(> form)) {
  display: inline-flex;
  gap: 0.125rem;
  padding: 0.1875rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
}
.vote > p {
  margin: 0;
  color: var(--muted);
  font-size: 0.875rem;
}

.vote-up,
.vote-down {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.375rem;
  min-width: var(--tap);
  min-height: var(--tap);
  padding: 0 0.75rem;
  border: 0;
  border-radius: 3px;
  background: transparent;
  color: var(--fg-soft);
  font: inherit;
  font-size: 0.9375rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  line-height: 1;
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}
.vote-up:hover { background: var(--surface-2); color: var(--accent-strong); }
.vote-down:hover { background: var(--surface-2); color: var(--danger); }
.vote-up[aria-pressed="true"] { background: var(--accent); color: var(--accent-fg); }
.vote-down[aria-pressed="true"] { background: var(--danger); color: var(--danger-fg); }
.vote-up:disabled,
.vote-down:disabled {
  background: transparent;
  color: var(--muted);
  opacity: 0.55;
  cursor: not-allowed;
}

/* Empty buttons (label in aria-label) get a drawn arrow. */
.vote-up:empty::before,
.vote-down:empty::before {
  content: "";
  width: 0;
  height: 0;
  border-right: 0.4375rem solid transparent;
  border-left: 0.4375rem solid transparent;
}
.vote-up:empty::before { border-bottom: 0.5rem solid currentColor; }
.vote-down:empty::before { border-top: 0.5rem solid currentColor; }

.vote-score {
  min-width: 2.5ch;
  padding: 0 0.25rem;
  font-size: 1.0625rem;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
  text-align: center;
}

.comments {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s-3);
  margin: 0;
  padding: 0;
  list-style: none;
}
.comments > ol,
.comments > ul {
  display: grid;
  gap: var(--s-3);
  margin: 0;
  padding: 0;
  list-style: none;
}
.comments > h2 { margin: 0; }
/* The comment composer, like Facebook's: one box with a one-line field that grows with the text
   (field-sizing; comments.js sets the height where that is missing) up to 10rem, and the send
   icon beside it. The box carries the border and the focus ring; its corners are the comment
   bubbles' (--radius), so it reads as the next bubble (no pill: only discs are round). */
.composer {
  display: grid;
  gap: var(--s-1);
  margin: 0;
}
.composer-box {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: end;
  gap: var(--s-2);
  padding: 0.125rem 0.125rem 0.125rem var(--s-3); /* the text starts where a comment's does */
  border: 1px solid var(--field-border);
  border-radius: var(--radius);
  background: var(--field-bg);
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.composer-box:focus-within {
  border-color: var(--focus);
  box-shadow: 0 0 0 3px var(--ring);
}
.composer-box > textarea {
  min-height: var(--tap);
  max-height: 10rem;
  padding: calc((var(--tap) - 1.5rem) / 2) 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  line-height: 1.5;
  resize: none;
  field-sizing: content;
}
.composer-box > textarea:focus-visible { box-shadow: none; }

.comment {
  display: grid;
  gap: 0.375rem;
  min-width: 0;
  margin: 0;
  padding: var(--s-3) var(--s-4);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
}
.comment > * { margin: 0; }
.comment-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.25rem var(--s-2);
  font-size: 0.875rem;
}
.comment-meta strong { color: var(--fg); font-weight: 600; }
.comment-meta time,
.comment-meta .muted { color: var(--muted); }
.comment .actions .btn {
  padding: 0.25rem 0.625rem;
  font-size: 0.875rem;
}
@media (pointer: fine) {
  .comment .actions .btn { min-height: 2.125rem; }
}
/* The deleted-comment placeholder: .is-deleted, or a comment holding [data-comment-deleted]. */
.comment.is-deleted {
  border-style: dashed;
  background: transparent;
  color: var(--muted);
  font-style: italic;
}
.comment:has(> [data-comment-deleted]) {
  border-style: dashed;
  background: transparent;
}
/* Meta line written as a plain muted paragraph (author · time · badge). */
.comment > p.muted:first-child { font-size: 0.875rem; }
.comment > p.muted:first-child strong { color: var(--fg); font-weight: 600; }

/* 13. Upload: dropzone and progress --------------------------------------- */

.dropzone {
  position: relative;
  display: grid;
  justify-items: center;
  gap: var(--s-3);
  padding: var(--s-6) var(--s-4);
  border: 2px dashed var(--line-strong);
  border-radius: var(--radius-lg);
  background: var(--surface);
  color: var(--fg-soft);
  text-align: center;
  transition: border-color 0.15s ease, background-color 0.15s ease;
}
.dropzone::before {
  content: "";
  width: 2.5rem;
  height: 2.5rem;
  background: currentColor;
  opacity: 0.55;
  -webkit-mask: url("/static/img/icon-upload.svg") center / contain no-repeat;
  mask: url("/static/img/icon-upload.svg") center / contain no-repeat;
}
.dropzone > * { margin: 0; }
.dropzone > h2 { font-size: 1.0625rem; }
.dropzone .muted { font-size: 0.875rem; }
.dropzone .actions { justify-content: center; }
.dropzone:focus-within {
  border-color: var(--focus);
}
.dropzone.is-dragover,
.dropzone[data-dragover] {
  border-color: var(--focus);
  border-style: solid;
  background: var(--accent-bg);
  color: var(--accent-strong);
}
/* A visually hidden file input still shows focus, on the label that opens it. */
.dropzone label:has(+ input[type="file"]:focus-visible),
.dropzone input[type="file"]:focus-visible + label {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}
/* The per-file list may live inside the dropzone: full width, left-aligned. */
.dropzone > ul,
.dropzone > ol {
  display: grid;
  gap: var(--s-2);
  justify-self: stretch;
  padding: 0;
  list-style: none;
  text-align: left;
}
.dropzone > ul:empty,
.dropzone > ol:empty,
.dropzone > p:empty { display: none; }
.dropzone > ul > .progress,
.dropzone > ol > .progress { margin: 0; }

/* One row per file: name and status on top, the bar below, extra content (duplicate warning
   with its link and buttons) on its own row. State: .is-done/.is-warn/.is-error or
   data-state="done|duplicate|error|skipped". */
.progress {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 0.375rem var(--s-3);
  margin: 0;
  padding: 0.625rem var(--s-3);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  font-size: 0.875rem;
  list-style: none;
}
.progress + .progress { margin-top: var(--s-2); }
.progress > * { grid-column: 1 / -1; }
.progress > :empty:not(progress):not(.progress-thumb) { display: none; }
.progress > :first-child {
  grid-row: 1;
  grid-column: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--fg);
}
.progress > :first-child + :not(progress),
.progress > .muted {
  grid-row: 1;
  grid-column: 2;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  text-align: right;
}
.progress > progress { grid-row: 2; }
.progress a { font-weight: 550; }
.progress .btn { min-height: 2.25rem; padding: 0.25rem 0.75rem; font-size: 0.875rem; }

.progress progress,
.upload-tile progress {
  grid-column: 1 / -1;
  width: 100%;
  height: 0.375rem;
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--surface-2);
  color: var(--focus);
  -webkit-appearance: none;
  appearance: none;
  overflow: hidden;
}
.progress progress::-webkit-progress-bar,
.upload-tile progress::-webkit-progress-bar {
  border-radius: var(--radius-pill);
  background: var(--surface-2);
}
.progress progress::-webkit-progress-value,
.upload-tile progress::-webkit-progress-value {
  border-radius: var(--radius-pill);
  background: var(--focus);
  transition: width 0.2s ease;
}
.progress progress::-moz-progress-bar,
.upload-tile progress::-moz-progress-bar {
  border-radius: var(--radius-pill);
  background: var(--focus);
}

.progress.is-done progress::-webkit-progress-value,
.progress[data-state="done"] progress::-webkit-progress-value,
.upload-tile[data-state="done"] progress::-webkit-progress-value { background: var(--ok); }
.progress.is-done progress::-moz-progress-bar,
.progress[data-state="done"] progress::-moz-progress-bar,
.upload-tile[data-state="done"] progress::-moz-progress-bar { background: var(--ok); }
.progress.is-done > :first-child + :not(progress),
.progress.is-done > .muted,
.progress[data-state="done"] > .muted { color: var(--ok); }

.progress.is-warn,
.progress[data-state="duplicate"],
.progress[data-state="warn"] {
  border-color: var(--warn-line);
  background: var(--warn-bg);
}
.progress.is-warn progress::-webkit-progress-value,
.progress[data-state="duplicate"] progress::-webkit-progress-value,
.upload-tile[data-state="duplicate"] progress::-webkit-progress-value { background: var(--warn); }
.progress.is-warn progress::-moz-progress-bar,
.progress[data-state="duplicate"] progress::-moz-progress-bar,
.upload-tile[data-state="duplicate"] progress::-moz-progress-bar { background: var(--warn); }

.progress.is-error,
.progress[data-state="error"] {
  border-color: var(--danger-line);
  background: var(--danger-bg);
}
.progress.is-error progress::-webkit-progress-value,
.progress[data-state="error"] progress::-webkit-progress-value,
.upload-tile[data-state="error"] progress::-webkit-progress-value { background: var(--danger); }
.progress.is-error progress::-moz-progress-bar,
.progress[data-state="error"] progress::-moz-progress-bar,
.upload-tile[data-state="error"] progress::-moz-progress-bar { background: var(--danger); }
.progress.is-error > :first-child + :not(progress),
.progress.is-error > .muted,
.progress[data-state="error"] > .muted { color: var(--danger); }

.progress[data-state="skipped"] { opacity: 0.7; }
.progress[data-state="skipped"] > :first-child { text-decoration: line-through; }

/* A small preview of the file at once (upload.js appends .progress-thumb last, so the name
   stays the first child). It spans the name and bar rows; the rest moves one column right. A
   long status (an error message) wraps within about half the row, so the name stays visible. */
.progress:has(> .progress-thumb) { grid-template-columns: 2.5rem minmax(0, 1fr) fit-content(55%); }
.progress:has(> .progress-thumb) > * { grid-column: 2 / -1; }
.progress:has(> .progress-thumb) > :first-child { grid-column: 2; }
.progress:has(> .progress-thumb) > :first-child + :not(progress),
.progress:has(> .progress-thumb) > .muted { grid-column: 3; }
.progress:has(> .progress-thumb) > .progress-thumb {
  grid-column: 1;
  grid-row: 1 / span 2;
}
.progress-thumb {
  display: grid;
  place-items: center;
  width: 2.5rem;
  height: 2.5rem;
  overflow: hidden;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--muted);
}
.progress-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* A file the browser cannot show (HEIC on Chrome/Windows): a neutral image glyph instead of a
   broken image; a tile also shows the file name under it. */
.progress-thumb.is-placeholder::before,
.upload-tile-preview.is-placeholder::before {
  content: "";
  width: 1.5rem;
  height: 1.5rem;
  background: currentColor;
  opacity: 0.6;
  -webkit-mask: url("/static/img/icon-empty.svg") center / contain no-repeat;
  mask: url("/static/img/icon-empty.svg") center / contain no-repeat;
}

/* "Minu pildid" staging grid: one tile per chosen file (preview, name, size, remove, its own
   folder select, then bar, status and extra content). Two columns on a 360px phone. */
.upload-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 8.5rem), 1fr));
  align-items: start;
  gap: var(--s-3);
  margin: 0;
  padding: 0;
  list-style: none;
}
.upload-tiles:empty { display: none; }
.upload-tiles + .actions > p {
  flex: 1 1 100%;
  min-width: 0;
  margin: 0;
  overflow-wrap: anywhere;
}

.upload-tile {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s-1);
  min-width: 0;
  margin: 0;
  padding: var(--s-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  font-size: 0.875rem;
}
.upload-tile > * { min-width: 0; margin: 0; }
.upload-tile > :empty:not(progress):not(.upload-tile-preview) { display: none; }
.upload-tile > .muted { font-size: 0.8125rem; font-variant-numeric: tabular-nums; }
.upload-tile select {
  width: 100%;
  min-width: 0;
  padding-left: 0.5rem;
  text-overflow: ellipsis;
}
.upload-tile[data-state="staged"] > progress { display: none; }

.upload-tile-preview {
  position: relative;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: var(--s-1);
  aspect-ratio: 1 / 1;
  overflow: hidden;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--muted);
}
.upload-tile-preview img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.upload-tile-preview.is-placeholder {
  padding: var(--s-2);
  font-size: 0.75rem;
  text-align: center;
  overflow-wrap: anywhere;
}
.upload-tile-preview.is-placeholder > span {
  display: -webkit-box;
  overflow: hidden;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  line-clamp: 3;
}

/* Remove (x): a 44px tap target over the preview's corner, drawn as a small round chip. */
.upload-tile-remove {
  position: absolute;
  top: var(--s-1);
  right: var(--s-1);
  display: grid;
  place-items: center;
  width: var(--tap);
  height: var(--tap);
  padding: 0;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--fg);
  font-size: 1.125rem;
  line-height: 1;
}
.upload-tile-remove > [aria-hidden="true"] {
  display: grid;
  place-items: center;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 4px;
  background: var(--surface);
  box-shadow: var(--shadow-sm);
}
.upload-tile-remove:hover > [aria-hidden="true"] { background: var(--surface-2); }
.upload-tile-remove:disabled { opacity: 0.5; cursor: not-allowed; }

.upload-tile-name {
  overflow: hidden;
  color: var(--fg);
  font-weight: 550;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.upload-tile-status { color: var(--muted); }
.upload-tile-extra {
  display: grid;
  gap: var(--s-2);
  overflow-wrap: anywhere;
}
.upload-tile-extra a { font-weight: 550; }
.upload-tile-extra .btn {
  width: 100%;
  min-height: 2.25rem;
  padding: 0.25rem 0.5rem;
  font-size: 0.875rem;
}

/* The "+ Uus kaust…" row when a tile's select opened it: stacked inside the tile. */
.upload-tile .field-row {
  display: grid;
  gap: var(--s-2);
  margin-top: var(--s-1);
}
.upload-tile .field-row > .field { min-width: 0; }
.upload-tile .field-row :is(input, select),
.upload-tile .field-row .btn { width: 100%; }

.upload-tile[data-state="done"] { border-color: var(--ok-line); }
.upload-tile[data-state="done"] .upload-tile-status { color: var(--ok); }
.upload-tile[data-state="duplicate"] {
  border-color: var(--warn-line);
  background: var(--warn-bg);
}
.upload-tile[data-state="error"] {
  border-color: var(--danger-line);
  background: var(--danger-bg);
}
.upload-tile[data-state="error"] .upload-tile-status { color: var(--danger); }
.upload-tile[data-state="skipped"] { opacity: 0.7; }
.upload-tile[data-state="skipped"] .upload-tile-name { text-decoration: line-through; }

/* 14. Utilities, motion, print -------------------------------------------- */

.visually-hidden {
  position: absolute !important;
  width: 1px !important;
  height: 1px !important;
  margin: -1px !important;
  padding: 0 !important;
  overflow: hidden !important;
  clip: rect(0 0 0 0) !important;
  clip-path: inset(50%) !important;
  border: 0 !important;
  white-space: nowrap !important;
}

.page-error { max-width: 40rem; }

[aria-busy="true"] { cursor: progress; }

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
    transition-duration: 0.01ms !important;
  }
  .thumb > a:hover img,
  a.thumb:hover img,
  .folder-tile > a:hover img { transform: none; }
}

@media (forced-colors: active) {
  .btn,
  .vote-up,
  .vote-down,
  .nav-menu-toggle,
  .post-actions > .post-action { border: 1px solid ButtonText; }
  /* The current nav item keeps its bar (a background, which forced colours would drop). */
  .nav-primary a[aria-current="page"]::after { background: Highlight; forced-color-adjust: none; }
  /* The translucent disc is dropped in forced colors: it gets a solid backing and a border. */
  .img-nav {
    opacity: 1;
    border: 2px solid CanvasText;
    background: Canvas;
    color: CanvasText;
    forced-color-adjust: none;
  }
  .img-nav::before { border-color: CanvasText; }
  .img-close {
    border: 2px solid CanvasText;
    background: Canvas;
    color: CanvasText;
    forced-color-adjust: none;
  }
  .img-close::before,
  .img-close::after { background: CanvasText; }
  /* The viewer's discs, the same way. */
  .lightbox-close,
  .lightbox-prev,
  .lightbox-next {
    border: 2px solid CanvasText;
    background: Canvas;
    color: CanvasText;
    forced-color-adjust: none;
  }
  .lightbox-prev::before,
  .lightbox-next::before { border-color: CanvasText; }
  .lightbox-close::before,
  .lightbox-close::after { background: CanvasText; }
  .vote-up[aria-pressed="true"],
  .vote-down[aria-pressed="true"] { outline: 2px solid Highlight; }
  .thumb:has(.thumb-select input:checked) > a::after { border-color: Highlight; }
}

@media print {
  .site-header,
  .site-footer,
  .skip-link,
  .actions,
  .toolbar,
  .thumb-select,
  .pager,
  .dropzone,
  .popover,
  .dialog,
  .folder-tile-new,
  .img-navs,
  .post-actions { display: none !important; }
  body { background: #ffffff; color: #000000; }
  .card,
  .comment { box-shadow: none; }
}

/* 15. Colour themes: dots, style page -------------------------------------- */

/* Colour dots ("Värviteema", views/partials/theme-switch.eta). Each dot is a submit button with
   a 44px target and a 16px swatch of ITS palette (fixed --swatch-* tokens, section 1), so the
   dots look the same whichever palette is active. The active dot gets a --focus ring. */
.theme-switch { margin: 0; }
.theme-switch-dots {
  display: flex;
  align-items: center;
  gap: var(--s-2);
}

.theme-dot {
  display: inline-grid;
  flex: none;
  place-items: center;
  width: var(--tap);
  height: var(--tap);
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  cursor: pointer;
}
.theme-dot::before {
  content: "";
  width: 1rem;
  height: 1rem;
  border-radius: 50%;
  background: var(--dot);
  box-shadow: 0 0 0 1px var(--dot-ring);
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.theme-dot[data-theme="salvei-roos"] { --dot: var(--swatch-salvei-roos); --dot-ring: var(--swatch-salvei-roos-ring); }
.theme-dot[data-theme="lillakas"] { --dot: var(--swatch-lillakas); --dot-ring: var(--swatch-lillakas-ring); }
.theme-dot[data-theme="hommikurand"] { --dot: var(--swatch-hommikurand); --dot-ring: var(--swatch-hommikurand-ring); }
.theme-dot[data-theme="pojeng"] { --dot: var(--swatch-pojeng); --dot-ring: var(--swatch-pojeng-ring); }
.theme-dot[data-theme="virsik"] { --dot: var(--swatch-virsik); --dot-ring: var(--swatch-virsik-ring); }
.theme-dot:hover::before { transform: scale(1.15); }
.theme-dot[aria-pressed="true"]::before {
  box-shadow: 0 0 0 1px var(--dot-ring), 0 0 0 3px var(--bg), 0 0 0 5px var(--focus);
}

/* The user menu's "Värviteema" and "Režiim" rows, on every width: a settings row, not a link
   (no tint, no icon), the label on the menu's left edge and the control on its right. In the menu
   the dots are a 20px swatch in a 28px target, 6px apart; the chosen one gets a 2px --fg ring and
   a check on the swatch (dark on every pastel swatch, in both modes), so the choice reads without
   colour. The header popover keeps the plain dots above. */
.nav-menu-theme,
.nav-menu-mode {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-2);
  min-height: var(--menu-row);
  padding: 0 var(--menu-pad);
}
.nav-menu-theme-label {
  color: var(--fg);
  font-size: 0.875rem;
  white-space: nowrap;
}
.nav-menu-theme .theme-switch-dots { gap: 0.375rem; }
.nav-menu-theme .theme-dot {
  width: 1.75rem;
  height: 1.75rem;
}
.nav-menu-theme .theme-dot::before {
  grid-area: 1 / 1;
  width: 1.25rem;
  height: 1.25rem;
}
.nav-menu-theme .theme-dot[aria-pressed="true"]::before {
  box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--fg);
}
.nav-menu-theme .theme-dot[aria-pressed="true"]::after {
  content: "";
  grid-area: 1 / 1;
  width: 0.3125rem;
  height: 0.5625rem;
  margin-top: -0.125rem;
  border: solid rgba(0, 0, 0, 0.72);
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}
/* Colour-theme button (owner 2026-10-15): in the header from 64rem, next to the icon links, one
   round dot carrying all five palettes (the --swatch-* tokens in THEMES order, a --line-strong
   ring) in a 44px target. It is a <details data-menu> like the user menu (common.js closes it on
   an outside click or Escape, one open at a time); its panel holds the same dots form as the menu
   row. Below 64rem there is no button, as with the light/dark toggle: the menu row covers it (a
   320px header has no room). The target's hover is square (--radius-sm); the dot stays round, as
   the palette dots do. */
.theme-pop {
  position: relative;
  display: none;
}
.theme-pop-toggle {
  display: grid;
  place-items: center;
  width: var(--tap);
  height: var(--tap);
  border-radius: var(--radius-sm);
  list-style: none;
  cursor: pointer;
  user-select: none;
  transition: background-color 0.15s ease;
}
.theme-pop-toggle::-webkit-details-marker { display: none; }
.theme-pop-toggle::marker { content: ""; }
.theme-pop-toggle::before {
  content: "";
  width: 1rem;
  height: 1rem;
  border-radius: 50%;
  background: conic-gradient(var(--swatch-salvei-roos) 0 20%, var(--swatch-lillakas) 0 40%, var(--swatch-hommikurand) 0 60%, var(--swatch-pojeng) 0 80%, var(--swatch-virsik) 0);
  box-shadow: 0 0 0 1px var(--line-strong);
  transition: transform 0.15s ease;
}
.theme-pop-toggle:hover,
.theme-pop[open] > .theme-pop-toggle { background: var(--surface-2); }
.theme-pop-toggle:hover::before { transform: scale(1.15); }
.site-nav .theme-pop-panel {
  position: absolute;
  top: calc(100% + 0.375rem);
  right: 0;
  z-index: 50;
  padding: 0.375rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-lg);
}
@media (min-width: 64rem) {
  .theme-pop { display: block; }
}

/* Light/dark ("Režiim", views/partials/mode-switch.eta, plan-2.0a §2b). Like the colour themes:
   on wide screens a square icon button in the header after the colour-theme button; on every
   width a "Režiim" row in the user menu (sun / moon / monitor for Hele / Tume / Seadme järgi as an
   icon-only .seg of submit buttons, the stored choice aria-pressed). The header form holds two buttons and shows the one that
   switches AWAY from the mode in effect: the moon ("Lülita tumedale režiimile") while light, the
   sun while dark, also when the device decides. The dark rules come in the same two forms as the token blocks (media + forced). */
.mode-switch { margin: 0; }
.mode-switch-bar {
  display: none;
  gap: var(--s-2);
}
.mode-toggle {
  display: grid;
  flex: none;
  place-items: center;
  width: var(--tap);
  height: var(--tap);
  margin: 0;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--fg-soft);
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.mode-toggle:hover {
  border-color: var(--line-strong);
  background: var(--surface-2);
  color: var(--fg);
}
.mode-icon {
  width: 1.125rem;
  height: 1.125rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.mode-toggle-to-light { display: none; }
@media (prefers-color-scheme: dark) {
  :root:not([data-mode="light"]) .mode-toggle-to-dark { display: none; }
  :root:not([data-mode="light"]) .mode-toggle-to-light { display: grid; }
}
:root[data-mode="dark"] .mode-toggle-to-dark { display: none; }
:root[data-mode="dark"] .mode-toggle-to-light { display: grid; }
/* The menu row stays on wide screens too: the header toggle only flips light/dark, so the row is
   where "Seadme järgi" is chosen again. */
@media (min-width: 64rem) {
  .mode-switch-bar { display: flex; }
}

@media (forced-colors: active) {
  .theme-dot::before,
  .theme-pop-toggle::before { forced-color-adjust: none; }
  .theme-dot[aria-pressed="true"] { outline: 2px solid Highlight; }
  .nav-menu-theme .theme-dot::after { forced-color-adjust: none; }
}

/* Style page (/welcome/style, views/welcome/style.eta): one card per palette (3 + 2 on wide
   screens), each carrying data-palette so it renders in its own palette (in the current
   light/dark mode). */
.style-picker-intro {
  max-width: 40rem;
  margin: 0;
  color: var(--fg-soft);
}

.style-cards {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s-4);
  margin: 0;
  padding: 0;
  list-style: none;
}
@media (min-width: 60rem) {
  .style-cards {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--s-5);
  }
}

.style-card[data-palette] {
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
  min-width: 0;
  margin: 0;
  padding: var(--s-4);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
  color: var(--fg);
  box-shadow: var(--shadow-sm);
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
@media (min-width: 40rem) {
  .style-card[data-palette] { padding: var(--s-5); }
}
.style-card[data-palette]:hover,
.style-card[data-palette]:focus-within {
  border-color: var(--line-strong);
  box-shadow: var(--shadow-lg);
}
.style-card-name {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2);
  margin: 0;
  font-size: 1.125rem;
}
.style-card-idea {
  margin: 0;
  color: var(--fg-soft);
  font-size: 0.9375rem;
}

/* Mini preview made of real components: a folder tile, a link, a muted line, a primary button. */
.style-preview {
  display: grid;
  grid-template-columns: 7.5rem minmax(0, 1fr);
  align-items: center;
  gap: var(--s-4);
  margin-top: auto;
  padding: var(--s-4);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--bg);
}
.style-preview-text {
  display: grid;
  justify-items: start;
  gap: var(--s-2);
  min-width: 0;
}
.style-preview-text .muted { font-size: 0.8125rem; }

.style-preview-tile > a {
  gap: 0;
  padding: var(--s-2) var(--s-3);
  border-color: transparent;
  border-radius: var(--radius);
  background: var(--stage);
  color: #ffffff;
}
.style-preview-tile > a::before { content: none; }
.style-preview-tile > a::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.58) 0%, rgba(0, 0, 0, 0.18) 50%, rgba(0, 0, 0, 0) 72%);
}
.style-preview-tile .folder-tile-name { font-size: 0.875rem; }
.style-preview-tile .muted { color: rgba(255, 255, 255, 0.86); font-size: 0.75rem; }

/* A soft "photo" drawn from the palette's own tokens: sky, a low warm sun, two hills.
   In dark mode the sun dims to a faint moon so nothing glows. */
.style-photo {
  --sun: color-mix(in srgb, var(--warn-bg) 60%, var(--surface));
  position: absolute;
  inset: 0;
  z-index: -2;
  background:
    radial-gradient(circle at 72% 30%, var(--sun) 0 8%, transparent 17%),
    radial-gradient(ellipse 75% 42% at 22% 100%, var(--accent-hover) 0 55%, transparent 76%),
    radial-gradient(ellipse 85% 48% at 88% 104%, var(--focus) 0 55%, transparent 76%),
    linear-gradient(to bottom, var(--accent-bg) 0%, var(--accent) 80%);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-mode="light"]) .style-photo { --sun: color-mix(in srgb, var(--muted) 40%, transparent); }
}
:root[data-mode="dark"] .style-photo { --sun: color-mix(in srgb, var(--muted) 40%, transparent); }

.style-card-form { margin: 0; }
.style-card-form .btn { width: 100%; }
/* The mode choice above the cards (Hele / Tume / Seadme järgi), sent with the chosen card. */
.style-mode { justify-self: start; max-width: 100%; }

/* 16. Timeline: "Kõik pildid" ---------------------------------------------- */

/* "/" (views/timeline/index.eta and _chunk.eta; plan-2.0a §5.4, §2a A). One section per month:
   a sticky h2, then (Päev) day h3s with their tiles, or (Kuu) one grid per month, or (Aasta) one
   section per year whose months fill one continuous grid. The view is data-view on .timeline.
   Phones: square tiles in columns with 2px gaps. From 44rem: justified rows, each tile growing
   by its aspect ratio --r (the ar-* classes) from a basis of --r × --tl-row, with a filler that
   keeps a day's last row at its natural size. */
.timeline { --tl-row: 12.5rem; }
/* The sticky month heading covers the top --tl-head-h of the window: every scroll to a target
   (a focused tile link, the no-JS #p<id> and #d-… jumps) stops below it (WCAG 2.4.11). On html,
   the scroll container, so it also applies to the links inside the tiles. Months and their
   headings (#m-…, #y-…, #h-… skip links) cancel it and land at the top, where the client's
   own jumps put them; the client's numeric scrollTo/scrollBy calls ignore both. */
html:has(.timeline) {
  --tl-head-h: 3rem;
  scroll-padding-top: calc(var(--tl-head-h) + 8px);
}
.timeline[data-view="kuu"] { --tl-row: 8.5rem; }
@media (min-width: 64rem) {
  .timeline { --tl-row: 13.75rem; }
  .timeline[data-view="kuu"] { --tl-row: 9.5rem; }
}

/* Phones: a tighter title row, so the photos start higher. */
@media (max-width: 39.99rem) {
  .timeline > .page-header { gap: var(--s-2) var(--s-4); }
  .timeline > .page-header + .tl-years { margin-top: calc(-1 * var(--s-2)); }
}

.tl-total {
  font-size: 1rem;
  font-weight: 400;
  letter-spacing: 0;
  white-space: nowrap;
}

/* The view switch "Aasta | Kuu | Päev": three links joined into one segmented control. It is
   the one joined group of controls (docs/UI.md "Spacing contract"): 2px between its links,
   allow-listed by class in test/ui-spacing.test.js and scripts/ui/spacing-lint.js. */
.seg {
  display: inline-flex;
  flex: none;
  gap: 2px;
  padding: 2px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
}
.seg > a {
  display: inline-flex;
  align-items: center;
  min-height: calc(var(--tap) - 6px);
  padding: 0 var(--s-3);
  border-radius: 3px;
  color: var(--fg-soft);
  font-size: 0.9375rem;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
  transition: background-color 0.15s ease, color 0.15s ease;
}
.seg > a:hover { background: var(--surface-2); color: var(--fg); }
.seg > a[aria-current="true"] { background: var(--accent); color: var(--accent-fg); }
.seg > a:focus-visible { outline-offset: -2px; }
/* The same look for submit buttons (the "Režiim" row of the user menu, plan-2.0a §2b), the
   chosen one aria-pressed. */
.seg > button {
  display: inline-flex;
  align-items: center;
  min-height: calc(var(--tap) - 6px);
  margin: 0;
  padding: 0 var(--s-3);
  border: 0;
  border-radius: 3px;
  background: transparent;
  color: var(--fg-soft);
  font-family: inherit;
  font-size: 0.9375rem;
  font-weight: 600;
  line-height: 1.2;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}
.seg > button:hover { background: var(--surface-2); color: var(--fg); }
.seg > button[aria-pressed="true"] { background: var(--accent); color: var(--accent-fg); }
.seg > button:focus-visible { outline-offset: -2px; }
/* Icon-only: the "Režiim" row of the user menu (sun, moon, monitor; named by aria-label and
   title), 30px squares. */
.seg-icons > button {
  justify-content: center;
  width: 1.875rem;
  min-height: 1.875rem;
  padding: 0;
}
.seg-icons .mode-icon {
  width: 1rem;
  height: 1rem;
  stroke-width: 1.8;
}

/* The year row: 2026 2025 … (links to each year's newest month), one line that scrolls sideways
   when it does not fit (owner 2026-10-16: no wrapped rows, no "·" separators). It bleeds to the
   window edges, so it scrolls edge to edge, while the first year's text lines up with the text
   above; 4px above and below keep a focused link's ring inside the scroll box. */
.tl-years ul {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  margin: calc(-1 * var(--s-3) - 4px) calc(-1 * var(--gutter)) -4px;
  padding: 4px calc(var(--gutter) - var(--s-2));
  overflow-x: auto;
  scrollbar-width: thin;
  list-style: none;
}
.tl-years li { display: inline-flex; flex: none; align-items: center; }
.tl-years a {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  padding: 0 var(--s-2);
  border-radius: var(--radius-sm);
  color: var(--accent-strong);
  font-weight: 550;
  font-variant-numeric: tabular-nums;
  text-decoration: none;
}
.tl-years a:hover { background: var(--surface-2); text-decoration: underline; }

/* Months (and years in Aasta). The heading sticks to the top while its section is in view (the
   site header is not sticky); days and tiles scrolled to keep clear of it (WCAG 2.4.11). */
.tl-month + .tl-month { margin-top: var(--s-6); }
.tl-year + .tl-year { margin-top: var(--s-5); }
.tl-month-head {
  position: sticky;
  top: 0;
  z-index: 3;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0 var(--s-2);
  margin: 0 calc(-1 * var(--gutter));
  padding: var(--s-3) var(--gutter) var(--s-2);
  background: var(--bg);
  font-size: 1.25rem;
}
.tl-month-head .muted {
  font-size: 0.9375rem;
  font-weight: 400;
  letter-spacing: 0;
}
.tl-month-head:focus { outline: none; }
.tl-month,
.tl-month-head { scroll-margin-top: calc(-1 * (var(--tl-head-h) + 8px)); }
/* Kuu and Aasta: the heading links one level down ("juuli 2025 ›" opens Päev at that month). */
.tl-drill {
  color: inherit;
  text-decoration: none;
}
.tl-drill:hover { color: inherit; text-decoration: underline; }
.tl-drill > span {
  color: var(--muted);
  font-weight: 400;
}

/* "Jäta vahele: järgmine kuu": hidden until focused, like .skip-link (clipped in place, so it
   never extends the page). */
.tl-skip {
  display: inline-block;
  margin: var(--s-1) 0;
  padding: var(--s-2) var(--s-3);
  border-radius: var(--radius-sm);
  background: var(--accent);
  color: var(--accent-fg);
  font-weight: 600;
  text-decoration: none;
}
.tl-skip:not(:focus) {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: 0;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.tl-day + .tl-day { margin-top: var(--s-5); }
[data-tl-chunk] > .tl-day:first-child,
[data-tl-chunk] > .tl-grid:first-child { margin-top: var(--s-2); }
.tl-day > h3 {
  margin: 0 0 var(--s-3);
  color: var(--fg);
  font-size: 1rem;
  font-weight: 600;
  letter-spacing: 0;
}
/* Fewer than 4 photos: the row sits closer to its heading. */
.tl-day.is-sparse > h3 { margin-bottom: var(--s-2); }
.tl-day-note { font-weight: 400; }
[data-tl-chunk] > .pager { margin-top: var(--s-4); }

/* Tiles. Phones: square, 3 columns (4 from 30rem); Kuu 4 (5 from 30rem). */
.tl-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 2px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.timeline[data-view="kuu"] .tl-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
@media (min-width: 30rem) {
  .tl-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .timeline[data-view="kuu"] .tl-grid { grid-template-columns: repeat(5, minmax(0, 1fr)); }
}
.tl-tile {
  position: relative;
  min-width: 0;
  margin: 0;
}
.tl-tile > a {
  position: relative;
  display: block;
  overflow: hidden;
  aspect-ratio: 1;
  border-radius: 2px;
  background: var(--surface-2);
  color: inherit;
  text-decoration: none;
}
.tl-tile img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.35s ease;
}
.tl-tile > a:focus-visible {
  z-index: 1;
  outline: 3px solid var(--focus);
  outline-offset: -3px;
  box-shadow: inset 0 0 0 5px var(--bg);
}
@media (hover: hover) {
  .tl-tile > a:hover img { transform: scale(1.035); }
}

/* From 44rem (the owner's "700px"): justified rows. */
@media (min-width: 44rem) {
  .tl-grid {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
  }
  .tl-grid::after {
    content: "";
    flex: 1e9 0 0;
  }
  .tl-tile { flex: var(--r) 1 calc(var(--r) * var(--tl-row)); }
  .tl-tile > a {
    aspect-ratio: var(--r);
    border-radius: 4px;
  }
}

/* Aspect-ratio classes: --r = 0.4 × 1.05^k for k = 0 … 41 (src/lib/format.js ratioIndex picks k;
   test/timeline-ui.test.js checks every value). */
.ar-0 { --r: 0.4000; }
.ar-1 { --r: 0.4200; }
.ar-2 { --r: 0.4410; }
.ar-3 { --r: 0.4631; }
.ar-4 { --r: 0.4862; }
.ar-5 { --r: 0.5105; }
.ar-6 { --r: 0.5360; }
.ar-7 { --r: 0.5628; }
.ar-8 { --r: 0.5910; }
.ar-9 { --r: 0.6205; }
.ar-10 { --r: 0.6516; }
.ar-11 { --r: 0.6841; }
.ar-12 { --r: 0.7183; }
.ar-13 { --r: 0.7543; }
.ar-14 { --r: 0.7920; }
.ar-15 { --r: 0.8316; }
.ar-16 { --r: 0.8731; }
.ar-17 { --r: 0.9168; }
.ar-18 { --r: 0.9626; }
.ar-19 { --r: 1.0108; }
.ar-20 { --r: 1.0613; }
.ar-21 { --r: 1.1144; }
.ar-22 { --r: 1.1701; }
.ar-23 { --r: 1.2286; }
.ar-24 { --r: 1.2900; }
.ar-25 { --r: 1.3545; }
.ar-26 { --r: 1.4223; }
.ar-27 { --r: 1.4934; }
.ar-28 { --r: 1.5681; }
.ar-29 { --r: 1.6465; }
.ar-30 { --r: 1.7288; }
.ar-31 { --r: 1.8152; }
.ar-32 { --r: 1.9060; }
.ar-33 { --r: 2.0013; }
.ar-34 { --r: 2.1013; }
.ar-35 { --r: 2.2064; }
.ar-36 { --r: 2.3167; }
.ar-37 { --r: 2.4326; }
.ar-38 { --r: 2.5542; }
.ar-39 { --r: 2.6819; }
.ar-40 { --r: 2.8160; }
.ar-41 { --r: 2.9568; }

/* "≈": a tile whose date is estimated (on a mixed day in Päev; every one in Kuu and Aasta). */
.tl-est {
  position: absolute;
  top: 0.3125rem;
  right: 0.3125rem;
  z-index: 1;
  display: grid;
  place-items: center;
  min-width: 1.375rem;
  height: 1.375rem;
  padding: 0 0.25rem;
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--fg);
  font-size: 0.9375rem;
  font-weight: 700;
  line-height: 1;
  box-shadow: 0 0 0 1px var(--line-strong), 0 1px 3px rgba(0, 0, 0, 0.25);
  pointer-events: none;
}

/* Aasta: small squares, the months of a year in one continuous grid. Each month is a .tl-run
   (display: contents, like its chunk and list), so the tiles become cells of the year's grid
   with no gap or heading between months; a month not loaded yet and a "Näita veel" pager
   take a whole row. */
.tl-year-grid {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 2px;
  margin-top: var(--s-2);
}
@media (min-width: 30rem) {
  .tl-year-grid { grid-template-columns: repeat(8, minmax(0, 1fr)); }
}
@media (min-width: 44rem) {
  .tl-year-grid {
    grid-template-columns: repeat(auto-fill, minmax(5.25rem, 1fr));
    gap: 3px;
  }
}
.tl-year-grid > .tl-run,
.tl-year-grid [data-tl-chunk],
.tl-year-grid .tl-grid { display: contents; }
.tl-year-grid .tl-grid::after { content: none; }
.tl-year-grid .tl-tile > a {
  aspect-ratio: 1;
  border-radius: 2px;
}
.tl-year-grid .tl-est {
  top: 2px;
  right: 2px;
  min-width: 1.125rem;
  height: 1.125rem;
  font-size: 0.8125rem;
}
.tl-year-grid .pager { grid-column: 1 / -1; margin-top: var(--s-2); }

/* A month not loaded yet (a shell): a muted block pattern drawn from the tokens, about one row
   of tiles high. Without JS it holds the "Näita <kuu> pilte" link; the client sets an estimated
   height and data-state="loading" (with a "Laen…" .tl-loading) while it fetches. */
.tl-month-body,
.tl-year-grid > .tl-run { --tl-cols: 3; }
.timeline[data-view="kuu"] .tl-month-body { --tl-cols: 4; }
.tl-year-grid > .tl-run { --tl-cols: 6; }
@media (min-width: 30rem) {
  .tl-month-body { --tl-cols: 4; }
  .timeline[data-view="kuu"] .tl-month-body { --tl-cols: 5; }
  .tl-year-grid > .tl-run { --tl-cols: 8; }
}
.tl-month-body:is([data-state="empty"], [data-state="loading"]),
.tl-year-grid > .tl-run:is([data-state="empty"], [data-state="loading"]) {
  position: relative;
  min-height: calc((100vw - 2 * var(--gutter)) / var(--tl-cols));
  margin-top: var(--s-2);
  padding: var(--s-3);
  border-radius: 4px;
  background-color: var(--surface-2);
  /* Each cell is a tile plus the 2px gap on its right and bottom, so the columns match the
     grid's and the last column's gap falls outside the box: the pattern reaches both edges. */
  background-image:
    linear-gradient(to left, var(--bg) 2px, transparent 2px),
    linear-gradient(to top, var(--bg) 2px, transparent 2px);
  background-size: calc((100% + 2px) / var(--tl-cols)) calc((100vw - 2 * var(--gutter) + 2px) / var(--tl-cols));
}
.tl-year-grid > .tl-run:is([data-state="empty"], [data-state="loading"]) {
  display: block;
  grid-column: 1 / -1;
  margin-top: 0;
}
@media (min-width: 44rem) {
  .tl-month-body:is([data-state="empty"], [data-state="loading"]) {
    min-height: var(--tl-row);
    background-image:
      linear-gradient(to left, var(--bg) 4px, transparent 4px),
      linear-gradient(to top, var(--bg) 4px, transparent 4px);
    background-size: calc((100% + 4px) / 4) calc(var(--tl-row) + 4px);
  }
  .tl-year-grid > .tl-run:is([data-state="empty"], [data-state="loading"]) {
    min-height: 5.25rem;
    background-image:
      linear-gradient(to left, var(--bg) 3px, transparent 3px),
      linear-gradient(to top, var(--bg) 3px, transparent 3px);
    background-size: 5.6rem 5.6rem;
  }
}
.tl-month-body[data-state="empty"] > p { margin: 0; }
.tl-month-body[data-state="empty"] > p > a,
.tl-year-grid > .tl-run[data-state="empty"] > a {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  padding: 0 var(--s-3);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--accent-strong);
  font-weight: 550;
  text-decoration: none;
  box-shadow: var(--shadow-sm);
}
.tl-month-body[data-state="empty"] > p > a:hover,
.tl-year-grid > .tl-run[data-state="empty"] > a:hover { text-decoration: underline; }
.tl-loading {
  position: absolute;
  top: var(--s-6);
  left: 50%;
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  margin: 0;
  padding: var(--s-2) var(--s-4);
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--fg-soft);
  font-size: 0.9375rem;
  box-shadow: var(--shadow-sm);
  transform: translateX(-50%);
}
:is(.tl-month-body, .tl-run)[data-state="loading"] { animation: tl-pulse 1.4s ease-in-out infinite; }
@keyframes tl-pulse { 50% { opacity: 0.55; } }
/* A month or "Näita veel" that failed to load (public/js/timeline.js): the message and
   "Proovi uuesti" (a .notice-error row; a shell keeps its no-JS link above it). */
.tl-failed {
  flex-wrap: wrap;
  align-items: center;
  margin: var(--s-2) 0 0;
}
.pager > .tl-failed { margin: 0; }
/* While the client restores a photo deeper in its month than the server rendered (or loads the
   month a year link jumps to), the months are hidden and "Laen…" stands in their place, so the
   page does not flash its top before it lands on the photo. */
.timeline.tl-restoring > :is(.tl-months, .tl-years-list) { visibility: hidden; }
.timeline > .tl-loading {
  position: static;
  width: fit-content;
  margin: var(--s-6) auto 0;
  transform: none;
}

/* "← Uuemad kuud · Kõik pildid · Vanemad kuud →" under a month opened with ?m= (no-JS paging). */
.tl-month-nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0 var(--s-4);
  margin-top: var(--s-3);
}
.tl-month-nav a {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  min-height: var(--tap);
  color: var(--accent-strong);
  font-weight: 550;
  text-decoration: none;
}
.tl-month-nav a:hover { text-decoration: underline; }

@media (prefers-reduced-motion: reduce) {
  .tl-tile > a:hover img { transform: none; }
  :is(.tl-month-body, .tl-run)[data-state="loading"] { animation: none; }
}
@media (forced-colors: active) {
  .tl-est {
    border: 1px solid CanvasText;
    background: Canvas;
    color: CanvasText;
    forced-color-adjust: none;
  }
  .tl-month-body:is([data-state="empty"], [data-state="loading"]),
  .tl-year-grid > .tl-run:is([data-state="empty"], [data-state="loading"]) { border: 1px dashed CanvasText; }
  .seg > a[aria-current="true"] { outline: 2px solid Highlight; outline-offset: -2px; }
  .seg > button[aria-pressed="true"] { outline: 2px solid Highlight; outline-offset: -2px; }
}
@media print {
  .seg,
  .tl-skip,
  .tl-years,
  .tl-month-nav { display: none !important; }
  .tl-month-head { position: static; }
}

/* 17. Environments (keskkonnad): Haldus -> Keskkonnad ----------------------- */

/* The list: the name is the row's link. */
.env-table-name { font-weight: 550; }

/* "Uus keskkond": who sees it, one row per person (the name, a role select with "Ei lisa"), in
   the card's own flow without a second frame. One column: the names keep their line. */
fieldset.env-people {
  padding: 0;
  border: 0;
}
fieldset.env-people > legend {
  padding: 0;
  margin-bottom: 0.375rem;
}
.env-people > .hint { margin: 0; }
.env-people-list {
  display: grid;
  gap: var(--s-2);
}
.env-person {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(8rem, 10rem);
  align-items: center;
  gap: var(--s-3);
}
.env-person > label {
  min-width: 0;
  overflow-wrap: anywhere;
  font-weight: 500;
}

/* One environment's people: the role select and "Muuda rolli" side by side, "Eemalda" after
   them (on its own line when the cell is narrow). */
.env-members .actions { align-items: center; }
.member-role {
  flex-wrap: nowrap;
  align-items: center;
}
.member-role > .field { flex: 0 1 10rem; }
.env-member-email { font-size: 0.875rem; }

/* Haldus -> Kasutajad: each person's environments with their role there. */
.user-envs {
  display: grid;
  gap: 0.125rem;
  margin: 0;
  padding: 0;
  list-style: none;
}
