/* start.lu stylesheet (the project is Gazelle; the site is start.lu).

   Everything is drawn from tokens, and four attributes on <html> choose them:
     data-palette  colours          data-font   type
     data-shape    boxes and lines  data-mode   light, dark or the device's choice
   The same attributes work on any element, which is how the look picker draws its
   samples: a swatch carries its own palette, a sample box its own shape. */

/* ======================================================================
   0. The brand, which is not a look.

      A visitor picks the look; nobody picks the logo. These four are start.lu's
      own colours - the flag's red and blue, the near-black the wordmark is written
      in and the paper it sits on - and they stay put whatever palette is chosen,
      so the mark in the top bar is the same mark on every one of the sixteen.
      The Mono look, which has no colour in it anywhere, is the single exception
      and takes the mark in grey.
   ====================================================================== */

:root {
    --brand-red: #ED2939;
    --brand-blue: #00A1DE;
    --brand-ink: #1E2229;
    --brand-paper: #ffffff;
}

/* ======================================================================
   1. Colour palettes. Each defines a light set (--l-*) and a dark set (--d-*);
      section 2 decides which one is live. --accent-ink is whatever reads on top
      of --accent, which is not always white: Slate's amber is light enough that
      it needs dark ink under it, the way Mono's white accent does.
   ====================================================================== */

/* The house look, built out of the brand: the flag's red as the accent and the
   brand's near-black as the ink. The red is taken a few shades darker than #ED2939
   for anything written in it - the mark's own red clears only 4.2:1 on white, and a
   link has to clear 4.5:1 on every ground a card can have under it, which includes
   --surface-2, the card the Flat shape draws. The mark keeps the brighter red,
   because a logo is not text. */
:root, [data-palette="startlu"] {
    --l-bg: #f4f5f7;  --l-surface: #ffffff; --l-surface-2: #eaecf0; --l-ink: #1e2229; --l-ink-soft: #5b6270;
    --l-line: #dcdfe5; --l-accent: #d01a27; --l-accent-ink: #ffffff; --l-accent-soft: #fce5e7;
    --d-bg: #14171c;  --d-surface: #1e2229; --d-surface-2: #272c35; --d-ink: #edeff2; --d-ink-soft: #9aa1ae;
    --d-line: #323842; --d-accent: #ff5a63; --d-accent-ink: #2a0508; --d-accent-soft: #3b1f23;
}

[data-palette="sand"] {
    --l-bg: #f5f0e6;  --l-surface: #fffdf8; --l-surface-2: #efe7d8; --l-ink: #2a2218; --l-ink-soft: #7b6a55;
    --l-line: #e1d5c1; --l-accent: #c0561e; --l-accent-ink: #ffffff; --l-accent-soft: #f6e2d3;
    --d-bg: #191512;  --d-surface: #231d17; --d-surface-2: #2d251e; --d-ink: #f0e7d9; --d-ink-soft: #b4a48f;
    --d-line: #3b3128; --d-accent: #e98d57; --d-accent-ink: #1b1108; --d-accent-soft: #3f2a1b;
}

[data-palette="forest"] {
    --l-bg: #f0f4ef;  --l-surface: #ffffff; --l-surface-2: #e4ece3; --l-ink: #151f18; --l-ink-soft: #56675a;
    --l-line: #d1dcd1; --l-accent: #1d7a4b; --l-accent-ink: #ffffff; --l-accent-soft: #dbeee2;
    --d-bg: #0e1411;  --d-surface: #151e19; --d-surface-2: #1b261f; --d-ink: #e2ede5; --d-ink-soft: #93a89a;
    --d-line: #27352c; --d-accent: #4fca85; --d-accent-ink: #06130b; --d-accent-soft: #15301e;
}

[data-palette="ocean"] {
    --l-bg: #edf4f6;  --l-surface: #ffffff; --l-surface-2: #dfecef; --l-ink: #112128; --l-ink-soft: #4f6a73;
    --l-line: #cddde2; --l-accent: #0e7b93; --l-accent-ink: #ffffff; --l-accent-soft: #d4edf2;
    --d-bg: #0c1417;  --d-surface: #131f28; --d-surface-2: #192933; --d-ink: #e1edf1; --d-ink-soft: #8fa9b2;
    --d-line: #23353e; --d-accent: #43bfda; --d-accent-ink: #05161b; --d-accent-soft: #10343f;
}

[data-palette="berry"] {
    --l-bg: #f8f1f5;  --l-surface: #ffffff; --l-surface-2: #f0e4ec; --l-ink: #23151e; --l-ink-soft: #6f5567;
    --l-line: #e4d1de; --l-accent: #a52577; --l-accent-ink: #ffffff; --l-accent-soft: #f5dbeb;
    --d-bg: #160f13;  --d-surface: #1f151f; --d-surface-2: #2a1c29; --d-ink: #f1e4ec; --d-ink-soft: #b293a9;
    --d-line: #3a2937; --d-accent: #ea6fba; --d-accent-ink: #1b0b16; --d-accent-soft: #3f1f39;
}

[data-palette="slate"] {
    --l-bg: #f1f1f3;  --l-surface: #ffffff; --l-surface-2: #e7e7eb; --l-ink: #1a1b1e; --l-ink-soft: #62646b;
    --l-line: #d6d7dc; --l-accent: #d87706; --l-accent-ink: #241a00; --l-accent-soft: #fcead0;
    --d-bg: #121315;  --d-surface: #1a1b1e; --d-surface-2: #222327; --d-ink: #e9e9eb; --d-ink-soft: #9d9fa7;
    --d-line: #2f3035; --d-accent: #f6a01c; --d-accent-ink: #1a1000; --d-accent-soft: #3c2a0a;
}

[data-palette="midnight"] {
    --l-bg: #edeefa;  --l-surface: #ffffff; --l-surface-2: #e2e4f6; --l-ink: #181a33; --l-ink-soft: #5b5e86;
    --l-line: #d2d5ee; --l-accent: #4d45e3; --l-accent-ink: #ffffff; --l-accent-soft: #dfdffb;
    --d-bg: #0a0b19;  --d-surface: #12142a; --d-surface-2: #191c36; --d-ink: #e7e8fb; --d-ink-soft: #9b9fc9;
    --d-line: #262a4d; --d-accent: #8e88f7; --d-accent-ink: #0a0a1f; --d-accent-soft: #232450;
}

[data-palette="mono"] {
    --l-bg: #ffffff;  --l-surface: #ffffff; --l-surface-2: #f1f1f1; --l-ink: #000000; --l-ink-soft: #4d4d4d;
    --l-line: #bfbfbf; --l-accent: #000000; --l-accent-ink: #ffffff; --l-accent-soft: #e6e6e6;
    --d-bg: #000000;  --d-surface: #0c0c0c; --d-surface-2: #191919; --d-ink: #ffffff; --d-ink-soft: #a8a8a8;
    --d-line: #3a3a3a; --d-accent: #ffffff; --d-accent-ink: #000000; --d-accent-soft: #262626;
}

/* ======================================================================
   2. Mode. The live tokens (--bg, --ink ...) come from the light set unless the
      element, or the device on its behalf, asks for the dark one. Semantic colours
      (ok, bad, warning, lock) are palette-independent and set per mode.
   ====================================================================== */

:root, [data-palette] {
    --bg: var(--l-bg); --surface: var(--l-surface); --surface-2: var(--l-surface-2);
    --ink: var(--l-ink); --ink-soft: var(--l-ink-soft); --line: var(--l-line);
    --accent: var(--l-accent); --accent-ink: var(--l-accent-ink); --accent-soft: var(--l-accent-soft);
    --shadow-rgb: 20 30 50;
    --ok-bg: #e4f5ea; --ok-ink: #1a6b3a; --bad-bg: #fdecea; --bad-ink: #a12d20;
    --warn-bg: #fdf3d8; --warn-ink: #7f5a00; --danger: #c0392b; --lock: #b08600;
}

[data-mode="dark"] {
    --bg: var(--d-bg); --surface: var(--d-surface); --surface-2: var(--d-surface-2);
    --ink: var(--d-ink); --ink-soft: var(--d-ink-soft); --line: var(--d-line);
    --accent: var(--d-accent); --accent-ink: var(--d-accent-ink); --accent-soft: var(--d-accent-soft);
    --shadow-rgb: 0 0 0;
    --ok-bg: #14301f; --ok-ink: #7fd39b; --bad-bg: #331917; --bad-ink: #f0928a;
    --warn-bg: #3a2e0c; --warn-ink: #f2cd6a; --danger: #e05c4c; --lock: #d9b13a;
}

@media (prefers-color-scheme: dark) {
    :root:not([data-mode="light"]), [data-palette]:not([data-mode="light"]) {
        --bg: var(--d-bg); --surface: var(--d-surface); --surface-2: var(--d-surface-2);
        --ink: var(--d-ink); --ink-soft: var(--d-ink-soft); --line: var(--d-line);
        --accent: var(--d-accent); --accent-ink: var(--d-accent-ink); --accent-soft: var(--d-accent-soft);
        --shadow-rgb: 0 0 0;
        --ok-bg: #14301f; --ok-ink: #7fd39b; --bad-bg: #331917; --bad-ink: #f0928a;
        --warn-bg: #3a2e0c; --warn-ink: #f2cd6a; --danger: #e05c4c; --lock: #d9b13a;
    }
    :root:not([data-mode="light"]) { color-scheme: dark; }
}

:root { color-scheme: light; }
:root[data-mode="dark"] { color-scheme: dark; }

/* ======================================================================
   3. Shapes: corners, lines, fills and shadows for every box on the page.

      A shape also says how the controls inside a card are drawn, because a fill
      that works under one shape disappears under another: a white button reads
      clearly inside an outlined card and vanishes on the white card Soft draws.
      So --button-bg and --field-bg are picked per shape against the --card-bg they
      sit on, always from the palette in play rather than from white, and
      --button-hover is kept a step away from --button-bg so hovering shows.

      Controls also keep their own edge, --control-border-w and --control-border-c,
      instead of the card's. A card may go without a line; a button may not, or
      there is nothing to tell the visitor where it is. The edge is --ink-soft,
      which is the palette's own muted ink and clears 3:1 against every surface
      these palettes offer, so the button stays findable in all sixteen of them.
      Only Outline and Brutal, which already draw a heavy --ink line, keep theirs.
   ====================================================================== */

:root, [data-shape="soft"] {
    --radius: 14px; --radius-sm: 9px; --radius-tile: 8px; --radius-pill: 999px;
    --border-w: 1px; --border-c: var(--line);
    --card-bg: var(--surface);
    --shadow: 0 1px 2px rgb(var(--shadow-rgb) / .05), 0 10px 30px rgb(var(--shadow-rgb) / .06);
    --hover: var(--surface-2);
    --lift: translateY(-1px);
    --button-bg: var(--surface-2);
    --button-hover: var(--accent-soft);
    --field-bg: var(--surface);
    --control-border-w: 1px;
    --control-border-c: var(--ink-soft);
}

[data-shape="sharp"] {
    --radius: 0px; --radius-sm: 0px; --radius-tile: 0px; --radius-pill: 0px;
    --border-w: 1px; --border-c: var(--line);
    --card-bg: var(--surface);
    --shadow: none;
    --hover: var(--surface-2);
    --lift: none;
    --button-bg: var(--surface-2);
    --button-hover: var(--accent-soft);
    --field-bg: var(--surface);
    --control-border-w: 1px;
    --control-border-c: var(--ink-soft);
}

[data-shape="round"] {
    --radius: 26px; --radius-sm: 16px; --radius-tile: 999px; --radius-pill: 999px;
    --border-w: 0px; --border-c: transparent;
    --card-bg: var(--surface);
    --shadow: 0 2px 6px rgb(var(--shadow-rgb) / .05), 0 18px 46px rgb(var(--shadow-rgb) / .10);
    --hover: var(--accent-soft);
    --lift: translateY(-1px);
    --button-bg: var(--accent-soft);
    --button-hover: color-mix(in srgb, var(--accent-soft) 78%, var(--accent));
    --field-bg: var(--surface-2);
    --control-border-w: 1px;
    --control-border-c: var(--ink-soft);
}

[data-shape="outline"] {
    --radius: 8px; --radius-sm: 6px; --radius-tile: 5px; --radius-pill: 6px;
    --border-w: 2px; --border-c: var(--ink);
    --card-bg: var(--bg);
    --shadow: none;
    --hover: var(--surface-2);
    --lift: none;
    --button-bg: var(--surface);
    --button-hover: var(--accent-soft);
    --field-bg: var(--surface);
    --control-border-w: var(--border-w);
    --control-border-c: var(--border-c);
}

[data-shape="flat"] {
    --radius: 12px; --radius-sm: 8px; --radius-tile: 8px; --radius-pill: 999px;
    --border-w: 0px; --border-c: transparent;
    --card-bg: var(--surface-2);
    --shadow: none;
    --hover: var(--accent-soft);
    --lift: none;
    --button-bg: var(--accent-soft);
    --button-hover: color-mix(in srgb, var(--accent-soft) 78%, var(--accent));
    --field-bg: var(--surface);
    --control-border-w: 1px;
    --control-border-c: var(--ink-soft);
}

[data-shape="brutal"] {
    --radius: 0px; --radius-sm: 0px; --radius-tile: 0px; --radius-pill: 0px;
    --border-w: 2px; --border-c: var(--ink);
    --card-bg: var(--surface);
    --shadow: 5px 5px 0 var(--ink);
    --hover: var(--accent-soft);
    --lift: translate(-1px, -1px);
    --button-bg: var(--surface-2);
    --button-hover: var(--accent-soft);
    --field-bg: var(--surface);
    --control-border-w: var(--border-w);
    --control-border-c: var(--border-c);
}

/* ======================================================================
   4. Type.
   ====================================================================== */

:root, [data-font="system"] {
    --font: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    --font-size: 0.9375rem; --head-weight: 700; --head-tracking: .06em; --head-case: uppercase; --head-size: 0.78125rem;
}
[data-font="poppins"] { --font: "Poppins", system-ui, sans-serif; --font-size: 0.96875rem; --head-weight: 600; --head-tracking: .08em; }
[data-font="inter"]   { --font: "Inter", system-ui, sans-serif; }
[data-font="nunito"]  { --font: "Nunito", system-ui, sans-serif; --font-size: 0.96875rem; --head-weight: 800; }
[data-font="grotesk"] { --font: "Space Grotesk", system-ui, sans-serif; --head-tracking: .04em; }
[data-font="lora"]    { --font: "Lora", Georgia, serif; --font-size: 0.96875rem; --head-case: none; --head-tracking: 0; --head-size: 0.9375rem; }
[data-font="georgia"] { --font: Georgia, "Iowan Old Style", "Times New Roman", serif; --font-size: 0.96875rem; --head-case: none; --head-tracking: 0; --head-size: 0.9375rem; }
[data-font="mono"]    { --font: ui-monospace, "Cascadia Code", Consolas, "SF Mono", Menlo, monospace; --font-size: 0.84375rem; --head-tracking: .04em; }

/* ======================================================================
   5. Base, and the bit of character on top of it

      Two things keep the portal from being merely tidy. Hover and press use a
      spring rather than a straight ease, so a tile answers back when you touch it;
      and every category carries a hue of its own, turned round from the palette's
      accent, so the board reads as a set of neighbourhoods instead of one blue
      wall. Both are drawn from the palette in play - a palette with no hue in it,
      Mono, has nothing to turn and stays black and white.
   ====================================================================== */

* { box-sizing: border-box; }

:root {
    /* Overshoots and settles: the difference between moving and being nudged. */
    --ease-spring: cubic-bezier(.34, 1.56, .64, 1);
    --ease-out: cubic-bezier(.2, .8, .3, 1);
    --chip-turn: 0deg;
}

/* One turn of the colour wheel per category, spread far enough apart to tell
   neighbours from each other and stopping short of a full circle. */
[data-tint="0"] { --chip-turn: 0deg; }
[data-tint="1"] { --chip-turn: 45deg; }
[data-tint="2"] { --chip-turn: 95deg; }
[data-tint="3"] { --chip-turn: 150deg; }
[data-tint="4"] { --chip-turn: 215deg; }
[data-tint="5"] { --chip-turn: 290deg; }

html { background: var(--bg); }

body {
    margin: 0; min-height: 100vh;
    color: var(--ink);
    font: var(--font-size)/1.5 var(--font);
    /* A wash of the accent at the top of the page, fading into the ground, with a
       soft bloom off to one side so the ground has somewhere to catch the light. */
    background:
        var(--bg)
        radial-gradient(60rem 22rem at 88% -6%, var(--accent-soft), transparent 70%) no-repeat,
        linear-gradient(180deg, var(--accent-soft), var(--bg) 380px) no-repeat;
    -webkit-font-smoothing: antialiased;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

h1, h2, h3 { line-height: 1.25; }

.sr-only {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* Every box on the page: columns, widgets, sheets, panels, cards. */
.column, .widget, .sheet, .panel, .edit-column, .page-card a, .choice {
    background: var(--card-bg);
    border: var(--border-w) solid var(--border-c);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
}

/* Small uppercase labels: column titles, widget titles, table heads. */
.column-head, .widget-head, .table th, .picker-axis legend, .adslot-empty {
    font-size: var(--head-size);
    font-weight: var(--head-weight);
    letter-spacing: var(--head-tracking);
    text-transform: var(--head-case);
    color: var(--ink-soft);
}

/* ======================================================================
   6. Chrome: top bar, page, footer, flash
   ====================================================================== */

.topbar {
    display: flex; align-items: center; justify-content: space-between; gap: 16px;
    padding: 10px 20px;
    background: color-mix(in srgb, var(--surface) 86%, transparent);
    backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
    border-bottom: var(--border-w) solid var(--border-c);
    position: sticky; top: 0; z-index: 20;
}

/* The brand: the mark, the wordmark, and the tagline when there is room for it. Together
   they are the logo - the flag as a tile, then "start" in the page's ink and ".lu" in the
   brand red - so they keep the brand's own colours rather than the palette's, which is the
   whole point of a logo. Mono is the one exception: it has no colour anywhere, and takes
   both the tile and the ".lu" in grey and in its own ink. */
.brand { display: flex; align-items: center; gap: 10px; min-width: 0; color: var(--ink); }
.brand:hover { text-decoration: none; }

.brand-logo {
    display: block; width: 34px; height: 34px; flex: 0 0 auto;
    border-radius: 7.5px;
    box-shadow: 0 3px 10px rgb(var(--shadow-rgb) / .22);
    transition: transform .25s var(--ease-spring);
}
.brand:hover .brand-logo { transform: rotate(-6deg) scale(1.08); }
[data-palette="mono"] .brand-logo { filter: grayscale(1); }

/* Poppins Bold is the wordmark's weight; the faces a visitor can put in its place
   are asked for their bold too, rather than a heavier one they may not have. */
.brand-word { font-weight: 700; font-size: 1.2rem; letter-spacing: -.03em; line-height: 1; white-space: nowrap; }
.brand-tld { color: var(--brand-red); }
[data-palette="mono"] .brand-tld { color: var(--ink); }

.brand-tagline {
    color: var(--ink-soft); font-size: 0.8125rem; font-weight: 500; white-space: nowrap;
    padding-left: 10px; border-left: 1px solid var(--line);
    overflow: hidden; text-overflow: ellipsis;
}
/* The tagline gives way before anything else in the bar, and it goes whole: the mark,
   the wordmark and the nav all have somewhere to be, and a tagline cut down to
   "Lëtzebuerger Star..." says less than none at all. Without script it goes at a width
   guessed in advance. With it the bar measures itself instead (portal.js, 9) and marks
   itself topbar-fit, which takes that guess back: the tagline is then kept at any width
   where the whole of it fits and dropped the moment it does not. */
@media (max-width: 900px) { .brand-tagline { display: none; } }
.topbar-fit .brand-tagline { display: block; }
.topbar-fit.topbar-tight .brand-tagline { display: none; }

/* On a phone the bar is one tight line whatever the measurement says. */
.is-mobile .brand-tagline { display: none; }

.topbar-nav { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.topbar-nav a, .topbar-nav .link-button {
    display: inline-flex; align-items: center; gap: 7px;
    padding: 6px 11px; border-radius: var(--radius-pill);
    color: var(--ink-soft); font-size: 0.875rem;
}
.topbar-nav a:hover, .topbar-nav .link-button:hover { color: var(--ink); background: var(--hover); text-decoration: none; }
.topbar-nav .cta { background: var(--accent); color: var(--accent-ink); font-weight: 600; }
.topbar-nav .cta:hover { background: var(--accent); color: var(--accent-ink); opacity: .9; }
.nav-flag { font-weight: 600; color: var(--ink); }

/* On a phone the bar is one line and stays one line. The brand keeps the left; the look,
   the light/dark button and the three languages keep the right. The links that merely
   lead somewhere - speciality pages, the customise page, the moderation queue, the
   invitation to register - are dropped, because every one of them is in the "More" panel
   of the mobile board as well and none of them is worth a second row of chrome above a
   start page. What survives is one account action, sign in or sign out, which is the one
   thing that panel does not carry on every page. Under 380px the wordmark goes too and
   the mark carries the brand on its own. */
@media (max-width: 700px) {
    .topbar { flex-wrap: nowrap; gap: 8px; padding: 8px 10px; }
    .topbar .brand { min-width: 0; gap: 8px; overflow: hidden; }
    .brand-word { font-size: 1.0625rem; }

    /* The nav never gives way: whatever has to shrink, shrinks on the brand's side. */
    .topbar-nav { flex: 0 0 auto; flex-wrap: nowrap; gap: 4px; }
    .topbar-nav .nav-page, .topbar-nav .nav-flag { display: none; }
    .topbar-nav a, .topbar-nav .link-button { padding: 5px 9px; font-size: 0.8125rem; }

    /* The look picker drops its word and keeps its dot, which is the sample itself. */
    .topbar-nav .nav-look { padding: 6px; }
    .nav-look-word { display: none; }

    .nav-mode { width: 28px; height: 28px; font-size: 0.875rem; }
    .langswitch .lang { padding: 5px 7px; font-size: 0.6875rem; }
}

@media (max-width: 359px) {
    .topbar .brand-word { display: none; }
}

.nav-look-dot {
    width: 12px; height: 12px; border-radius: 50%;
    background: conic-gradient(var(--accent) 0 50%, var(--accent-soft) 50% 100%);
    border: 1px solid var(--line);
}

/* Light and dark. One button rather than a menu, because there are only two answers
   a visitor can mean by asking; the third setting, "follows your device", stays on
   the look picker where it can be explained. */
.nav-mode {
    display: inline-grid; place-items: center; width: 30px; height: 30px;
    border: var(--control-border-w) solid var(--control-border-c); border-radius: var(--radius-pill);
    background: var(--button-bg); color: var(--ink); cursor: pointer; font-size: 0.9375rem; line-height: 1;
    transition: background-color .14s var(--ease-out), transform .18s var(--ease-spring);
}
.nav-mode:hover { background: var(--button-hover); }
.nav-mode:active { transform: scale(.94); }

/* All three languages at once. Nobody should have to open a menu to find out whether
   their language is offered at all, and three two-letter labels fit where a menu would. */
.langswitch {
    display: inline-flex; margin: 0; overflow: hidden;
    border: var(--control-border-w) solid var(--control-border-c); border-radius: var(--radius-pill);
    background: var(--button-bg);
}
.langswitch .lang {
    border: 0; padding: 5px 9px; min-height: 28px;
    background: none; color: var(--ink-soft); cursor: pointer;
    font: inherit; font-size: 0.75rem; font-weight: 600; letter-spacing: .03em;
    transition: background-color .14s var(--ease-out), color .14s var(--ease-out);
}
.langswitch .lang + .lang { border-left: 1px solid var(--control-border-c); }
.langswitch .lang:hover { background: var(--button-hover); color: var(--ink); }
.langswitch .lang-on { background: var(--accent); color: var(--accent-ink); }
.langswitch .lang-on:hover { background: var(--accent); color: var(--accent-ink); opacity: .92; }

.page { padding: 20px; max-width: 1680px; margin: 0 auto; }

.sitefoot {
    display: flex; gap: 18px; justify-content: center; flex-wrap: wrap;
    padding: 28px 20px; color: var(--ink-soft); font-size: 0.8125rem;
}
.sitefoot-brand strong { color: var(--ink); font-weight: 700; }
.sitefoot a { color: var(--ink-soft); }
.sitefoot a:hover { color: var(--ink); }

.flash {
    max-width: 1680px; margin: 12px auto 0; padding: 10px 14px;
    border-radius: var(--radius-sm); font-size: 0.875rem;
}
.flash-ok { background: var(--ok-bg); color: var(--ok-ink); }
.flash-bad { background: var(--bad-bg); color: var(--bad-ink); }

/* ======================================================================
   7. Desktop board: four fixed columns, three of links and the widget column
   ====================================================================== */

.board {
    display: grid; gap: 16px; align-items: start;
    grid-template-columns: repeat(4, minmax(0, 1fr));
}

.board-col { display: grid; gap: 16px; align-content: start; min-width: 0; }

/* The four columns hold as long as they stay readable: below that, two, then one.
   The widget column is the last child either way, so it lands under the links. */
@media (max-width: 1080px) {
    .board { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
}
@media (max-width: 640px) {
    .board { grid-template-columns: minmax(0, 1fr); }
}

.column { padding: 12px 12px 14px; }

.column-head {
    margin: 0 0 8px; padding-bottom: 8px;
    border-bottom: 1px solid var(--line);
    display: flex; align-items: center; gap: 9px;
}

.column-icon {
    display: grid; place-items: center; width: 22px; height: 22px; flex: 0 0 auto;
    border-radius: var(--radius-tile);
    background: var(--accent); color: var(--accent-ink);
    font-size: 0.6875rem; letter-spacing: 0; font-family: var(--font);
    filter: hue-rotate(var(--chip-turn));
    transition: transform .25s var(--ease-spring);
}
.column:hover .column-icon { transform: rotate(-6deg) scale(1.1); }

.column-empty, .widget-empty { color: var(--ink-soft); font-size: 0.875rem; margin: 6px 0; }

.tiles { list-style: none; margin: 0; padding: 0; display: grid; gap: 1px; }

/* .tile-face is the same tile with nothing behind it: the link editor's preview,
   which has to look exactly like the board but must not take the visitor there. */
.tile > a, .tile > .tile-face {
    display: flex; align-items: center; gap: 9px;
    padding: 6px 8px; margin: 0 -2px;
    border-radius: var(--radius-tile);
    color: var(--ink);
    transition: background-color .14s var(--ease-out), transform .18s var(--ease-spring);
}
.tile > a:hover { background: var(--hover); text-decoration: none; transform: var(--lift); }
.tile > a:active { transform: scale(.985); }

.tile-icon {
    display: grid; place-items: center; flex: 0 0 26px; height: 26px;
    border-radius: var(--radius-tile);
    background: var(--accent-soft); color: var(--accent);
    font-size: 0.78125rem; font-weight: 700;
    filter: hue-rotate(var(--chip-turn));
    transition: transform .22s var(--ease-spring);
}
.tile > a:hover .tile-icon { transform: rotate(-7deg) scale(1.14); }

.tile-body { display: grid; min-width: 0; }
.tile-title { font-weight: 500; font-size: 0.875rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tile-note { color: var(--ink-soft); font-size: 0.75rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tile-lock { margin-left: auto; font-size: 0.6875rem; color: var(--lock); }

/* The number a tile answers to while Alt is held (portal.js 11). It is drawn over the
   tile's own chip instead of beside it, so nothing on the board moves as it appears, and
   it is given the accent under the tint the chip is already wearing. */
.tile-icon[data-key] { position: relative; }
.tile-icon[data-key]::after {
    content: attr(data-key);
    position: absolute; inset: 0;
    display: grid; place-items: center;
    border-radius: inherit;
    background: var(--accent); color: var(--accent-ink);
    font-family: var(--font); font-size: 0.875rem; font-weight: 700; letter-spacing: 0;
}

/* ======================================================================
   8. Widget column
   ====================================================================== */

.widget { padding: 14px; }
.widget-head { margin: 0 0 10px; }

/* The web search, above the board rather than inside the last column: one line, centred
   over the columns, and the same width whatever the board below it does - a search box
   that moves as the columns reflow is one nobody can aim at. */
.searchbar {
    display: flex; align-items: center; gap: 8px;
    width: min(100%, 720px); margin: 0 auto 18px;
}
.searchbar input { flex: 1 1 auto; min-width: 0; }
.searchbar .button { flex: 0 0 auto; }
.is-mobile .searchbar { margin-bottom: 12px; }

/* How much of the board is left standing, written under the box that filtered it and in
   the same width, so the number sits over the columns it is counting. It is out of the
   page entirely while nothing is typed, which leaves the board where it always starts. */
.searchcount {
    width: min(100%, 720px); margin: -10px auto 14px;
    color: var(--ink-soft); font-size: 0.8125rem;
}
.searchcount:empty { display: none; }
.is-mobile .searchcount { margin: -6px auto 10px; }

.clock-time { font-size: 2.125rem; font-weight: 700; letter-spacing: -.01em; font-variant-numeric: tabular-nums; line-height: 1.1; }
.clock-date { color: var(--ink-soft); font-size: 0.8125rem; margin-top: 2px; }

.weather-now { display: flex; align-items: center; gap: 12px; }
.weather-glyph {
    font-size: 2.25rem; line-height: 1;
    animation: drift 6s ease-in-out infinite;
}

/* Barely there, and on a long enough cycle to read as weather rather than as a gif. */
@keyframes drift {
    0%, 100% { transform: translateY(0) rotate(0); }
    50%      { transform: translateY(-3px) rotate(-4deg); }
}
.weather-temp { font-size: 2rem; font-weight: 700; letter-spacing: -.01em; }
.weather-meta { color: var(--ink-soft); font-size: 0.75rem; line-height: 1.4; }

.weather-days { list-style: none; margin: 12px 0 0; padding: 10px 0 0; border-top: 1px solid var(--line); display: grid; gap: 6px; }
.weather-days li { display: grid; grid-template-columns: 3.2rem 2rem 1fr; align-items: center; font-size: 0.8125rem; }
.day-name { color: var(--ink-soft); }
.day-glyph { text-align: center; }
.day-range { text-align: right; font-variant-numeric: tabular-nums; }

.news-list, .traffic-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.news-list li, .traffic-list li {
    display: grid; gap: 2px; font-size: 0.8125rem;
    padding-left: 10px; border-left: 3px solid var(--accent-soft);
}
.news-list li, .traffic-list li { transition: border-left-color .14s var(--ease-out), padding-left .18s var(--ease-spring); }
.news-list li:hover, .traffic-list li:hover { border-left-color: var(--accent); padding-left: 13px; }
.news-list a, .traffic-list a { color: var(--ink); font-weight: 500; }
.news-list a:hover, .traffic-list a:hover { color: var(--accent); }
.news-title, .traffic-title { font-weight: 500; }
.news-meta {
    display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; margin-top: 2px;
    color: var(--ink-soft); font-size: 0.6875rem; letter-spacing: .03em;
}

/* The paper the headline came from, in the accent rather than in the muted ink the
   timestamp beside it takes: with several papers merged into one list, the name is
   what the eye should land on first. */
.news-source {
    color: var(--accent); font-weight: 700; text-transform: uppercase;
    filter: hue-rotate(var(--chip-turn));
}
.news-when { text-transform: uppercase; }
.news-when::before { content: "·"; margin-right: 6px; }
.traffic-note { color: var(--ink-soft); font-size: 0.75rem; }

.widget-links { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; font-size: 0.875rem; }
.widget-link { display: inline-block; margin-top: 10px; font-size: 0.8125rem; }

.badge {
    display: inline-block; min-width: 18px; padding: 0 6px; margin-left: 6px;
    border-radius: var(--radius-pill); background: var(--accent); color: var(--accent-ink);
    font-size: 0.6875rem; font-weight: 600; text-align: center;
}

/* ======================================================================
   9. Ads
   ====================================================================== */

.adslot {
    display: grid; place-items: center;
    width: 100%; max-width: var(--ad-w); min-height: var(--ad-h);
    margin: 0 auto 20px; overflow: hidden;
    border: 1px dashed var(--line); border-radius: var(--radius-sm);
    background: color-mix(in srgb, var(--surface-2) 60%, transparent);
}
.adslot img { max-width: 100%; height: auto; display: block; }
.functions .adslot { margin-bottom: 0; }

.adslot-empty { display: grid; gap: 2px; place-items: center; font-size: 0.6875rem; }
.adslot-size { opacity: .7; font-weight: 400; letter-spacing: 0; }

.sticky-ad {
    position: sticky; bottom: 0; z-index: 15;
    padding: 6px 0; background: var(--bg);
    border-top: 1px solid var(--line);
}
.sticky-ad .adslot { margin-bottom: 0; }

/* ======================================================================
   10. Mobile board: a tab strip over a swipe track
   ====================================================================== */

.is-mobile .page { padding: 12px; }

.tabstrip {
    display: flex; gap: 6px; overflow-x: auto; padding-bottom: 8px;
    margin-bottom: 12px; scrollbar-width: none;
    /* A pixel under the height of the phone top bar, so a tab slides beneath it rather
       than leaving a sliver of the board showing through. */
    position: sticky; top: 48px; z-index: 10;
    background: linear-gradient(var(--bg) 80%, transparent);
}
.tabstrip::-webkit-scrollbar { display: none; }

.tab {
    flex: 0 0 auto; display: flex; align-items: center; gap: 6px;
    padding: 9px 14px;
    border: var(--border-w) solid var(--border-c); border-radius: var(--radius-pill);
    background: var(--card-bg); color: var(--ink-soft);
    font: inherit; font-size: 0.875rem; cursor: pointer;
}
.tab { transition: transform .2s var(--ease-spring), background-color .14s var(--ease-out); }
.tab-active { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); font-weight: 600; transform: scale(1.04); }
.tab:active { transform: scale(.96); }
/* The tab icon carries its category's tint, so the strip reads as the same set of
   colours as the board. The chosen tab is already accent-on-accent and wants none. */
.tab-icon { font-size: 0.8125rem; color: var(--accent); filter: hue-rotate(var(--chip-turn)); }
.tab-active .tab-icon { color: inherit; filter: none; }

/* A tab is a whole column of the board, so it says how many categories stand behind the
   one it is named after. A whisper beside the name, not a badge shouting for attention. */
.tab-more { font-size: 0.6875rem; font-weight: 600; color: var(--ink-soft); }
.tab-active .tab-more { color: inherit; opacity: .75; }

/* The last pill is not a category but the panel that holds the news and the weather, so
   it is kept where it can always be reached: pinned to the right end of the strip while
   the categories scroll along underneath, and down to two glyphs - a list for the
   headlines and a sun for the weather - which is what leaves room to pin it at all. The
   name is still in the page for a screen reader. */
.tab-functions {
    position: sticky; right: 0; margin-left: auto;
    padding: 9px 11px; gap: 5px;
}
.tab-functions .tab-icon { font-size: 1rem; filter: none; }

/* The categories do not simply disappear under the pinned pill; they fade out into it. */
.tab-functions::before {
    content: ""; position: absolute; top: 0; bottom: 0; right: 100%; width: 20px;
    background: linear-gradient(to right, transparent, var(--bg));
    pointer-events: none;
}

/* One panel per column, swiped horizontally with snap points. */
.swipe {
    display: flex; align-items: start; gap: 12px;
    overflow-x: auto; overflow-y: hidden;
    scroll-snap-type: x mandatory;
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    /* Room for the shadow of the panels; the track clips otherwise. */
    padding: 4px 4px 12px; margin: -4px -4px 0;
}
.swipe::-webkit-scrollbar { display: none; }

.panel {
    /* A phone gets one panel per screen. On a wide screen - the mobile view
       previewed from a desktop - the panels stay phone-sized and several show at
       once, rather than one column stretched across the window. */
    flex: 0 0 min(100%, 460px); min-width: 0; padding: 10px;
    scroll-snap-align: start; scroll-snap-stop: always;
}

/* A panel holds a column of the board, which is several categories one under the other.
   The panel is the card, so the categories inside it go flat and are told apart by their
   headings - the same treatment the widgets get in the "More" panel. */
.panel .column {
    background: none; border: 0; border-radius: 0; box-shadow: none;
    padding: 2px 2px 10px;
}
.panel .column + .column { padding-top: 14px; }

/* A search runs across the whole board rather than the panel that happens to be open, so
   while one is on, the panels that kept a link are stacked one under the other and the
   strip that chooses between them is put away - it has nothing left to choose between.
   The panels the filter emptied are gone from the page, and with them the news and
   weather panel, which holds no links to filter. */
.mboard.is-filtered .tabstrip { display: none; }
.mboard.is-filtered .swipe { display: block; overflow: visible; scroll-snap-type: none; }
.mboard.is-filtered .panel + .panel { margin-top: 12px; }

.tiles-mobile { gap: 3px; }
.tiles-mobile .tile > a { padding: 11px 10px; }
.tiles-mobile .tile-icon { flex-basis: 34px; height: 34px; font-size: 0.875rem; }
.tiles-mobile .tile-icon[data-key]::after { font-size: 1rem; }
.tiles-mobile .tile-title { font-size: 0.9375rem; }

.is-mobile .widget { border: 0; box-shadow: none; border-radius: 0; padding: 12px 0; border-bottom: 1px solid var(--line); background: none; }
.is-mobile .widget:last-child { border-bottom: 0; }

/* ======================================================================
   11. Sheets, forms, buttons, tables
   ====================================================================== */

.sheet { padding: 20px; margin: 0 auto 20px; max-width: 1100px; }
.sheet-narrow { max-width: 460px; }
.sheet-medium { max-width: 880px; }
.sheet-danger { border-color: var(--danger); }
.sheet h1 { margin: 0 0 6px; font-size: 1.5rem; letter-spacing: -.01em; }
.sheet h2 { margin: 0 0 10px; font-size: 1.125rem; }

.sheet-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-bottom: 8px; }

.page-head { max-width: 1100px; margin: 0 auto 16px; }
.page-head h1 { margin: 0 0 4px; font-size: 1.625rem; letter-spacing: -.01em; }
.page-sub { margin: 0 0 4px; color: var(--ink); }
.page-meta { margin: 0; color: var(--ink-soft); font-size: 0.8125rem; }

.hint { color: var(--ink-soft); font-size: 0.8125rem; }

/* A plain bulleted list on an ordinary sheet, where the legal pages' own list styles
   do not reach. */
.bullets { margin: 0 0 12px; padding-left: 20px; display: grid; gap: 6px; }

.stack { display: grid; gap: 12px; margin-top: 12px; }
.stack-tight { gap: 6px; }
.row { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }

/* .field-label is the same caption for a control that is not one input in one
   label - the icon field, which is a box and a row of suggestions together. */
label > span, .field-label { display: block; font-size: 0.8125rem; color: var(--ink-soft); margin-bottom: 4px; }

input, select, textarea, button { font: inherit; }

/* :where() rather than :not() so this rule stays at the specificity of a bare
   element: a class that sizes one control - .icon-input, .inline-form - then wins
   without having to shout. */
input:where(:not([type=checkbox]):not([type=radio])), select, textarea {
    width: 100%; padding: 9px 11px;
    border: var(--control-border-w) solid var(--control-border-c); border-radius: var(--radius-sm);
    background: var(--field-bg); color: var(--ink);
    min-height: 40px;
}

input:focus, select:focus, textarea:focus, button:focus-visible, a:focus-visible {
    outline: 2px solid var(--accent); outline-offset: 2px;
}

.icon-input { width: 4.5rem; text-align: center; }

.check { display: flex; align-items: center; gap: 8px; }
.check > span { margin: 0; color: var(--ink); }
.check input { width: auto; accent-color: var(--accent); }

.fieldset { border: var(--border-w) solid var(--border-c); border-radius: var(--radius-sm); padding: 10px 12px; display: grid; gap: 8px; }
.fieldset legend { color: var(--ink-soft); font-size: 0.8125rem; padding: 0 4px; }
.fieldset-plain { border: 0; padding: 0; }
.fieldset-plain legend { padding: 0; }

.button, .link-button {
    display: inline-flex; align-items: center; justify-content: center; gap: 6px;
    padding: 8px 14px; min-height: 38px;
    border: var(--control-border-w) solid var(--control-border-c); border-radius: var(--radius-sm);
    background: var(--button-bg); color: var(--ink);
    cursor: pointer; font-size: 0.875rem; font-weight: 500;
    transition: background-color .14s var(--ease-out), transform .18s var(--ease-spring);
}
.button:active, .icon-button:active { transform: scale(.96); }
.button:hover { background: var(--button-hover); text-decoration: none; transform: var(--lift); }
.button-primary { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
[data-shape="brutal"] .button-primary, [data-shape="outline"] .button-primary { border-color: var(--ink); }
.button-primary:hover { background: var(--accent); opacity: .92; }
.button-danger { border-color: var(--danger); color: var(--danger); background: var(--button-bg); }
.button-danger:hover { background: var(--bad-bg); color: var(--bad-ink); }
.button-google { width: 100%; }
.button-small { padding: 5px 10px; min-height: 32px; font-size: 0.8125rem; }
.button[disabled] { opacity: .5; cursor: not-allowed; }

.link-button { border: 0; background: none; color: var(--accent); padding: 0; min-height: 0; font-weight: 400; }
.link-button:hover { text-decoration: underline; background: none; }

.icon-button {
    display: inline-grid; place-items: center; width: 30px; height: 30px;
    border: var(--control-border-w) solid var(--control-border-c); border-radius: var(--radius-sm);
    background: var(--button-bg); color: var(--ink-soft); cursor: pointer; font-size: 0.8125rem;
}
.icon-button:hover { background: var(--button-hover); color: var(--ink); text-decoration: none; }
.icon-danger { color: var(--danger); border-color: var(--danger); }
.icon-button-small { width: 26px; height: 26px; font-size: 0.8125rem; }
.icon-button:disabled { opacity: .35; cursor: not-allowed; }
.icon-button:disabled:hover { background: var(--button-bg); color: var(--ink-soft); }

/* Hidden is the shown glyph with a line drawn through it, so the two states are one
   stroke apart rather than two unrelated pictures. The glyph is a plain character
   and not an emoji, so it takes the ink colour the look asks for. */
.icon-eye { position: relative; font-size: 0.875rem; }
.icon-eye-off::after {
    content: ''; position: absolute; left: 50%; top: 50%;
    width: 17px; height: 2px; margin: -1px 0 0 -8.5px;
    background: currentColor; border-radius: 1px; transform: rotate(-45deg);
}

.inline-form { display: inline-flex; gap: 6px; align-items: center; margin: 0; }
.inline-form input:where(:not([type=checkbox]):not([type=radio])), .inline-form select { width: auto; }

.divider { display: grid; place-items: center; margin: 16px 0; color: var(--ink-soft); font-size: 0.75rem; }
.divider span { padding: 0 10px; }

.field-errors, .field-error { color: var(--danger); font-size: 0.8125rem; }
/* An untouched form still renders both of these - the summary with an empty list
   inside it - so :empty alone leaves a gap in a laid-out form. The validation
   scripts mark the quiet state, and that is what has to be hidden. */
.field-errors:empty, .field-error:empty,
.validation-summary-valid, .field-validation-valid { display: none; }

/* ======================================================================
   12. The link editor, moderation, speciality pages

      The editor mirrors the board it edits: the same categories in the same order,
      each wearing the tinted chip it wears on the dashboard, so a link is changed
      where the eye already looks for it. A row keeps chip, name and tools on one
      line while the column is wide enough for them, and drops the tools whole onto
      a second line rather than letting them break up into a heap - which is what
      the old row did as soon as a column narrowed.
   ====================================================================== */

.customize-grid {
    display: grid; gap: 14px; align-items: start;
    grid-template-columns: repeat(auto-fill, minmax(min(380px, 100%), 1fr));
    margin: 16px 0 0;
}

.edit-column {
    padding: 12px; background: var(--surface-2); box-shadow: none;
    display: grid; gap: 10px; align-content: start;
}
[data-shape="flat"] .edit-column, [data-shape="outline"] .edit-column { background: var(--card-bg); }

/* The name on one line and the column's own tools on the next: a long title then
   has nowhere to push the buttons to, which is how they used to end up outside
   the card altogether. */
.edit-column-head { display: grid; gap: 8px; }
.edit-column-name { display: flex; align-items: center; gap: 6px; margin: 0; }
.edit-column-name .icon-input { flex: 0 0 3.25rem; }
.edit-column-name .edit-column-title { flex: 1 1 auto; min-width: 0; font-weight: 600; }

.edit-column-meta { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.edit-column-count { color: var(--ink-soft); font-size: 0.75rem; }
.edit-column-tools { display: flex; gap: 4px; }

.edit-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }

.edit-row {
    display: flex; flex-wrap: wrap; align-items: center; gap: 6px 9px;
    padding: 8px 9px; border-radius: var(--radius-sm);
    background: var(--surface); border: var(--border-w) solid var(--border-c);
    transition: border-color .14s var(--ease-out);
}
.edit-row:hover, .edit-row:focus-within { border-color: var(--accent); }

/* The same chip the link wears on the board, tint and all. */
.edit-row-icon {
    display: grid; place-items: center; flex: 0 0 26px; height: 26px;
    border-radius: var(--radius-tile);
    background: var(--accent-soft); color: var(--accent);
    font-size: 0.8125rem; font-weight: 700;
    filter: hue-rotate(var(--chip-turn));
}

.edit-row-main { flex: 1 1 6rem; display: grid; min-width: 0; }
.edit-row-title { display: flex; align-items: center; gap: 5px; min-width: 0; font-weight: 500; }
.edit-row-title .tile-lock { margin-left: 0; }
.edit-row-name, .edit-row-url { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.edit-row-url { color: var(--ink-soft); font-size: 0.75rem; }
.edit-row-flags { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 3px; }
.edit-row-flags:empty { display: none; }

/* The tools travel as one block: either they sit at the end of the row, or they
   take the next line together. */
.edit-row-tools { display: flex; flex-wrap: nowrap; gap: 3px; margin-left: auto; }

.edit-row-locked { border-left: 3px solid var(--lock); }
.edit-row-hidden { opacity: .65; }
.edit-row-hidden .edit-row-icon { filter: grayscale(1); }

/* The same gold edge one level up, for a whole category the operator keeps. */
.edit-column-locked { border-left: 3px solid var(--lock); }

/* The operator's lock, ticked in the column head. It wraps onto its own line
   under the name, and reads as a quiet note until it is on. */
.check-lock { flex: 1 0 100%; }
.check-lock > span { color: var(--ink-soft); font-size: 0.75rem; }
.check-lock:has(input:checked) > span { color: var(--ink); font-weight: 600; }

.edit-empty {
    margin: 0; padding: 12px 10px; text-align: center;
    border: 1px dashed var(--control-border-c); border-radius: var(--radius-sm);
    color: var(--ink-soft); font-size: 0.8125rem;
}

/* A dashed slot rather than a filled button: it reads as the empty place a new
   thing goes into. The dashes are the controls' own edge colour, which is kept
   clear of every surface the palettes offer, and hovering fills it from the look. */
.add-slot {
    display: flex; align-items: center; justify-content: center; gap: 7px;
    width: 100%; padding: 9px 12px; min-height: 38px;
    border: 1px dashed var(--control-border-c); border-radius: var(--radius-sm);
    background: none; color: var(--ink-soft);
    font-size: 0.8125rem; font-weight: 500; cursor: pointer;
    transition: background-color .14s var(--ease-out), color .14s var(--ease-out),
                border-color .14s var(--ease-out);
}
.add-slot:hover {
    background: var(--button-hover); color: var(--ink);
    border-color: var(--accent); border-style: solid; text-decoration: none;
}
.add-slot-plus { font-size: 1rem; line-height: 1; }

.add-column {
    display: grid; gap: 8px; align-content: start; padding: 12px;
    border: 1px dashed var(--control-border-c); border-radius: var(--radius);
}
.add-column h2 { margin: 0; font-size: 0.8125rem; color: var(--ink-soft); }
.add-column-row { display: flex; gap: 6px; }
.add-column-row .icon-input { flex: 0 0 3.25rem; }
.add-column-row input:not(.icon-input) { flex: 1 1 auto; min-width: 0; }

/* A remark about the page rather than about something the visitor just did:
   quieter than a flash, and it stays put. */
.note {
    display: flex; align-items: flex-start; gap: 9px;
    margin: 12px 0 0; padding: 9px 12px;
    border-radius: var(--radius-sm); border-left: 3px solid var(--lock);
    background: var(--accent-soft); color: var(--ink);
    font-size: 0.8125rem;
}
.note-mark { flex: 0 0 auto; }

.pill {
    display: inline-block; padding: 1px 8px; border-radius: var(--radius-pill);
    background: var(--surface-2); color: var(--ink-soft); font-size: 0.6875rem;
}
.pill-hidden { background: var(--warn-bg); color: var(--warn-ink); }
/* Locked is not a warning, so it takes the look's own tint rather than the amber
   one. Ink on accent-soft is the pairing the note above already uses. */
.pill-lock { background: var(--accent-soft); color: var(--ink); }

/* ======================================================================
   12b. One link, open for editing

      The form on the left, and on the right the tile exactly as the board will
      draw it, kept in step with the fields as they are typed. It is the only
      honest answer to "what will this look like", and it costs one small script.
   ====================================================================== */

.link-editor {
    display: grid; gap: 20px; align-items: start; margin-top: 14px;
    grid-template-columns: minmax(0, 1fr) minmax(0, 264px);
}
.link-editor > .form-actions { grid-column: 1 / -1; }

@media (max-width: 760px) {
    .link-editor { grid-template-columns: minmax(0, 1fr); gap: 16px; }
}

.link-fields { display: grid; gap: 14px; margin: 0; min-width: 0; }
.field-note { display: block; margin-top: 4px; color: var(--ink-soft); font-size: 0.75rem; }
.field-note code { font-family: ui-monospace, Consolas, monospace; background: var(--surface-2); padding: 1px 4px; border-radius: var(--radius-sm); }

.icon-field { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.icon-field .icon-input { flex: 0 0 auto; }
.icon-choices { display: flex; flex-wrap: wrap; gap: 4px; }
.icon-choice {
    display: grid; place-items: center; width: 30px; height: 30px;
    border: var(--control-border-w) solid var(--control-border-c); border-radius: var(--radius-sm);
    background: var(--button-bg); cursor: pointer; font-size: 0.875rem; line-height: 1;
}
.icon-choice:hover { background: var(--button-hover); }

/* One tick per surface, drawn as a card so the chosen ones can be seen at a
   glance instead of as two identical squares in a list. */
.option-list { display: grid; gap: 6px; }
.option {
    display: flex; align-items: center; gap: 10px;
    padding: 9px 11px; border-radius: var(--radius-sm);
    border: var(--control-border-w) solid var(--control-border-c);
    background: var(--field-bg); cursor: pointer;
    transition: background-color .14s var(--ease-out), border-color .14s var(--ease-out);
}
.option:hover { background: var(--button-hover); }
.option:has(input:checked) { border-color: var(--accent); background: var(--accent-soft); }
.option input { width: auto; accent-color: var(--accent); flex: 0 0 auto; }
/* The option is a label, so its text would otherwise be caught by the caption rule
   above and drawn as a muted 13px hint. It is the option name; it reads as ink. */
.option-text { display: grid; min-width: 0; margin-bottom: 0; color: var(--ink); font-size: var(--font-size); }
.option-note { color: var(--ink-soft); font-size: 0.75rem; }
/* Muted ink on the tinted fill of a chosen option is only 4.15:1 in Sand, so the note
   takes full ink once the box is ticked. It reads as the chosen one either way. */
.option:has(input:checked) .option-note { color: var(--ink); }
/* The one operator-only tick on the link editor: set a little apart from the
   options every member gets, and wearing the padlock so it is recognisable
   before it is read. */
.option-lock { margin-top: 4px; }
.option-mark { margin-right: 6px; }

.link-preview {
    position: sticky; top: 16px;
    display: grid; gap: 10px; padding: 12px;
    border-radius: var(--radius); background: var(--surface-2);
}
[data-shape="flat"] .link-preview, [data-shape="outline"] .link-preview { background: var(--card-bg); }
/* Stacked under the fields there is nothing to stick beside, so it travels with them.
   This has to come after the rule it undoes, not with the rest of the narrow layout. */
@media (max-width: 760px) {
    .link-preview { position: static; }
}
.link-preview h2 {
    margin: 0; font-size: var(--head-size); font-weight: var(--head-weight);
    letter-spacing: var(--head-tracking); text-transform: var(--head-case); color: var(--ink-soft);
}
.link-preview-board { padding: 10px; border-radius: var(--radius-sm); background: var(--card-bg); }
.link-preview-note { margin: 0; color: var(--ink-soft); font-size: 0.75rem; }

.form-actions {
    display: flex; flex-wrap: wrap; gap: 10px; align-items: center;
    padding-top: 14px; border-top: 1px solid var(--line);
}
.form-actions-end { margin-left: auto; }

.table { width: 100%; border-collapse: collapse; margin-top: 12px; }
.table th, .table td { text-align: left; padding: 10px; border-bottom: 1px solid var(--line); vertical-align: top; }
.row-tools { display: flex; gap: 6px; }

.status { display: inline-block; padding: 2px 10px; border-radius: var(--radius-pill); font-size: 0.75rem; background: var(--surface-2); color: var(--ink-soft); }
.status-published { background: var(--ok-bg); color: var(--ok-ink); }
.status-rejected { background: var(--bad-bg); color: var(--bad-ink); }
.status-pendingreview { background: var(--warn-bg); color: var(--warn-ink); }
.status-confirmed { background: var(--ok-bg); color: var(--ok-ink); }
.status-unconfirmed { background: var(--warn-bg); color: var(--warn-ink); }

.page-cards { list-style: none; margin: 16px 0 0; padding: 0; display: grid; gap: 12px; grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr)); }
.page-card a {
    display: grid; gap: 4px; padding: 14px; height: 100%;
    color: var(--ink);
    transition: transform .18s var(--ease-spring), border-color .14s var(--ease-out);
}
.page-card a:hover { text-decoration: none; border-color: var(--accent); transform: var(--lift); }
.page-card-icon { font-size: 1.375rem; transition: transform .25s var(--ease-spring); transform-origin: left center; }
.page-card a:hover .page-card-icon { transform: rotate(-8deg) scale(1.15); }
.page-card-title { font-weight: 600; }
.page-card-note, .page-card-meta { color: var(--ink-soft); font-size: 0.8125rem; }

/* Where a topic can be found. A granted topic has two addresses that are one page - its
   own host and the path under /pages - and the host is the one worth reading, so it is
   the one set in the monospace the rest of the site keeps for an address. Both take the
   look's accent like every other link here; neither is ever left colourless. */
.topic-address {
    margin: 0 0 10px; display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px;
    font-size: 0.8125rem; color: var(--ink-soft);
}
.topic-address-label { color: var(--ink-soft); }
.topic-address-host, .topic-address-path {
    font-family: ui-monospace, Consolas, monospace;
    background: var(--surface-2); padding: 2px 7px; border-radius: var(--radius-sm);
}
.topic-address-host { color: var(--accent); font-weight: 600; }
/* A span rather than a link, for a topic whose host does not answer yet. */
span.topic-address-host { color: var(--ink-soft); font-weight: 500; }
.topic-address-path { color: var(--ink-soft); }
.topic-address a:hover { border-color: var(--accent); }

/* The same address on the public page, inside the line of credits under the heading. */
.page-meta-host { font-family: ui-monospace, Consolas, monospace; color: var(--accent); }

/* What an applicant is told about a field, under the field rather than beside it. */
.field-hint { display: block; margin-top: 4px; color: var(--ink-soft); font-size: 0.78125rem; }

/* The verdict on a name, once the server has been asked: free, or the reason it is not.
   Colour from the look's own good and bad, never a bare green and red. */
.field-hint-ok { color: var(--accent); }
.field-hint-bad { color: var(--danger); }

/* A topic's name in a table of applications, set as the address it is. */
.hint code { font-family: ui-monospace, Consolas, monospace; background: var(--surface-2); padding: 1px 5px; border-radius: var(--radius-sm); }

/* One domain of its own, on the operator's topic screen: the host it answers on, and the
   look and language it preselects. Each row is its own form, so it is boxed as one. The
   news sources on the widgets page are kept the same way, and are boxed the same. */
.domain-list, .feed-list { display: grid; gap: 12px; margin: 12px 0 20px; }
.domain-row, .feed-row {
    display: grid; gap: 10px;
    border: 1px solid var(--line); border-radius: var(--radius); padding: 14px;
    background: var(--surface-2);
}

/* A source that is not read is still on the page - it is kept so that the day its
   publisher says yes, switching it on is a click - so it is dimmed rather than hidden,
   and says which one it is by being the quiet row. */
.feed-row-off { opacity: 0.62; }
.feed-row-off:focus-within, .feed-row-off:hover { opacity: 1; }
.domain-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; }
.domain-head code { font-family: ui-monospace, Consolas, monospace; font-size: 0.9375rem; }

/* The four look axes and the language, side by side where there is room and stacked where
   there is not. Each is a plain select, so each takes the look's own control styling. */
.domain-fields { display: grid; gap: 10px; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); align-items: end; }
.domain-fields .check { align-self: center; }
.feed-row .row-tools { justify-content: flex-end; }

/* The case an applicant made for a topic, as the moderator reads it: their words, so
   the line breaks they wrote are kept. */
.sheet-subhead { margin: 14px 0 4px; font-size: 0.9375rem; }
.review-rationale { margin: 0 0 12px; white-space: pre-line; }

.place-list { list-style: none; margin: 12px 0 0; padding: 0; display: grid; gap: 6px; }

/* ======================================================================
   13. The look picker
   ====================================================================== */

.picker-axis { border: 0; padding: 0; margin: 0; min-width: 0; }
.picker-axis legend { padding: 0; margin-bottom: 10px; }

.choices { display: grid; gap: 10px; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
.choices-modes { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); max-width: 520px; }

.choice {
    position: relative; display: grid; gap: 2px; padding: 10px;
    cursor: pointer; transition: transform .18s var(--ease-spring), border-color .14s var(--ease-out);
}
.choice:hover { transform: var(--lift); }
.choice:active { transform: scale(.98); }
.choice input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.choice:has(input:checked) { border-color: var(--accent); outline: 2px solid var(--accent); outline-offset: -1px; }
.choice:has(input:focus-visible) { outline: 3px solid var(--accent); outline-offset: 2px; }
.choice-name { font-weight: 600; font-size: 0.875rem; margin-top: 8px; }
.choice-note { color: var(--ink-soft); font-size: 0.75rem; }

.choice-preview { display: block; height: 76px; border-radius: var(--radius-sm); overflow: hidden; }

/* A palette sample is a tiny page drawn from that palette's own tokens. */
.swatch {
    position: relative; display: block; height: 100%;
    background: var(--bg); border: 1px solid var(--line);
    font-family: system-ui, sans-serif;
}
.swatch-bar { position: absolute; top: 0; left: 0; right: 0; height: 12px; background: var(--accent); }
.swatch-card {
    position: absolute; left: 10px; right: 10px; top: 22px; bottom: 10px;
    display: grid; grid-template-columns: 18px 1fr; align-items: center; gap: 8px; padding: 8px;
    background: var(--surface); border: 1px solid var(--line); border-radius: 6px;
}
.swatch-dot { width: 18px; height: 18px; border-radius: 5px; background: var(--accent-soft); border: 1px solid var(--accent); }
.swatch-line { height: 6px; border-radius: 3px; background: var(--ink); opacity: .85; }

.mode-demo { display: flex; gap: 0; }
.mode-demo .swatch { flex: 1 1 0; }
.swatch-half + .swatch-half { border-left: 0; }

.type-demo {
    display: grid; place-items: center;
    background: var(--surface-2); color: var(--ink);
    font-family: var(--font); font-size: 2.125rem; font-weight: var(--head-weight); line-height: 1;
}

/* A shape sample carries its own shape tokens; the box inside is drawn from them. */
.shape-demo { display: grid; place-items: center; padding: 10px 12px; background: var(--surface-2); }
.shape-demo-box {
    width: 100%; height: 100%; display: flex; align-items: center; gap: 8px; padding: 8px 10px;
    background: var(--card-bg); border: var(--border-w) solid var(--border-c);
    border-radius: var(--radius); box-shadow: var(--shadow);
}
[data-shape="outline"] .shape-demo-box, [data-shape="flat"] .shape-demo-box { background: var(--surface); }
[data-shape="brutal"] .shape-demo-box { box-shadow: 3px 3px 0 var(--ink); margin: 0 3px 3px 0; }
.shape-demo-chip { width: 18px; height: 18px; flex: 0 0 auto; border-radius: var(--radius-tile); background: var(--accent); }
.shape-demo-tile { height: 10px; flex: 1 1 auto; border-radius: var(--radius-tile); background: var(--accent-soft); }

.picker-reset { margin-top: 16px; }

/* ======================================================================
   14. Legal pages and the cookie banner
   ====================================================================== */

.legal { max-width: 760px; }
.legal-head { margin-bottom: 4px; }
.legal-head h1 { margin: 0 0 4px; }
.legal-head .hint { margin: 0; }

.legal section { margin-top: 26px; }
.legal h2 { margin: 0 0 8px; font-size: 1.1875rem; }
.legal h3 { margin: 20px 0 6px; font-size: 0.9375rem; }
.legal p { margin: 0 0 10px; }
.legal ul { margin: 0 0 10px; padding-left: 20px; display: grid; gap: 6px; }
.legal li { padding-left: 2px; }
/* The article that names the ground for a use, set apart from the paragraph it closes. */
.legal-basis { display: block; margin-top: 4px; color: var(--ink-soft); font-style: normal; font-size: 0.8125rem; }
.legal code { font-family: ui-monospace, Consolas, monospace; font-size: 0.78125rem; background: var(--surface-2); padding: 1px 5px; border-radius: var(--radius-sm); }

/* A key to press, where the accessibility statement names one. */
.legal kbd {
    font-family: ui-monospace, Consolas, monospace; font-size: 0.78125rem;
    background: var(--surface-2); color: var(--ink);
    padding: 1px 6px; border: 1px solid var(--line); border-radius: var(--radius-sm);
}

/* The row of links carried by every legal page. */
.legal-nav {
    display: flex; flex-wrap: wrap; gap: 4px;
    margin: 14px 0 4px; padding: 8px 0;
    border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
}
.legal-nav a { padding: 4px 10px; border-radius: var(--radius-pill); color: var(--ink-soft); font-size: 0.8125rem; }
.legal-nav a:hover { background: var(--hover); color: var(--ink); text-decoration: none; }
.legal-nav-current { background: var(--accent-soft); color: var(--accent); font-weight: 600; }

/* An unconfigured imprint is a deployment fault, and says so rather than hiding. */
.legal-missing {
    margin-top: 16px; padding: 12px 14px;
    border-radius: var(--radius-sm); border-left: 3px solid var(--warn-ink);
    background: var(--warn-bg); color: var(--warn-ink);
}
.legal-missing p { margin: 6px 0 0; }
.legal-missing code { background: rgb(var(--shadow-rgb) / .08); color: inherit; }

.legal-facts { display: grid; grid-template-columns: 9rem 1fr; gap: 6px 12px; margin: 0 0 12px; }
.legal-facts dt { color: var(--ink-soft); font-size: 0.8125rem; }
.legal-facts dd { margin: 0; }
/* A postal address is written down as it would be on an envelope, so the line breaks
   configuration carries have to survive being put on the page. */
.legal-facts dd.legal-address { white-space: pre-line; }

.legal-summary { padding: 14px 16px; border-radius: var(--radius-sm); background: var(--surface-2); }
.legal-summary h2 { font-size: 0.9375rem; }
.legal-summary ul { margin-bottom: 0; }

/* Cookie tables are four columns of prose; on a phone they scroll rather than squash. */
.table-scroll { overflow-x: auto; }
.table-scroll .table { min-width: 540px; }

.consent-settings { padding: 16px; border-radius: var(--radius-sm); background: var(--surface-2); }
.consent-settings .check { align-items: flex-start; }
.consent-settings .check input { margin-top: 4px; }
.consent-state { font-weight: 500; }

.adslot-consent { font-size: 0.6875rem; }

/* The banner. Fixed to the bottom, above everything, and gone the moment it is
   answered. It never covers the whole page: the portal stays readable behind it. */
.consent {
    position: fixed; inset: auto 0 0 0; z-index: 60;
    padding: 12px; background: var(--surface);
    border-top: var(--border-w) solid var(--border-c);
    box-shadow: 0 -8px 30px rgb(var(--shadow-rgb) / .12);
    animation: consent-in .18s ease-out;
}

@keyframes consent-in { from { transform: translateY(100%); } to { transform: none; } }

.consent-inner {
    max-width: 1100px; margin: 0 auto;
    display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap;
}

.consent-text { flex: 1 1 380px; min-width: 0; }
.consent-text h2 { margin: 0 0 4px; font-size: 1rem; }
.consent-text p { margin: 0; font-size: 0.84375rem; color: var(--ink-soft); }
.consent-text a { color: var(--accent); }

/* Accepting and refusing are the same size, side by side: an answer only counts as
   freely given when saying no costs no more than saying yes. */
.consent-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 0; }
.consent-actions .button { min-width: 140px; justify-content: center; }
.consent-more { font-size: 0.8125rem; color: var(--ink-soft); padding: 0 4px; }

/* The mobile sticky advertisement lives in the same corner; the banner wins until
   it is answered, and the slot comes back the moment it is. */
body:has(.consent) .sticky-ad { display: none; }
body:has(.consent) .sitefoot { padding-bottom: 96px; }

@media (max-width: 640px) {
    .consent-inner { align-items: stretch; }
    .consent-actions { width: 100%; }
    .consent-actions .button { flex: 1 1 0; min-width: 0; }
    .consent-more { width: 100%; text-align: center; padding: 2px 0 0; }
    .legal-facts { grid-template-columns: 1fr; gap: 0 0; }
    .legal-facts dd { margin-bottom: 10px; }
}

/* ======================================================================
   15. Motion
   ====================================================================== */

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after { transition: none !important; animation: none !important; }
    .swipe { scroll-behavior: auto; }
}
