/* Generated by `vgarun valheim cms create stylesheet` — do not change by hand. */
@layer tokens, base, elements, widgets, slices, layouts, templates;

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: The token set — measures from the golden ratio, colours in OKLCH.

   Everything else derives from here. No package sets a number of its own
   where a token already carries it: a size that stands in only one place is
   a size that does not move along at the next pass. */

@layer tokens {
    :root {
        /* **Dark, and always.** `dark light` meant: the browser may choose by
           the system setting — and on a light Windows it chose light,
           whereupon every dark face turned almost white and the text on it
           unreadable (reported on 3 September 2026). A light theme is
           thought ahead, not decided: the light values in `light-dark()`
           stay, so that they can be found some day without searching, but
           served is the dark version. One word here turns that around
           ({T0026S12}). */
        color-scheme: dark;

        /* Measure ladder — the step is √φ = 1.272, two steps are φ. Type takes
           every point, spacing every second. */
        /* The base grows with the width: 14 px on the phone, 15 from tablet,
           17 from screen. Measured on the running text of the design in all
           three versions — it scales the type instead of setting it the same
           everywhere, and without that the page looks a fifth too large on
           the phone.

           The ladder itself stays: 17 by √φ is 13.4; times √φ is 21.6;
           times φ is 27.5 — the design hits 13, 22 and 28. The steps below
           stand at the breakpoints. */
        --scale-base:  0.875rem;

        /* Two sizes do not follow the ladder and therefore stand per width:

           The stage — 23, 30, 44 px. It is the game name and should fill
           the bar, which looks different at 430 px than at 1440.

           The page heading — 19, 22, 28 px. Its ratio to the base grows
           with the width (1.36 · 1.47 · 1.65 instead of a constant φ), and
           that is no carelessness of the design: a heading that carried the
           same multiple on the phone as on the desk would take half the
           height of the viewport there. Where there is little room, it moves
           closer to the text. */
        --text-stage:  1.4375rem;
        --text-head:   1.1875rem;

        /* Labels in capitals — card titles, metric labels. 10, 11, 12 px: they
           grow more slowly than the base, because capitals build wider at
           the same size and would otherwise wrap in a tight card. */
        --text-label:  0.625rem;

        --text-micro:  calc(var(--scale-base) / 1.618);   /* 10.5 → labels */
        --text-small:  calc(var(--scale-base) / 1.272);   /* 13.4 → tags, card titles */
        --text:        var(--scale-base);                 /* 17   → running text */
        --text-large:  calc(var(--scale-base) * 1.272);   /* 21.6 → metrics */
        --text-title:  calc(var(--scale-base) * 2.058);   /* 35   → the stage */

        --space-hair:  calc(var(--scale-base) * 0.146);
        --space-tight: calc(var(--scale-base) * 0.236);
        --space-snug:  calc(var(--scale-base) * 0.382);
        --space-close: calc(var(--scale-base) * 0.618);
        --space:       var(--scale-base);
        --space-open:  calc(var(--scale-base) * 1.618);

        /* The height of every control — field, button, and whatever comes.
           
           It stood in the seed_form widget before, which stretched field and
           button in one grid row. On extracting into elements it got lost:
           on the landing page the button stretched over label **and** field,
           on the item page it shrank to its content. Two pages, two heights,
           one thing.
           
           The landing page is the model, and this number is measured from
           there: 0.9375rem of padding top and bottom plus 0.8125rem of type. */
        --control-height: 2.6875rem;

        /* The height of the sticky head ({T0097}). Measured on the running
           page: 68 points — two times 1rem of padding, 30 points of mark,
           5 points of progress bar, 1 point of border. It stands here
           because something floating under the head has to know it and
           cannot ask: `position: fixed` refers to the window, and CSS cannot
           write a measured height into a property.

           **Whoever changes the head changes this number.** That is the
           price; the alternative would be script that reads a height and
           writes it back — more mobility for more parts. */
        --head-height: 4.25rem;
        --space-wide:  calc(var(--scale-base) * 2.618);

        /* The sheet — the measures of every popover that opens ({T0084}).

           They stood in the item_sheet widget, measured by the owner on
           1 September 2026. When the map got its own sheet, I copied the
           same six numbers a second time, and with that there were two
           truths about one thing: whoever changes the width changes it in
           one place and sees the next day that the other stayed. Exactly
           the case {K0054} describes — only that the shell is no markup one
           could lift into an element, but measure. And measure lives here —
           like `--control-height` above.

           Whoever builds a sheet writes these tokens and no numbers. */
        --sheet-inline-size:     min(400px, calc(100vw - 2rem));
        --sheet-max-block-size:  min(900px, calc(100vh - 2rem));
        /* **The edge of everything that lies on the page** — sheets and
           bands. The name says »sheet«, because the first one was there;
           meant is every element that floats above the content and
           therefore has to set itself off from it ({T0097}).

           Whoever builds one writes this token and no number. Until
           12 September 2026 it did not stand in two places: the map's
           favourites sheet carried its 3px written out, and the notice band
           a single one — and a band with a hairline beside a sheet with
           three points looks like two house orders. */
        --sheet-edge:            3px;
        --sheet-shadow:          0 24px 64px oklch(0% 0 0 / 0.55);
        --sheet-scrim:           oklch(0% 0 0 / 0.6);
        /* Belt and braces: in the top layer a popover needs no z-index, but on
           closing it drops out, and then it decides after all (Owner,
           1 September 2026). */
        --sheet-layer:           9999;

        /* Output width. A single cap: the slice's track bounds itself by
           min\(\), hence the width needs no four steps — the gutter grows,
           not the cap. */
        /* The design leaves the content about 40 px of edge at 1440 px, so
           uses almost the full width. 84rem hits that and at the same time
           caps what would otherwise run boundless on a very wide screen —
           the running text stays at 66ch anyway \(see prose\). */
        --measure:     90rem;
        --gutter:      2.5rem;

        /* Grid of the pixel font. Its glyphs are eight font pixels wide; for an
           edge to fall on a whole device pixel, size by eight times device
           resolution must be a whole number — at resolution 1 and 3 a
           multiple of 8, at 2 one of 4 \({K0044}\). The ladder stays the
           same; rounding happens at the place of use with round\(\). */
        --pixel-grid:  8px;

        /* Surface and type are two scales, not one. The design sets the
           surfaces close together — 12 to 21 percent, five steps on nine
           points — and the type far above. A shared scale would have to
           serve both and would hit neither.

           The values for dark are measured from the design, those for light
           mirrored on them. light-dark() takes the first value in the light
           scheme. */
        --hue-surface: 260;
        --c-surface:   0.010;

        --surface-0:  light-dark(oklch(99% var(--c-surface) var(--hue-surface)),
                                 oklch(12% var(--c-surface) var(--hue-surface)));
        --surface-1:  light-dark(oklch(97% var(--c-surface) var(--hue-surface)),
                                 oklch(15% var(--c-surface) var(--hue-surface)));
        --surface-2:  light-dark(oklch(94% var(--c-surface) var(--hue-surface)),
                                 oklch(17% var(--c-surface) var(--hue-surface)));
        --surface-3:  light-dark(oklch(91% var(--c-surface) var(--hue-surface)),
                                 oklch(18% var(--c-surface) var(--hue-surface)));
        --surface-4:  light-dark(oklch(86% var(--c-surface) var(--hue-surface)),
                                 oklch(21% var(--c-surface) var(--hue-surface)));

        /* Type: three steps, measured from the design. 65 carries the text, 58
           the trimmings, 55 the quietest — mirrored in the light scheme. */
        /* Three steps, all measured from the export. The lightest carries
           another hue — 90 instead of 260 —, because a heading on dark
           ground should look warm and not blue.

           There were four. The quietest, --ink-faint, lay at 48 percent in
           dark mode and gave only 3.02 to 1 on --surface-0 — under AA, at
           all five places where it stood. Measured, 58 percent are needed
           there, and that is exactly --ink-quiet: below this step there is
           no text colour on this ground any more that holds the limit. A
           step that is nowhere usable within the rules is no step. */
        --ink-bright: light-dark(oklch(20% 0.005 90),  oklch(95% 0.005 90));
        --ink:        light-dark(oklch(28% 0.008 var(--hue-surface)),
                                 oklch(70% 0.008 var(--hue-surface)));
        /* The quiet step stood at 58 percent and held AA only on --surface-0
           — on the raised faces it fell to 4.43 and 4.36, measured at nine
           places. The ground is lighter there, so the type has to be too.
           61 percent carries on all three with reserve; 60 would have
           sufficed, but at 4.74 would again lie close to the edge. */
        --ink-quiet:  light-dark(oklch(42% 0.008 var(--hue-surface)),
                                 oklch(61% 0.008 var(--hue-surface)));

        --ground:     var(--surface-0);
        --ground-up:  var(--surface-2);

        /* **Three role colours, and there were always three** (Owner,
           12 September 2026: »es gibt folgende Grünwerte und das von Anbeginn
           … es gab niemals ein weiteres Grün oder ähnliches«).

           | Primary Green  | oklch(76% 0.17 130) | what leads, what triggers |
           | Secondary Gold | oklch(80% 0.13 85)  | what shows more           |
           | Tertiary Orange| oklch(68% 0.18  35) | the mark, sparingly       |

           Until then a fourth token stood here, `--action` with
           `oklch(72% 0.20 145)`, and beside it an `--on-action` nobody ever
           used. The sentence that justified both — »Verweis führt weg,
           Aktion löst aus« — described a division that never existed: the
           action button carried `--link` all along, and `--action` stood in
           twenty places almost only in focus rings. Two greens, one pixel
           apart in meaning and clearly so to the eye ({K0233}, {T0096}).

           **That holds for the roles of the surface, not for data.** The
           biome colours of the map and the rank ladder white-green-blue-
           magenta follow the game and stay where they are. */
        --brand:      oklch(68% 0.18  35);
        --link:       oklch(76% 0.17 130);
        /* Golden yellow, the third role beside the two greens: it says
           neither »leads away« nor »triggers« but »shows more«. The chevron
           on the map button carries it \(Owner, 1 September 2026\). */
        --gold:       oklch(80% 0.13 85);
        /* Red, the fourth role: »takes away«. The button in the target's sheet
           so leads out of the building plan back to the item page \(Owner,
           8 September 2026, {T0073S13}\). Measured on --surface-4 with dark
           type: 6.1 to 1. */
        --danger:     oklch(66% 0.19 28);

        /* The rank ladder of quality: white, green, blue, magenta — the
           sequence World of Warcraft and Elder Scrolls Online taught every
           player, chosen by the owner \({T0023S4}, 02-valheim.md\). It says
           **how good** a piece is; the biome colours say **when** in the
           game. Two systems, two questions — and hence no biome tone here.

           Green and magenta are measured from the design: rgb\(0,255,28\)
           and rgb\(255,0,252\). The design's blue, rgb\(11,97,172\), holds
           only 2.8 to 1 on surface-4 — under AA even for large type. It
           therefore stands at 65 percent lightness in the same hue: 5.5 to
           1, and still the blue of the ladder. The deviation is measured and
           named \(AW22\). White is the light ink the name carries anyway. */
        --rank-1:     var(--ink-bright);
        --rank-2:     oklch(86.7% 0.291 143);
        --rank-3:     oklch(65% 0.14 252);
        --rank-4:     oklch(70% 0.321 329);

        /* The scanline grid. One dark line in three, at 14 percent — on
           average 4.7 percent of darkening.

           **Where a picture gets a raster, it comes from here.** Not copied
           out, not adjusted by eye: the values are tight, and lying beside
           them does not look like »somewhat different« but like another
           effect. The biome bar stood at one line in two at 28 percent —
           three times as dark, and because line and gap run into each other
           on a double-resolution screen, nothing of the grid was left to
           see. Only a dark picture \({K0097}\).

           `linear-gradient` with `background-size` and not
           `repeating-linear-gradient`: a tile the browser does not compute
           itself does not let one drop out every few lines.

           To be used as a background layer, not as an overlay in a
           pseudo-element — the layer lies in the same `background` as the
           picture and cannot deviate from it. */
        --interlace:      linear-gradient(180deg,
                              oklch(0% 0 0 / 0.14) 0 1px, transparent 1px 3px);
        --interlace-size: 100% 3px;
    }

    /* The ladder has five steps — base · phone 16.25rem · tablet 35rem ·
       screen 60rem · widescreen 78.75rem. The gutter grows at two of them;
       at the others deliberately nothing stands, because nothing changes
       there. Only deltas, only min-width. */
    /* On the phone the gutter is narrower, otherwise nothing is left of the
       content. From tablet on it stands at the design's 40 px and grows no
       further — what room there is beyond, the cap measure takes.

       Only deltas, only min-width: every step names what changes, and
       nothing else. */
    :root { --gutter: var(--space); }

    @media (min-width: 35rem) {
        :root {
            --gutter:     2.5rem;
            --scale-base: 0.9375rem;
            --text-stage: 1.875rem;
            --text-head:  1.375rem;
            --text-label: 0.6875rem;
        }
    }

    @media (min-width: 60rem) {
        :root {
            --scale-base: 1.0625rem;
            --text-stage: 2.75rem;
            --text-head:  1.75rem;
            --text-label: 0.75rem;
        }
    }

    @media (resolution >= 2x) { :root { --pixel-grid: 4px; } }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: The foundation — box model, base type, and the tracks of every slice. */

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

    :where(body, h1, h2, h3, h4, p, figure, ul, ol, dl, dd) { margin: 0; }
    :where(ul[role="list"], ol[role="list"]) { padding: 0; list-style: none; }

    html {
        font-size: 100%;
        -webkit-text-size-adjust: none;
    }

    body {
        font-family: "Oxanium", "Oxanium Fallback", system-ui, sans-serif;
        font-size: var(--text);
        line-height: 1.5;
        color: var(--ink);
        background: var(--ground);
    }

    :where(img, picture, svg) { display: block; max-inline-size: 100%; }
    :where(input, button, select, textarea) { font: inherit; color: inherit; }

    /* A slice is a row across the full width \({K0031}\). The bounding is
       carried by the named track, not a wrapper: children land in
       `content`, and what should run edge to edge says `grid-column: full`.

       Expressly no width statement — the slice is a child of the
       single-column grid and fills its track. `width: 100vw` would include
       the scrollbar and give about 15 px of horizontal overflow on the
       desk, which does not occur on the phone and is therefore noticed late. */
    [data-slice] {
        display: grid;

        /* The edge is computed and stands ready as a variable. That is the
           difference that counts: a band that wants to share inside only
           replaces the content track and inherits the same edge. As
           minmax\(gutter, 1fr\) the gutter would fight with every inner fr
           track over the room, and every band would come to another edge —
           measured 40, 63, 77 and 110 px on four bands of the same page
           \({K0057}\). */
        --edge: max(var(--gutter), (100% - var(--measure)) / 2);

        grid-template-columns:
            [full-start] var(--edge)
            [content-start] minmax(0, 1fr)
            [content-end] var(--edge)
            [full-end];
    }

    [data-slice] > * { grid-column: content; }
}

/* As long as a sheet stands in the top layer, the page beneath does not move.

   A popover does **not** lock scrolling by itself — unlike a <dialog> in
   the modal state. `:has()` does it without script, and the rule holds for
   every popover, not only for this one \(Owner, 1 September 2026\).

   `scrollbar-gutter: stable` stands beside it, so that the vanishing of the
   scrollbar does not let the page jump by its width. */
/* **The scrollbar's room is always reserved, not only once it is gone.**
   Exactly here `scrollbar-gutter: stable` stood at first — in the rule
   below, so valid only once the sheet was open. That is too late: the bar
   vanishes at the same moment the reservation takes hold, and the page
   jumps by its width. It has to be there beforehand. */
:root {
    scrollbar-gutter: stable;
}

/* As long as a sheet stands in the top layer, the page beneath does not move.
   A popover does **not** lock scrolling by itself — unlike a <dialog> in
   the modal state. Both elements, because depending on the browser the one
   or the other scrolls. */
:root:has([popover]:popover-open),
:root:has([popover]:popover-open) body {
    overflow: hidden;
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: The theme for Valheim — the four things that change per game.

   Structure is general, colours are specific. A second game lays a file
   beside it and changes nothing else; that is the whole theme capability
   ({K0029}). What stands here is the palette — icons, map and biome colours
   come from the game data and not from the stylesheet. */

@layer tokens {
    [data-theme="valheim"] {
        /* The stage bar — copper, like the first ore one smelts in the game.
           Eight bands with hard edges, symmetrical around the middle:
           measured from the design, not imitated. The hue moves from 38 at
           the edges to 55 in the middle, which gives the metal its shimmer —
           a gradient on a single tone would look like a colour area, not
           like light. */
        /* The scanlines lie above the gradient: one pixel of black at 28
           percent, two pixels of gap. That is the screen the type means —
           without them the bar is a colour area with stripes, with them it
           is a monitor. Measured from the design.

           As a second background layer instead of an absolutely positioned
           pseudo-element: saves a node and cannot slip.

           A plain gradient with a fixed tile size, not repeating: a
           continuous gradient stretches over the whole area and rounds its
           periods against the device pixel grid — every few lines one drops
           out. With `background-size: 100% 3px` the tile is exactly three
           pixels tall and repeats cleanly. */
        --stage-scan:
            linear-gradient(180deg,
                oklch(0% 0 0 / 0.28) 0 1px,
                transparent 1px 3px);

        /* The peak stood at 74 percent and was the lightest spot of the bar —
           exactly where the title stands. Measured that gave 2.19 to 1
           against the title colour, needed are 3.0 for large type. The row
           only crossed the limit from 64 percent on; 62 gives reserve, so
           that a later type size does not tear it again at once.

           The bar so becomes somewhat richer and less glaring. That is a
           deviation from the design, it stands here, and it is measured — as
           AW22 demands. The design meets no AA at this point. */
        --stage-bar:
            linear-gradient(180deg,
                oklch(30% 0.09 40) 0 14%,
                oklch(45% 0.14 42) 0 28%,
                oklch(58% 0.18 45) 0 42%,
                oklch(62% 0.17 55) 0 50%,
                oklch(58% 0.18 45) 0 58%,
                oklch(45% 0.14 42) 0 72%,
                oklch(28% 0.08 38) 0 86%,
                oklch(16% 0.05 35) 0 100%);
        --stage-ink:   oklch(97% 0.02 40);

        /* The ten biomes. The order is that of the game's progress, not of
           the alphabet — whoever reads them reads the way. */
        --biome-meadows:   oklch(74% 0.14 130);
        --biome-forest:    oklch(52% 0.10 150);
        --biome-swamp:     oklch(44% 0.07  95);
        --biome-mountain:  oklch(88% 0.03 230);
        --biome-plains:    oklch(80% 0.13  85);
        --biome-mistlands: oklch(46% 0.05 280);
        --biome-ashlands:  oklch(58% 0.19  35);
        --biome-deepnorth: oklch(84% 0.05 210);
        --biome-ocean:     oklch(56% 0.10 235);
        --biome-edge:      oklch(38% 0.02 260);
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: The beta mark — a Workbench gadget in red. */

@layer elements.beta_badge {
    @scope ([data-type="beta-badge"]) {
        /* The three reds stand here and not in tokens.css. The sentence above
           once read »waechst daraus eine zweite Stelle, wandern sie hoch«,
           and with the status window \({T0097}\) the second place came — the
           move into `:root` was wrong all the same. **AW10 and {K0004} forbid
           derivations in `:root`**: a computed tone stands at every context
           that sets the base colour, so that it follows when that changes.
           The status window computes the same line itself. The same
           calculation in two places is the price, and it is smaller than a
           value that freezes.

           Chosen after the Amiga: the face is the palette's red darkened, the
           light edge lies above it, the shadow edge below. White on the
           ground measures 6.66 to 1 and so carries even the smallest step. */
        :scope {
            /* **Computed from the palette, not written beside it**
               ({T0096}, AW10). Three red values of their own stood here, and
               the sentence above called them »das satte Rot der Palette« —
               they were not: `--danger` stands at 66 % lightness, here stood
               50, and the hue differed by a degree. No design intent, a
               copying error.

               The face is darker than `--danger`, and that stays: it carries
               white type, and for that it needs the distance. The two edges
               compute symmetrically from it — thirteen points lighter above,
               thirteen darker below. */
            --badge-face:   oklch(from var(--danger) 0.50 c h);
            --badge-light:  oklch(from var(--badge-face) calc(l + 0.13) c h);
            --badge-shadow: oklch(from var(--badge-face) calc(l - 0.13) c h);

            display: inline-block;
            padding: 0.375rem 0.5rem 0.3125rem;
            background: var(--badge-face);
            color: oklch(100% 0 0);

            /* The Workbench box: a black line outside, the two edges inside.
               Both without a radius — there is no rounded corner on a device
               with 320 by 256 points. */
            border: 1px solid oklch(0% 0 0);
            box-shadow:
                inset  2px  2px 0 0 var(--badge-light),
                inset -2px -2px 0 0 var(--badge-shadow);

            font-family: "Press Start 2P", monospace;
            font-weight: 400;
            font-size: 0.5rem;
            line-height: 1;
            letter-spacing: 0.08em;
            text-transform: uppercase;

            /* The baseline of the pixel font sits low; without the correction
               the mark hangs under the word mark instead of beside it. */
            translate: 0 -0.0625rem;
        }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: A button — the pixel edge from the design. */

@layer elements.button {
    @scope ([data-type="button"]) {
        :scope {
            display: grid;
            place-items: center;
            block-size: var(--control-height);
            padding-inline: 0.625rem;
            background: var(--link);
            border: 0;
            color: oklch(20% 0.05 130);
            font-family: "Press Start 2P", monospace;
            font-size: 0.8125rem;
            line-height: 1;
            text-transform: uppercase;
            cursor: pointer;

            /* The edge is the whole button: two inner shadows, light top left and
               dark bottom right, two pixels wide. That is the form a button
               had in a game of that time — no frame, no rounding, no query.
               Measured from the design, values unchanged. */
            box-shadow:
                inset  2px  2px 0 oklch(88% 0.12 130),
                inset -2px -2px 0 oklch(52% 0.14 130);
        }

        /* **Locked: the same form, only without colour and without edge.**
           A button that cannot act yet does not vanish and does not move —
           it stands where it will stand, and only says that it is not ready
           yet. Otherwise the line below jumps at the moment one looks.

           The pixel edge drops, because it says »press me«; the face becomes
           the sheet's, the type loses its contrast. `cursor` does not stay on
           `pointer`, because nothing happens (Owner, 11 September 2026: the
           button only turns green once the picture is done). */
        :scope:disabled {
            background: var(--surface-1, oklch(32% 0.01 260));
            color: var(--ink-muted, oklch(62% 0.01 260));
            box-shadow: none;
            cursor: default;
        }

        /* Gold instead of green: the same shape, another role. The edges follow
           the tone, otherwise a green setting would sit around a golden face. */
        :scope[data-tone="gold"] {
            background: var(--gold);
            color: oklch(24% 0.06 85);
            box-shadow:
                inset  2px  2px 0 oklch(90% 0.09 85),
                inset -2px -2px 0 oklch(58% 0.11 85);
        }

        :scope[data-tone="gold"]:where(:hover, :focus-visible) {
            background: oklch(from var(--gold) calc(l + 0.05) c h);
        }

        /* Red: the button takes something away ({T0073S13}) - in the target's
           sheet the way out of the building plan back to the item page. The
           same shape, the edges follow the tone. */
        :scope[data-tone="red"] {
            background: var(--danger);
            color: oklch(18% 0.05 28);
            box-shadow:
                inset  2px  2px 0 oklch(82% 0.14 28),
                inset -2px -2px 0 oklch(46% 0.17 28);
        }

        :scope[data-tone="red"]:where(:hover, :focus-visible) {
            background: oklch(from var(--danger) calc(l + 0.05) c h);
        }

        /* As a link: the browser's text decoration off, the button's shape
           stays. */
        a:scope { text-decoration: none; }

        /* The button carries the action colour, the link the link colour — the
           two greens and their roles stand in {K0032}. */
        :scope:where(:hover, :focus-visible) {
            background: oklch(from var(--link) calc(l + 0.05) c h);
        }

        /* **The sign** ({T2001S6}, the seed form in the map options drawer
           after the design of 28 September 2026): the same button, square -
           as wide as every control is tall - with one symbol on the face
           instead of a word. Face, pixel edge, tone and the answer on
           pointing are the word form's and stand above; a first cut framed
           the symbol in the tone's colour on dark ground, and the owner sent
           it back the same day: »wie auch vorher zu vollfarbigen Buttons mit
           schwarzem Symbol«. The symbol stands in the page's type and not in
           the pixel face, which has no ✓ and no ↻ and would fall back glyph
           by glyph. */
        :scope[data-shape="sign"] {
            inline-size: var(--control-height);
            padding-inline: 0;
            font-family: inherit;
            font-size: var(--text-large);
            text-transform: none;
            /* Twice the stroke ({T2011S2}, the owner on 1 October 2026: »wir
               haben eine seite in pixelart und diese elemente wirken
               hauchdünn«). Oxanium carries neither ✓ nor ↻, so they come from
               the system's face, whose weight a font-weight does not reach.
               A contour in the symbol's own colour does: it lies on the
               outline, half outside and half in, so a contour as wide as the
               stroke doubles it. Measured: the ring of the ↻ is 1.63 points at
               21.6, 0.075 em - and in em it grows with the size at every
               width. */
            -webkit-text-stroke: 0.075em currentColor;
        }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: A switch in mixing-desk form — slider in the track, knob on it. */

@layer elements.checkbox {
    @scope ([data-type="checkbox"]) {
        :scope {
            display: flex;
            align-items: center;
            gap: 0.5rem;
            color: var(--ink);
            cursor: pointer;
            user-select: none;
        }

        /* Drawn ourselves and not `accent-color`.
           
           Before, the opposite stood here, with good reason: the browser
           draws a box in its own form and knows every state. But this form
           does not exist as a box — a slider whose knob protrudes over the
           track, as on a mixing desk. Whoever wants a shape that does not
           exist has to draw it.
           
           What is kept: it is still an `<input type=
           "checkbox">`. Keyboard, screen reader and form submission notice
           nothing of it looking different. */
        input {
            appearance: none;
            margin: 0;
            position: relative;
            inline-size: 2.25rem;
            /* The track is narrow — the knob above it is the thing. */
            block-size: 0.5rem;
            background: oklch(18% 0.008 260);
            cursor: pointer;

            /* The track lies recessed: dark top left, light bottom right. The
               same direction as with the input field, and for the same
               reason — the light falls from the same corner for everything. */
            box-shadow:
                inset 1.5px 1.5px 0 oklch(9% 0.008 260),
                inset -1.5px -1.5px 0 oklch(30% 0.010 260);
        }

        /* The knob: taller than the track, so that it sits on it instead of
           in it. Off it stands on the left and is dark metal, on it stands
           on the right and carries the action colour — the same as the
           search button, because both say the same: here something is live. */
        input::before {
            content: "";
            position: absolute;
            inset-block: -0.3125rem;
            inset-inline-start: 0;
            inline-size: 1.125rem;
            background: oklch(32% 0.010 260);
            box-shadow:
                inset  2px  2px 0 oklch(46% 0.012 260),
                inset -2px -2px 0 oklch(16% 0.008 260);
            transition: inset-inline-start 90ms steps(3), background 90ms;
        }

        input:checked::before {
            inset-inline-start: calc(100% - 1.125rem);
            background: var(--link);
            box-shadow:
                inset  2px  2px 0 oklch(88% 0.12 130),
                inset -2px -2px 0 oklch(52% 0.14 130);
        }

        /* No soft gliding: `steps(3)` snaps in three steps, like a slider
           running over detents. Whoever reports reduced motion gets none
           at all. */
        @media (prefers-reduced-motion: reduce) {
            input::before { transition: none; }
        }

        input:where(:focus-visible) {
            outline: 2px solid var(--link);
            outline-offset: 4px;
        }

        /* Gold instead of green where two rows side by side switch different
           things ({T0093}): on the map the layers in green, the biomes in
           gold. Only the knob changes colour — form, way and snapping stay,
           because it is the same control form and not a second one.

           The highlights are computed from the base colour and not written
           beside it ({K0004}), with the same distances as with the green:
           twelve points lighter top left, twenty-four darker bottom right.
           A tone that stands there twice differs the third time. */
        :scope[data-tone="gold"] input:checked::before {
            background: var(--gold);
            box-shadow:
                inset  2px  2px 0 oklch(from var(--gold) calc(l + 0.12) c h),
                inset -2px -2px 0 oklch(from var(--gold) calc(l - 0.24) c h);
        }

        /* The switched-on state colours the label too: whoever looks across
           the row sees without comparing what is on. */
        input:checked ~ span { color: var(--ink-bright); }

        /* A switch that has no effect accepts no pointer either. What it looks
           like then is decided by the page that disables it — here stands
           only that it rests. The screen reader says so anyway. */
        :scope:has(input:disabled) { cursor: default; }
        input:disabled { cursor: default; }

        /* Two more forms of the same switch ({T2001S5}): the owner drew the
           map options' marks as pictures, cut from the design round by
           `render marks` without a change of pixel. A form is a drawing -
           the input, the label and the keyboard are the mixing desk's.

           **`mark`**: the black box with the grey rim, the green tick when
           on. The box is 27 by 27 on the picture; here it stands at
           `--mark`, a step of the scale, and the picture scales with it. */
        :scope[data-shape="mark"] input {
            inline-size: var(--mark, 0.875rem);
            block-size: var(--mark, 0.875rem);
            background: url("/icons/switch-off.webp") center / contain no-repeat;
            box-shadow: none;
            /* The mark keeps its size whatever the name beside it does: a
               flex item gives way by default, and a long name that wrapped
               squeezed the box to a sliver (owner, 28 September 2026: »die
               Checkboxen dürfen sich nicht kleiner skalieren, weil sie
               gedrängt werden«). */
            flex: none;
        }
        :scope[data-shape="mark"] input::before { content: none; }
        :scope[data-shape="mark"] input:checked {
            background-image: url("/icons/switch-on.webp");
        }
        /* **`knob`**: the square knob on its short track - left and grey when
           none of the group is on, right with a green rim when some, right
           and filled when all. Three pictures of one frame, 46 by 32, so the
           knob never moves on screen: the picture changes. Which one stands
           is the group's to say - it sets `--knob-picture` by :has() over its
           switches - and the knob does not know its group; without a
           container it shows none. The width is `--knob`, the height follows
           from the frame. */
        :scope[data-shape="knob"] input {
            inline-size: var(--knob, 1.375rem);
            block-size: calc(var(--knob, 1.375rem) * 32 / 46);
            background: var(--knob-picture, url("/icons/group-none.webp")) center / contain no-repeat;
            box-shadow: none;
        }
        :scope[data-shape="knob"] input::before { content: none; }
        :scope[data-shape="knob"] input:checked ~ span,
        :scope[data-shape="mark"] input:checked ~ span { color: var(--ink-bright); }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: Short labels — without a frame, the colour carries the statement. */

@layer elements.chip {
    @scope ([data-type="chip"]) {
        :scope {
            font-family: ui-monospace, monospace;
            font-size: var(--text-micro);
            letter-spacing: 0.16em;
            text-transform: uppercase;
            color: var(--link);

            /* No frame and no face. A label that already stands out of the text
               through type, colour and capitals needs no second carrier —
               the frame costs room and carries nothing. */
        }

        /* Gold instead of green, as on the button and the switch ({K0032},
           {T0093}). On the map it separates the switch group that asks for
           the place from the one that asks for the kind: the label carries
           the same tone as the switches below. Only the colour changes —
           type, tracking and capitals stay, because it is the same label. */
        :scope[data-tone="gold"] { color: var(--gold); }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: A button in the corner of a card — flat, thin grey frame. */

@layer elements.corner_button {
    @scope ([data-type="corner-button"]) {
        /* As large as the symbol of the mark in the head line: 1.875 rem. The
           owner named that as the measure so that the symbols stay
           recognisable — below it a chevron becomes a stroke. */
        :scope {
            display: grid;
            place-items: center;
            inline-size: 1.875rem;
            aspect-ratio: 1;
            padding: 0;
            background: none;

            /* Flat, with a thin grey frame. No face and no colour on the button
               itself: two coloured tiles side by side in the corner of a
               card that already carries a biome colour would be one colour
               too many. The frame says »here is something to press«, the
               symbol says what. */
            border: 1px solid oklch(from var(--ink-quiet) l c h / 0.45);

            color: var(--ink-quiet);
            font-size: 1.0625rem;
            line-height: 1;
            cursor: pointer;
        }

        /* The colour is carried by the symbol, not the button. */
        :scope[data-tone="action"] { color: var(--link); }
        :scope[data-tone="gold"]   { color: var(--gold); }

        /* On pointing the frame approaches the colour of the symbol — the only
           movement that is none: nothing jumps, nothing grows. */
        :scope:where(:hover, :focus-visible) {
            border-color: currentColor;
            background: oklch(from currentColor l c h / 0.12);
        }

        :scope:where(:focus-visible) {
            outline: 2px solid var(--link);
            outline-offset: 2px;
        }

        /* The chevron points right as long as it is closed, and turns down on
           opening — the usual unfold pointer. The rotation sits on the
           symbol and not on the button, so that the frame stays put. */
        :scope[data-part="toggle"] span {
            display: block;
            transition: rotate 120ms ease;
        }

        @media (prefers-reduced-motion: reduce) {
            :scope[data-part="toggle"] span { transition: none; }
        }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: The donate button — white, rounded, the cup in front. */

@layer elements.donate_button {
    @scope ([data-type="donate-button"]) {
        :scope {
            /* The measure comes from the context, the form the button computes
               from it: rounding, padding and cup grow with the height.
               Without a value it is as tall as every control; the map's
               footer strip gives it a lower one. */
            --height: var(--donate-height, var(--control-height));

            /* Symbol beside text: the one row for which flex is allowed. */
            display: inline-flex;
            align-items: center;
            gap: calc(var(--height) * 0.22);
            block-size: var(--height);
            padding-inline: calc(var(--height) * 0.3) calc(var(--height) * 0.42);

            /* White and Ko-fi's dark, 32 32 32 in RGB — measured 16 to 1. */
            background: oklch(100% 0 0);
            color: oklch(24.4% 0 0);
            border-radius: calc(var(--height) * 0.3);

            font-size: var(--donate-font, var(--text-small));
            font-weight: 700;
            line-height: 1;
            white-space: nowrap;
            text-decoration: none;
        }

        img {
            block-size: calc(var(--height) * 0.52);
            inline-size: auto;
        }

        /* On pointing the face steps back a touch and the cup wobbles, as on
           Ko-fi itself. The face jumps, the cup moves only through
           transform ({K0036}). */
        :scope:where(:hover, :focus-visible) {
            background: oklch(93% 0 0);
        }

        :scope:where(:hover, :focus-visible) img {
            animation: cup-wobble 600ms ease-in-out;
        }

        /* The focus ring reads the one green like every other ({K0234}). */
        :scope:focus-visible {
            outline: 2px solid var(--link);
            outline-offset: var(--space-tight);
        }

        @media (prefers-reduced-motion: reduce) {
            :scope:where(:hover, :focus-visible) img { animation: none; }
        }
    }

    /* `@keyframes` knows no scope and therefore stands beside it, with a
       name that says whom it belongs to. */
    @keyframes cup-wobble {
        0%, 100% { transform: rotate(0deg); }
        25%      { transform: rotate(-12deg); }
        50%      { transform: rotate(10deg); }
        75%      { transform: rotate(-6deg); }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: The handle bar at the lower edge of a card. */

@layer elements.drawer_handle {
    @scope ([data-type="drawer-handle"]) {
            /* The handle bar at the lower edge — sixteen pixels, darkened, and it
               lies deeper than the card.

               »Deeper« is no colour here but an edge: the shadow sits at the
               top and inside, as with everything recessed in this design.
               The light falls from the same corner for field, track and bar,
               and whoever swaps the direction turns the recess into a rise.

               Across the whole width and not as a button in the corner: a
               strip is a larger target, and its position says where the card
               opens to. */
            :scope {
                position: absolute;
                inset-inline: 0;
                inset-block-end: 0;
                display: grid;
                place-items: center;
                /* The height comes from what carries the handle — the card reckons
                   with it when it centres its content. The fallback stands
                   beside it so that the element stands alone too: a
                   component that collapses without its caller is none. */
                block-size: var(--handle, 16px);
                padding: 0;
                border: 0;
                background: oklch(0% 0 0 / 0.30);
                box-shadow:
                    inset 0  2px 0 oklch(0% 0 0 / 0.38),
                    inset 0 -1px 0 oklch(100% 0 0 / 0.05);
                cursor: pointer;
            }

            /* The chevron drawn, not set: an angle of two edges on a rotated
               square. A glyph would work too, but »stretched wide« would
               then mean distorting a glyph — and a distorted glyph looks like
               an error, while a drawn line is simply wide.

               `transform` and not the single properties `rotate`/`scale`:
               there the order is fixed, and `scale` would come after the
               rotation, so stretch the diagonal instead of the width. Here
               `scaleX` stands first and so acts from outside. */
            :scope::before {
                content: "";
                inline-size: 5px;
                block-size: 5px;
                /* Light green, not grey (Owner, 1 September 2026). On the darkened
                   bar it is the only coloured thing, and so the eye finds the
                   handle without it needing a face. The primary colour —
                   there is only one ({T0096}). */
                border-inline-end: 2px solid var(--link);
                border-block-end: 2px solid var(--link);
                transform: translateY(-2px) scaleX(2.4) rotate(45deg);
            }

            /* On pointing it gets lighter rather than different: a second colour
               would mean the handle did something else. */
            :scope:where(:hover, :focus-visible)::before {
                border-color: oklch(from var(--link) calc(l + 0.08) c h);
            }

            :scope:where(:focus-visible) {
                outline: 2px solid var(--link);
                outline-offset: -2px;
            }

        @media (prefers-reduced-motion: no-preference) {
            :scope::before { transition: transform 140ms ease; }
        }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: An input field — recessed, with its label above. */

@layer elements.field {
    @scope ([data-type="field"]) {
        :scope {
            display: grid;
            gap: 0.4375rem;
            min-inline-size: 0;
        }

        /* The chip otherwise carries the link colour — here it is a field
           label and no statement, so muted and 10 px. Measured from the
           design. */
        [data-type="chip"] {
            font-size: 0.625rem;
            letter-spacing: 0.12em;
            color: var(--ink);
        }

        input {
            min-inline-size: 0;
            /* The height comes from the token and not from the padding: so it is
               the same as the button's, even if a typeface falls differently.
               The padding only aligns horizontally now. */
            block-size: var(--control-height);
            padding: 0 0.75rem;
            background: var(--surface-0);
            border: 0;
            color: oklch(90% 0.005 90);
            font-family: ui-monospace, monospace;
            font-size: 0.8125rem;
            letter-spacing: 0.04em;

            /* The edge lies the other way round from the button: dark top left,
               light bottom right. That is no ornament but the statement — a
               button stands out, a field is recessed, and the light falls
               from the same direction for both. Whoever swaps the order turns
               the field into a button nobody can press. Measured from the design. */
            box-shadow:
                inset  1.5px  1.5px 0 oklch(9% 0.008 var(--hue-surface)),
                inset -1.5px -1.5px 0 oklch(32% 0.014 var(--hue-surface));
        }

        /* min-inline-size: 0 on the field is no ornament: an input field brings
           a preset minimum width that overrides a 1fr track and pushes the
           line out of the grid on the phone. */

        input::placeholder { color: var(--ink-quiet); }

        /* The focus ring lies inside, so that it does not cover the edge: the
           field keeps its recess and gets a setting.

           In the colour of the button beside it, not in the action colour:
           field and button are a pair, and a field that carries another
           green in focus than the button that submits it looks like two
           components (Owner, 2 September 2026). The same token as `button`,
           so that it is and stays exactly the same green. */
        input:where(:focus-visible) {
            outline: 2px solid var(--link);
            outline-offset: -2px;
        }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: Item icons from the game. */

@layer elements.game_icon {
    @scope ([data-type="game-icon"]) {
        :scope {
            inline-size: var(--space-wide);
            block-size: auto;

            /* The sources are pixel graphics. Without this line the browser
               interpolates when scaling up and turns hard edges into mush —
               the same problem as with the pixel font, only without rounding
               as a way out \({K0044}\). */
            image-rendering: pixelated;
        }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: The fonts — served ourselves, with a metric fallback.

   No element in the document head is rendered, hence no scope stands here:
   what this package contributes in CSS is the font declarations themselves. */

@layer tokens {
    @font-face {
        font-family: "Oxanium";
        src: url("/fonts/oxanium.woff2") format("woff2");
        font-weight: 200 800;
        font-display: swap;
    }

    @font-face {
        font-family: "Press Start 2P";
        src: url("/fonts/press-start-2p.woff2") format("woff2");
        font-weight: 400;
        font-display: swap;
    }

    /* The metric fallback: it takes the jump out of the font swap without
       hiding the text ({K0039}). The values are measured on Oxanium — ascent
       790, descent 210, line gap 250 of 1000 units. */
    @font-face {
        font-family: "Oxanium Fallback";
        src: local("Helvetica Neue"), local("Arial");
        ascent-override: 79%;
        descent-override: 21%;
        line-gap-override: 25%;
        size-adjust: 99%;
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: Headings — pixel font, rounded onto its grid. */

@layer elements.heading {
    @scope ([data-type="heading"]) {
        :scope {
            font-family: "Press Start 2P", monospace;
            font-weight: 400;
            color: var(--ink-bright);
            text-wrap: pretty;

            /* The pixel font has no descender in the metric sense, hence the line
               height always stands written out here \({K0044}\). 1.4 leaves
               exactly half a glyph height between two lines. */
            line-height: 1.55;
        }

        /* NO rounding onto the pixel grid. It was intended here and is wrong
           here: the design sets 28, 22 and 13 px, and round\(\) shifts each
           of these values to the next multiple — 27.5 would become 24, 13
           would become 16. Where the design sets a size, it holds; the
           rounding stays reserved for what we choose ourselves ({K0059}).

           The ladder hits the values almost exactly anyway: 27.5 against 28,
           21.6 against 22, 13.4 against 13. */
        :scope:is(h1) { font-size: var(--text-head); }
        :scope:is(h2) { font-size: var(--text-head); }
        :scope:is(h3) { font-size: var(--text-large); }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: A button that carries only a symbol. */

@layer elements.icon_button {
    @scope ([data-type="icon-button"]) {
        :scope {
            display: grid;
            place-items: center;
            inline-size: var(--space-open);
            aspect-ratio: 1;
            background: none;
            border: 0;
            color: var(--ink-quiet);
            font-size: var(--text-large);
            line-height: 1;
            cursor: pointer;
        }

        /* No face, no frame: the colour is the whole feedback. A button
           without a face that got one on pointing would jump — and the
           action colour says the same without touching the layout. */
        :scope:where(:hover, :focus-visible) { color: var(--link); }

        /* **The × in the head of a sheet** ({T0084}, Owner on 11 September
           2026: »der schliessen button im popover für download und für share
           entspricht nicht dem was im item popover zu sehen ist«).

           It is twice as large as an ordinary symbol, because it stands as
           the only control in a head line that otherwise carries only text
           — and because a sheet must let itself be closed before one looks
           for where.

           Golden on pointing and not green: green is the action colour, and
           closing is no action but its end. The colour of the sheet frame
           says »this sheet« instead.

           The measures stood in the item sheet and nowhere else, which is
           why the map sheets carried a smaller ×. Here they stand once, and
           every sheet gets the same; **where** it sits, the sheet still
           decides. */
        :scope[data-role="close"] {
            inline-size: calc(var(--space-open) * 2);
            font-size: calc(var(--text-large) * 2);
        }

        :scope[data-role="close"]:where(:hover, :focus-visible) {
            color: var(--gold);
        }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: Links — one place where colour, focus and state are fixed. */

@layer elements.link {
    @scope ([data-type="link"]) {
        :scope {
            color: var(--link);
            text-decoration: none;
        }

        /* The focus ring stands here and only here. That is the real gain of
           the element: one place instead of four, and no chance that one of
           them is forgotten. :focus-visible instead of :focus, so that it
           does not appear with the mouse — whoever clicks sees where they
           clicked. */
        :scope:where(:focus-visible) {
            outline: 2px solid var(--link);
            outline-offset: var(--space-tight);
        }

        :scope:where(:hover) {
            text-decoration: underline;
            text-underline-offset: var(--space-tight);
        }

        /* The current page is read from the state, not from a class:
           aria-current tells the screen reader anyway, and a second carrier
           of the same statement could differ from it \({K0005}\). */
        :scope[aria-current] {
            text-decoration: underline;
            text-underline-offset: var(--space-tight);
        }

        /* The chevron moves on hovering, not the whole link: a movement through
           transform that does not touch the layout \({K0036}\). */
        > span {
            display: inline-block;
            margin-inline-start: 0.35em;
            transition: transform 120ms ease-out;
        }

        :scope:where(:hover, :focus-visible) > span {
            transform: translateX(0.15em);
        }

        @media (prefers-reduced-motion: reduce) {
            > span { transition: none; }
            :scope:where(:hover, :focus-visible) > span { transform: none; }
        }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: The symbol of the mark — face plus mask, both colours from the theme. */

@layer elements.logo_picture {
    @scope ([data-type="logo-picture"]) {
        :scope {
            display: grid;
            place-items: center;
            inline-size: 1.875rem;
            aspect-ratio: 1;
            background: var(--brand);

            /* The same edge as the start button, only narrower: one and a half
               pixels. It turns the field into an object instead of a colour
               area. Measured from the design. */
            box-shadow:
                inset  1.5px  1.5px 0 oklch(from var(--brand) calc(l + 0.14) c h),
                inset -1.5px -1.5px 0 oklch(from var(--brand) calc(l - 0.16) c h);
        }

        /* The signet as a mask on a face in the base colour. No extra node,
           and a theme change alters two variables. */
        /* 62 percent. The value was never the problem — the symbol's viewBox
           was: it lay on the advance instead of on the ink, and the last 125
           units of that are the distance to the next letter. The symbol so
           sat on the left in the field, and enlarging covered the error
           instead of fixing it. Since the viewBox lies on the ink, it sits
           centred, and 62 percent give it air again. */
        :scope::before {
            content: "";
            inline-size: 62%;
            aspect-ratio: 1;
            background: var(--ground);
            mask-image: url("/brand/signet.svg");
            mask-size: contain;
            mask-repeat: no-repeat;
            mask-position: center;
        }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: The word mark. */

@layer elements.logo_text {
    @scope ([data-type="logo-text"]) {
        :scope {
            font-family: "Press Start 2P", monospace;
            font-weight: 400;
            font-size: 0.875rem;
            line-height: 1;
            letter-spacing: 0.04em;
            color: var(--ink);
        }

        /* b and i carry no emphasis here but markup without a voice — exactly
           what the two elements are meant for since HTML5. The italic is
           taken back, because the pixel font has none and the browser would
           otherwise slant it. */
        > i {
            font-style: normal;
            color: var(--brand);
        }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: Metric above label. */

@layer elements.metric {
    @scope ([data-type="metric"]) {
        :scope {
            display: grid;
            gap: var(--space-tight);
        }

        /* The value stands in the markup before the label, because it is the
           statement — the label explains it. In the grid they stand in the
           same order; there is no reason to swap them visually. */
        > dd {
            margin: 0;
            font-family: ui-monospace, monospace;
            font-size: var(--text-large);
            color: var(--link);
        }

        > dt {
            font-family: ui-monospace, monospace;

            /* 12 px lies between two steps of the ladder — 10.5 and 13.4. The
               design sets it so, and AW22 agrees: a label under a number
               needs weight without competing with it. */
            font-size: var(--text-label);
            letter-spacing: 0.08em;
            text-transform: uppercase;
            color: var(--ink-quiet);
        }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: The picture follows its track and keeps its aspect ratio. */

@layer elements.picture {
    @scope ([data-type="picture"]) {
        :scope {
            display: block;
            max-inline-size: 100%;
            block-size: auto;
        }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: A pill — radio button with rank colour, the golden triangle above the chosen one. */

@layer elements.pill {
    @scope ([data-type="pill"]) {
        :scope {
            /* The rank as colour. White is the resting value and at the same
               time the first rank — hence the colour cannot also say active
               against inactive: it would do so twice. Active is said by the form. */
            --pill: var(--rank-1);
            position: relative;
            display: inline-grid;
            /* Room for the triangle above the pill, in its own box and not as a
               negative offset (AW5). */
            padding-block-start: var(--space-snug);
            cursor: pointer;
        }

        :scope[data-rank="2"] { --pill: var(--rank-2); }
        :scope[data-rank="3"] { --pill: var(--rank-3); }
        :scope[data-rank="4"] { --pill: var(--rank-4); }

        /* The radio button stays a radio button — keyboard, screen reader and
           group notice nothing —, but lies invisibly over the whole pill,
           so that every hit counts. */
        input {
            appearance: none;
            position: absolute;
            inset: 0;
            margin: 0;
            opacity: 0;
            cursor: pointer;
        }

        /* The pill itself: an outline in the rank colour, the digit in it.
           Measured from the design — outlined, not filled, with a slightly
           broken corner; the one and a half pixels are the edge of mark and
           field ({K0062}). */
        span {
            display: grid;
            place-items: center;
            min-inline-size: 2.4em;
            block-size: 1.6em;
            padding-inline: 0.4em;
            border: 1.5px solid var(--pill);
            border-radius: 0.2em;
            color: var(--pill);
            font-family: ui-monospace, monospace;
            font-size: var(--text-micro);
            line-height: 1;
            font-variant-numeric: tabular-nums;
        }

        /* The golden triangle above the chosen pill, centred, with the point
           downwards (Owner, 2 September 2026). Gold in the sheet means
           »belongs here« — corner, frame, places —, and the chosen level
           belongs to it. Two edges without a face give the form; an SVG
           would be a file for three strokes. */
        :scope::before {
            content: "";
            position: absolute;
            inset-block-start: 0;
            inset-inline: 0;
            margin-inline: auto;
            inline-size: 0;
            block-size: 0;
            border-inline: 0.3em solid transparent;
            border-block-start: 0.35em solid var(--gold);
            opacity: 0;
        }

        :scope:has(:checked)::before { opacity: 1; }

        /* The focus ring in the action colour, outside around the pill —
           distinguished from the triangle above by form, place and colour. */
        input:where(:focus-visible) ~ span {
            outline: 2px solid var(--link);
            outline-offset: 2px;
        }

        /* On pointing a touch of its own colour on the face: says »this one is
           meant« without changing the rank. */
        :scope:hover span {
            background: color-mix(in oklab, var(--pill) 12%, transparent);
        }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: The progress bar — frame, fill, size. */

@layer elements.progress {
    @scope ([data-type="progress"]) {
        /* A hundred by fifteen pixels: one percent per pixel, as the owner
           fixed it. That is the one place where a pixel number is right —
           it is the statement, not the measure. */
        :scope {
            inline-size: 100px;
            block-size: 15px;
            appearance: none;
            border: 1px solid oklch(72% 0.13 85);
            background: oklch(20% 0.02 260 / 0.8);
        }

        /* Two spellings for the same thing: WebKit and Firefox name the fill
           differently, and a selector a browser does not know throws the
           whole rule away in some versions. */
        :scope::-webkit-progress-bar {
            background: oklch(20% 0.02 260 / 0.8);
        }
        :scope::-webkit-progress-value {
            background: var(--gold, oklch(80% 0.15 85));
            transition: inline-size 0.2s linear;
        }
        :scope::-moz-progress-bar {
            background: var(--gold, oklch(80% 0.15 85));
        }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: Running text. */

@layer elements.prose {
    @scope ([data-type="prose"]) {
        :scope {
            font-size: var(--text);
            line-height: 1.6;
            color: var(--ink);

            /* In ch, not in rem: the line length is a question of characters per
               line and not of width in pixels. 66 is the upper edge of what
               still reads comfortably. */
            max-inline-size: 58ch;
            text-wrap: pretty;
        }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: A square button with a face for a picture or a symbol ({T0106}). */
@layer elements.quick_button {
    @scope ([data-type="quick-button"]) {
        /* **It looks like `button`, only square and without a word** (Owner,
           14 September 2026: »der goldene button sieht exakt genauso aus wie
           der zufall-button im geoeffneten, nur eben mit dem wuerfel darauf«).
           So the same pixel edge: two inner shadows, light top left and dark
           bottom right, two pixels wide, the values from button.css
           unchanged. The size is said by the caller through `--quick-size`;
           the resting value is the height of a button, so that it could
           stand beside one. In the panel's strip it is as wide as the strip
           minus the edge. */
        :scope {
            display: grid;
            place-items: center;
            inline-size: var(--quick-size, var(--control-height));
            /* Square as long as the caller gives no height; in the panel's strip
               two buttons share the height (Owner, 14 September 2026:
               »vertikal im grid mit gap, aber beide den platz nutzend«),
               there the caller sets --quick-block. */
            block-size: var(--quick-block, var(--quick-size, var(--control-height)));
            padding: 0;
            border: 0;
            text-decoration: none;
            line-height: 1;
            cursor: pointer;
            /* The quiet tone: dark grey on which the edge stays visible (Owner:
               »mit einem dunklen grau, das die erhabenheit gut sichtbar
               macht«). The edges follow the tone as with every button: light
               out of the face, dark into it. */
            background: oklch(34% 0.012 260);
            color: var(--gold);
            box-shadow:
                inset  2px  2px 0 oklch(52% 0.012 260),
                inset -2px -2px 0 oklch(20% 0.012 260);
        }
        :scope:where(:hover, :focus-visible) {
            background: oklch(39% 0.012 260);
        }
        /* Gold: face, type and edges as on the random button (button.css, tone
           gold) - values unchanged, so that both carry the same setting. */
        :scope[data-tone="gold"] {
            background: var(--gold);
            color: oklch(24% 0.06 85);
            box-shadow:
                inset  2px  2px 0 oklch(90% 0.09 85),
                inset -2px -2px 0 oklch(58% 0.11 85);
        }
        :scope[data-tone="gold"]:where(:hover, :focus-visible) {
            background: oklch(from var(--gold) calc(l + 0.05) c h);
        }
        :scope:where(:focus-visible) {
            outline: 2px solid var(--link);
            outline-offset: 2px;
        }
        /* The picture leaves face all around, otherwise the button looks like a
           picture with a border. Seven tenths are measured from the owner's
           design: about 56 on 80. */
        :scope > [data-type="picture"] {
            inline-size: 70%;
            block-size: 70%;
            object-fit: contain;
        }
        /* The golden star of the favourites button: empty as long as nothing
           is remembered, filled afterwards - the same state as on the star
           of the title row, set by the script through data-remembered. The
           size follows the button, not the type of the page. */
        :scope > span {
            font-size: calc(var(--quick-size, var(--control-height)) * 0.55);
        }
        /* The dice arrow is black on gold (Owner: »einen schwarzen reroll
           pfeil«) - the same dark type as the word ZUFALL. */
        :scope[data-tone="gold"] > span { color: oklch(24% 0.06 85); }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: A picture strip as the background of a bar. */

@layer elements.strip {
    @scope ([data-type="strip"]) {
        :scope {
            position: absolute;
            inset: 0;
            inline-size: 100%;
            block-size: 100%;
            object-fit: cover;
            /* Muted, because text stands above it: the picture is mood and not
               content. */
            opacity: 0.55;
        }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: A golden category of the map options — caption, rule, chevron, the
   grid of its groups ({T2001S5}). */

@layer widgets.category_accordion {
    @scope ([data-type="category-accordion"]) {
        :scope {
            /* A container, so the groups answer to the category's width and not
               to the window's: on the phone the drawer sets `--columns` to one
               and the groups stand in one column ({T2001S5}). */
            container-type: inline-size;
            container-name: category;
        }
        /* The caption line: the chip on the left, the chevron on the right,
           the golden rule beneath - drawn, not a glyph. The marker of the
           browser is put away; the chevron says what it said. */
        > summary {
            display: grid;
            grid-template-columns: 1fr auto;
            align-items: center;
            padding-block: var(--space-snug) var(--space-tight);
            border-block-end: 2px solid var(--gold);
            cursor: pointer;
            list-style: none;
            user-select: none;
        }
        > summary::-webkit-details-marker { display: none; }
        /* The note under the caption ({T2004}), in the drawer's quiet micro
           type, as the line above the categories. */
        > [data-part="note"] {
            margin: var(--space-tight) 0 0;
            font-size: var(--text-micro);
            color: var(--ink-quiet);
        }
        > summary::after {
            content: "";
            inline-size: 0.5rem;
            block-size: 0.5rem;
            margin-inline-end: var(--space-tight);
            border-inline-end: 2px solid var(--gold);
            border-block-end: 2px solid var(--gold);
            /* Points down while folded, up while open: the chevron of the
               picture, turned by the state and by nothing else. */
            transform: translateY(-25%) rotate(45deg);
            transition: transform 90ms steps(3);
        }
        :scope[open] > summary::after { transform: translateY(25%) rotate(225deg); }
        @media (prefers-reduced-motion: reduce) {
            > summary::after { transition: none; }
        }
        > summary:where(:focus-visible) {
            outline: 2px solid var(--gold);
            outline-offset: 4px;
        }
        /* Two stacks side by side, each stacking its groups on its own. A
           grid put the groups in rows and left a hole under every short
           group; text columns filled the holes but moved a group from left
           to right the moment another one opened - nothing may jump for
           room (owner, 28 September 2026). Which group stands in which stack
           is the caller's, as the picture has it. Two stacks by default;
           `--columns` is the container's to set - the drawer sets 1 at every
           width since the owner saw the two stacks and chose one column -
           and below 18 rem of the category's own width the stacks stand one
           under the other whatever it says. */
        > [data-part="groups"] {
            display: grid;
            grid-template-columns: repeat(var(--columns, 2), minmax(0, 1fr));
            column-gap: var(--space-snug);
            row-gap: var(--space-tight);
            padding-block: var(--space-snug);
        }
        > [data-part="groups"] > [data-part="stack"] {
            display: grid;
            align-content: start;
            row-gap: var(--space-tight);
        }
        @container category (inline-size < 18rem) {
            > [data-part="groups"] { grid-template-columns: minmax(0, 1fr); }
        }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: The control strip — arrangement, face and the type on it. */

@layer widgets.control_bar {
    @scope ([data-type="control-bar"]) {
        /* `flex-wrap` instead of a breakpoint — when it gets tight, the switches
           slip under the search. The row gap is larger than the column gap,
           so that the two then stay readable as two things instead of one
           crowded one. The same construction as a grid with `auto-fill`: no
           media query guessing a width that flex knows anyway.

           `space-between` and no `margin-inline-start: auto` on the switches.
           Both push them to the right as long as the line is shared — but
           after the wrap they stand alone, and then `auto` sets them at the
           right edge, where there is nothing left to be flush with. */
        :scope {
            display: flex;
            flex-wrap: wrap;
            align-items: end;
            justify-content: space-between;
            gap: 1.5rem 2rem;
            padding: 1rem 1.25rem;
            /* The strip is a container: the switches answer to its width, not
               to the window (how-we-build.md). */
            container-type: inline-size;

            /* Ten percent opacity: the strip sets itself off without becoming a
               face of its own. It stays a lightened cut of the page ground,
               and the page keeps its dark.

               Symmetrical in both modes — in the light the same ten percent
               in black. The statement is »somewhat closer to the viewer«,
               and that holds in both directions. */
            background: light-dark(oklch(0% 0 0 / 0.1),
                                   oklch(100% 0 0 / 0.1));

            /* The pixel edge of a raised face: light top left, dark bottom
               right, like region block and card. */
            box-shadow:
                inset  2px  2px 0 oklch(33% 0.014 var(--hue-surface)),
                inset -2px -2px 0 oklch(12% 0.010 var(--hue-surface));

            /* The three ink steps one step stronger, in both directions.

               The strip lies at about 26 percent lightness in dark mode and
               so above --surface-4; the ladder in tokens.css is checked up
               to --surface-2. --ink-quiet would stand at 3.5 to 1 there and
               fail. On a lightened face the type has to be lightened too.

               Set on the container and not overridden in the elements:
               field, button and checkbox keep reading the same tokens they
               always read — they say something else here. An element added
               tomorrow is right without doing anything. The reasoning stands
               in manual/web/03-tokens.md. */
            --ink:        light-dark(oklch(24% 0.008 var(--hue-surface)),
                                     oklch(82% 0.008 var(--hue-surface)));
            --ink-quiet:  light-dark(oklch(36% 0.008 var(--hue-surface)),
                                     oklch(72% 0.008 var(--hue-surface)));
            --ink-bright: light-dark(oklch(16% 0.005 90),
                                     oklch(95% 0.005 90));
        }

        /* The search does not grow into the free space: a field of nine
           hundred pixels beside six switches does not look like two
           controls but like a botched one. It takes 26 rem and gives of
           that when it gets tight. */
        [data-type="search-form"] { flex: 0 1 26rem; }

        /* The switches, mobile first: a grid with two exactly equal columns
           and an 8 px gap, across the whole width of the strip, so that the
           six switches line up one under the other (Owner, 2 September
           2026). `minmax(0, 1fr)` and not `1fr`: a column may not grow
           wider because its word is longer — otherwise the switches of the
           right column would no longer stand on one line.

           The switch row sits at the height of the field, not at the height
           of its label: the strip aligns at the bottom edge, and without the
           minimum height the switches would stand at the foot of the label. */
        /* The line under the switches ({T0052}) takes a row of the strip of
           its own — otherwise it would stand beside them as soon as the
           strip runs in one row. What it says belongs to the page. */
        [data-part="options-note"] {
            flex: 1 1 100%;
            /* Centred under the switches and sixteen points below (Owner,
               7 September 2026): it belongs to them but does not stand in
               their row. */
            margin: 16px 0 0;
            text-align: center;
        }

        /* The container of the groups carries their spacing ({T0093}, Owner,
           11 September 2026). **A grid with `gap` and no margins on the
           groups themselves**: a `margin` on the second form would hold
           even where there is no first, and would then have to be ruled
           away again. The `gap` holds only between two — if there is only
           one, there is nothing to separate.

           It stands here and not on the strip, because the strip carries
           `display: block` on the map page: there is no search there to lay
           something beside, and a block knows no `gap`. Exactly hence the
           two groups stacked there without any spacing, although a row gap
           stood on the strip.

           `--space-open` is the step above the base step, factor 1.618:
           22.6 to 27.5 points depending on width. It has to be clearly
           larger than the spacing **within** a group — otherwise the label
           visually belongs to the row above instead of its own. */
        [data-part="switches"] {
            display: grid;
            gap: var(--space-open);
            flex: 1 1 100%;
        }

        /* The form carries nothing more than its semantics; the width comes
           from the grid above, the arrangement from the <fieldset> in it. */

        [data-part="switch-group"] {
            /* A <fieldset> brings frame, padding and a minimum width from the
               browser, and all three stand in the grid's way.
               `min-inline-size: 0` is the least known of them: without it a
               fieldset takes `min-content` as the lower bound and cannot be
               computed narrower in a flex or grid child than its longest word. */
            border: 0;
            padding: 0;
            margin: 0;
            min-inline-size: 0;

            display: grid;
            grid-template-columns: repeat(2, minmax(0, 1fr));
            align-items: center;
            gap: 0.5rem;
            min-block-size: var(--control-height);
        }

        /* The section marker (Owner, 11 September 2026): the label in the type
           of every other — monospace, capitals, tracked — and under it the
           line across the full width.

           **The line takes `currentcolor`.** So it never stands in another
           colour than its label without the tone being written twice
           ({K0004}); whoever recolours the group colours both.

           The spacings come from the ladder and not as a number: `tight`
           holds label and line together as one sign, `close` sets the
           distance to the switches. `close` carries the factor 0.618 and is
           so the step of the golden ratio itself — it lies at 8.7, 9.3 and
           10.5 points depending on width, on the narrow one almost exactly
           at the named eight. */
        [data-part="switch-group"] > legend {
            inline-size: 100%;
            padding-block-end: var(--space-tight);
            margin-block-end: var(--space-close);
            border-block-end: 1px solid currentcolor;
        }

        /* From 35 rem of strip width — the tablet step of the ladder, here as
           a container query — the row again, which wraps when it gets
           tight; the row gap stays at 8 px. */
        @container (min-width: 35rem) {
            [data-part="switches"] { flex: 0 1 auto; }

            [data-part="switch-group"] {
                display: flex;
                flex-wrap: wrap;
                gap: 0.5rem 1.5rem;
            }

            /* If two groups stand there, the container takes the full width
               ({T0093}) — otherwise it would slip beside the search and the
               two would be as narrow together as one. Without an upper row
               nothing changes: `:has` then does not apply. */
            :scope:has([data-row="lead"]) [data-part="switches"] {
                flex: 1 1 100%;
            }
        }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: The legal notice — quiet, but readable. */

@layer widgets.footer_legal {
    @scope ([data-type="footer-legal"]) {
        :scope {
            display: grid;
            gap: var(--space-tight);
            justify-items: center;
            text-align: center;

            /* 80 characters — the width at which a line is still taken in at one
               glance. Measured in ch and not in rem: the limit is a number
               of characters, no length, and it stays right when somebody
               sets the type larger. */
            max-inline-size: 80ch;
            margin-inline: auto;

            /* Spacing upwards: the notice belongs to the footer but does not stand
               on one level with name and game list. */
            margin-block-start: var(--space-open);
        }

        /* Smaller than the running text, but not smaller than what still reads
           comfortably. A legal notice nobody can decipher does not serve
           its purpose — it should be quiet, not hidden. */
        :scope :where([data-type="prose"], small) {
            font-size: var(--text-small);
            color: var(--ink-quiet);
            max-inline-size: none;
        }

        > p { margin: 0; }

        /* The compulsory links stand above the notice and side by side: they
           are two targets and no list, and a ul with two entries is an
           enumeration that is none \(AW2\). The spacing separates them from
           the sentences above without drawing a line. */
        [data-part="links"] {
            display: flex;
            gap: var(--space-open);
            justify-content: center;
            flex-wrap: wrap;
            margin-block-start: var(--space-tight);
        }

        /* Tabular figures, so that the year does not flicker.

           **No more capitals.** They stood here as long as the line was a
           signature — Vioxide // Vivian Voss // Anno MMXXVI. The owner set
           the spelling themselves on 9 September 2026 \({T0077}\), and the
           display does not rewrite it: a legal notice shows what stands
           there. The marks carry their capitals in the text \(VGA.RUN,
           VIOXIDE SARL\), the name of the person beside them does not.
           Incidentally, a screen reader often reads continuous capitals as
           an abbreviation. */
        small {
            font-family: ui-monospace, monospace;
            font-variant-numeric: tabular-nums;
            letter-spacing: 0.02em;
        }

        /* The carrier line stands under the notice and quieter than it: it
           names the company, the complete statement stands in the imprint.
           One step smaller suffices for that — below begins what nobody
           reads any more, and a notice nobody deciphers does not serve its
           purpose. */
        [data-part="carrier"] small {
            font-size: 0.8125rem;
            letter-spacing: 0.01em;
        }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: A group of kind switches — the three-state knob and the label in the
   summary, the switches with their tree lines beneath ({T2001S5}). */

@layer widgets.group_accordion {
    @scope ([data-type="group-accordion"]) {
        /* **The three states are the group's to say.** It sets the knob's
           picture by :has() over its own switches, and the knob reads
           `--knob-picture` with none as its default - the container sets a
           property, the object reads it (how-we-build). No class, no
           script: the state is the switches' state. */
        :scope { --knob-picture: url("/icons/group-none.webp"); }
        :scope:has([data-part="kinds"] input:checked) {
            --knob-picture: url("/icons/group-some.webp");
        }
        :scope:not(:has([data-part="kinds"] input:not(:checked))) {
            --knob-picture: url("/icons/group-all.webp");
        }
        /* The summary line: the knob with its label on the left, the chevron
           on the right. The knob's own label is the group's label - the
           checkbox carries the text, the widget writes no word of its own. */
        > summary {
            display: grid;
            grid-template-columns: 1fr auto;
            align-items: center;
            padding-block: var(--space-tight);
            cursor: pointer;
            list-style: none;
            user-select: none;
        }
        > summary::-webkit-details-marker { display: none; }
        > summary::after {
            content: "";
            inline-size: 0.375rem;
            block-size: 0.375rem;
            margin-inline-end: var(--space-tight);
            border-inline-end: 2px solid var(--link);
            border-block-end: 2px solid var(--link);
            transform: translateY(-25%) rotate(45deg);
            transition: transform 90ms steps(3);
        }
        :scope[open] > summary::after { transform: translateY(25%) rotate(225deg); }
        @media (prefers-reduced-motion: reduce) {
            > summary::after { transition: none; }
        }
        > summary:where(:focus-visible) {
            outline: 2px solid var(--link);
            outline-offset: 4px;
        }
        /* The kinds, one under the other, set in from the knob by the tree's
           width; every row carries its line - the └ of the picture, drawn:
           a stem down the left and a branch to the mark. The last row's stem
           stops at its branch. */
        > [data-part="kinds"] {
            display: grid;
            row-gap: 0.125rem;
            margin: 0;
            padding-inline-start: var(--space-snug);
            padding-block-end: var(--space-tight);
            list-style: none;
        }
        > [data-part="kinds"] > li {
            position: relative;
            padding-inline-start: var(--space-snug);
        }
        > [data-part="kinds"] > li::before {
            content: "";
            position: absolute;
            inset-inline-start: 0;
            inset-block-start: -0.125rem;
            inset-block-end: 50%;
            inline-size: 0.625rem;
            border-inline-start: 1px solid var(--ink-quiet);
            border-block-end: 1px solid var(--ink-quiet);
        }
        > [data-part="kinds"] > li:not(:last-child)::after {
            content: "";
            position: absolute;
            inset-inline-start: 0;
            inset-block-start: 50%;
            inset-block-end: -0.125rem;
            border-inline-start: 1px solid var(--ink-quiet);
        }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: The stage bar — stripes across the full width, title on them. */

@layer widgets.header_banner {
    @scope ([data-type="header-banner"]) {
        :scope {
            display: grid;
            grid-template-columns: 1fr;
            align-items: start;
            gap: var(--space-close);
            padding-block: 2.125rem 1.875rem;
            padding-inline: max(var(--gutter), (100% - var(--measure)) / 2);
            border-block-end: 1px solid oklch(30% 0.014 260);
            color: var(--stage-ink);

            /* The stripes come from a repeated gradient, not from a picture: no
               request, no file, and the colours come from the theme. A
               second game changes three variables and gets its own bar.

               The gradient stands in the theme and not here: it is what
               changes per game. The bar itself stays the same form. */
            background: var(--stage-scan), var(--stage-bar);
            background-size: 100% 3px, auto;
            background-repeat: repeat, no-repeat;
        }

        /* The title stands on the stripes and needs a hold against them. A
           shadow in the deepest colour of the bar itself, not in black: it
           so sits in the same family and acts as an edge, not as dirt. */
        /* 44 px — the largest type of the site and the only one not on the
           ladder. It is the stage itself: the game name, and it should fill
           the bar, not stand in it. Measured from the design.

           The shadow is hard and offset, three pixels to the bottom right —
           no soft glow. That is how screens of that time cast their type,
           and it is the same reason the bar carries a scanline grid. */
        [data-type="heading"] {
            font-size: var(--text-stage);
            line-height: 1.2;
            color: oklch(99% 0.01 90);
            text-shadow: 3px 3px 0 oklch(22% 0.06 34);
        }

        /* The date line: 13 px, widely tracked, in the light stage colour. The
           chip brings its own size and is overridden here, because it
           stands on the bar and not in the text. */
        [data-type="chip"] {
            font-size: 0.8125rem;
            letter-spacing: 0.12em;
            color: oklch(97% 0.02 70);
            padding-block-end: 0.5rem;
        }

        /* The expansion, under the name. Smaller and set back: it says which
           content holds, and that is information and no announcement. The
           type cut stays the same as the title's — it is the same thing,
           only one level deeper.

           No grid place of its own: the line stands in the flow under the
           title and is conditional. A game without an expansion does not
           show it, and the bar may not jump because of that. */
        [data-type="edition"] {
            margin: 0;
            font-size: var(--text-head);
            line-height: 1.1;
            letter-spacing: 0.02em;
            color: oklch(88% 0.06 70);
        }

        /* From tablet on the note stands beside the title instead of under
           it. Only the difference, and only upwards — the base is the
           narrow form. */
        /* From tablet on two columns: title and expansion one under the other
           on the left, the note on the right. The expansion belongs under
           the name and not beside it — it says which content holds, and
           one reads that as a continuation of the title, not as a second
           statement at the other end.

           Named areas instead of counting: with three children and two
           columns the third otherwise lands in a place nobody chose —
           exactly that happened here. */
        @media (min-width: 35rem) {
            :scope {
                grid-template-columns: 1fr auto;
                grid-template-areas:
                    "title   note"
                    "edition note";
                align-items: end;
            }
            [data-type="heading"] { grid-area: title; }
            [data-type="edition"] { grid-area: edition; }
            [data-type="chip"]    { grid-area: note; }
        }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: Symbol and word mark side by side. */

@layer widgets.header_logo {
    @scope ([data-type="header-logo"]) {
        :scope {
            display: grid;
            grid-auto-flow: column;
            align-items: center;
            gap: 0.75rem;
            text-decoration: none;

            /* The link does not fill its cell — it is as wide as its content.
               Without that the stretch of the head grid would come through
               and the clickable area would reach up to the navigation. */
            justify-self: start;
        }

        :scope:where(:focus-visible) {
            outline: 2px solid var(--link);
            outline-offset: var(--space-tight);
        }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: The navigation — a switch on the phone, an open list from screen on. */

@layer widgets.header_menu {
    @scope ([data-type="header-menu"]) {
        /* The nav passes its two children through to the head grid instead of
           enclosing them. Otherwise switch and list would have to share one
           area, and the switch would stand on the phone where the list
           begins from screen on — so in the middle instead of outside. */
        :scope { display: contents; }

        /* The button is no bare <button> any more since the element
           icon_button. The old selector no longer applied, and the rule
           hiding it from screen on ran into nothing: it stood on the desk
           and made the head bar taller. The picture comparison found it,
           not the markup — there stands a button that may stand. */
        [data-type="icon-button"] {
            grid-area: toggle;
        }

        /* No display here: a closed popover is display:none, and any statement
           at this point holds it open. The grid therefore stands only in
           the opened state and from screen on, where it lies in the flow
           anyway. */
        > ul {
            grid-area: menu;
            gap: var(--space-close);
        }

        > ul:popover-open {
            display: grid;
        }

        /* On the phone the list stands as a popover over the content. The
           browser brings focus trap, Escape and closing on a click beside
           it — everything that would otherwise be written as script. */
        > ul:where([popover]) {
            margin: 0;
            padding: var(--space-open);
            background: var(--ground-up);
            border: 1px solid var(--surface-4);
        }

        /* Colour, focus ring and state of the link stand in the link element
           and not here — one place instead of four \({K0054}\). The widget
           only decides how the links are arranged, and overrides the colour
           for the head line, where it sits more muted than in the running text. */
        /* The typography hangs on the entry, not on the link: a tab without a
           target (span, »soon«) carries the same type, the same capitals and
           the same tracking as one with — it differs only in colour and
           pointer. Before, it hung on the link, and »Bauplan« and »Spiele«
           fell back to the base type (Owner, 3 September 2026, T0026 QA). */
        li {
            font-family: ui-monospace, monospace;
            font-size: var(--text-small);
            letter-spacing: 0.08em;
            text-transform: uppercase;
        }

        [data-type="link"]:not([aria-current]) { color: var(--ink-quiet); }
        [data-type="link"]:where(:hover, :focus-visible) { color: var(--ink); }

        /* A tab whose page does not exist yet. It stands in the flow like the
           others, but accepts no pointer and is quieter — it shows what is
           coming without promising a target ({K0079}). */
        [data-state="soon"] { color: var(--ink-quiet); cursor: default; }

        /* From screen on the two areas swap roles. The popover is set visible
           and so loses everything popover-like: it stands in the flow, in
           the area menu, as a row. The switch disappears.

           position:static is needed because the default stylesheet gives a
           popover position:fixed. Nothing more: a popover enters the top
           layer only on opening, and this one is never opened — the switch
           no longer stands from screen on. */
        @media (min-width: 60rem) {
            [data-type="icon-button"] { display: none; }

            > ul:where([popover]) {
                display: grid;
                position: static;
                grid-auto-flow: column;
                gap: var(--space-open);
                padding: 0;
                background: none;
                border: 0;
            }
        }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: The language choice — the current value visible, the others unfoldable. */

@layer widgets.header_options {
    @scope ([data-type="header-options"]) {
        :scope {
            position: relative;
            justify-self: end;
        }

        > summary {
            display: grid;
            place-items: center;
            min-inline-size: var(--space-wide);
            padding-block: var(--space-tight);
            padding-inline: var(--space-snug);
            border: 1px solid var(--surface-4);
            font-family: ui-monospace, monospace;
            font-size: var(--text-small);
            letter-spacing: 0.08em;
            text-transform: uppercase;
            color: var(--ink-quiet);
            cursor: pointer;
            list-style: none;
        }

        > summary::-webkit-details-marker { display: none; }

        /* The list hangs over the content instead of pushing it. Anchor
           positioning would be the finer means here, but absolute position
           on a relatively positioned parent costs nothing and is everywhere.
           The change pays only once the list can hit an edge. */
        > ul {
            position: absolute;
            inset-inline-end: 0;
            inset-block-start: calc(100% + var(--space-tight));
            z-index: 10;
            display: grid;
            gap: var(--space-tight);
            min-inline-size: max-content;
            padding: var(--space-snug);
            background: var(--ground-up);
            border: 1px solid var(--surface-4);
        }

        /* The link brings focus ring and state \({K0054}\); here stands only
           the more muted colour of the head line. */
        [data-type="link"] { font-size: var(--text-small); color: var(--ink-quiet); }
        [data-type="link"]:where(:hover, :focus-visible) { color: var(--ink); }
        [data-type="link"][aria-current] { color: var(--link); }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: An item as a card — symbol, name, marks, values, origin, ingredients. */

@layer widgets.item_card {
    @scope ([data-type="item-card"]) {
        /* The same pixel edge as tool card, button and mark: two inner shadows
           instead of a frame. A frame would lie outside and shift the face
           by two pixels; the edge lies inside and leaves the card where it
           is. Derived from tool_card, not reinvented — a second card form
           with its own edge would be a second design language. */
        :scope {
            display: grid;
            grid-template-areas:
                "icon  title"
                "icon  marks"
                "stats stats"
                "orig  orig"
                "input input";
            grid-template-columns: auto 1fr;
            column-gap: 1rem;
            row-gap: 0.5rem;
            align-content: start;
            padding: 1rem;
            background: var(--surface-4);
            box-shadow:
                inset  2px  2px 0 oklch(33% 0.014 var(--hue-surface)),
                inset -2px -2px 0 oklch(12% 0.010 var(--hue-surface));

            /* The card carries its own query: it stands in a list, in a grid or
               alone, and none of these places should have to tell it the
               width. */
            container-type: inline-size;
        }

        /* 100 px shown, 128 delivered. The file is the exact doubling of the
           64s from the game — every pixel into four, without intermediate
           tones —, and shown at 100 that gives 1.28-fold density.
           
           No `image-rendering: pixelated`: here it is scaled down, not up,
           and for that the default is the better. And no AI in between: at
           exactly 2x there would be nothing to invent that is not already
           there, and an upscaler would smooth edges the design shows.
           Measured: doubled 1416 bytes, the same size with Lanczos 8438. */
        [data-type="game-icon"] {
            grid-area: icon;
            inline-size: 100px;
            block-size: 100px;
            align-self: start;
        }

        [data-type="heading"] { grid-area: title; align-self: end; }

        [data-part="marks"] {
            grid-area: marks;
            display: flex;
            flex-wrap: wrap;
            gap: 0.25rem;
            margin: 0;
            align-self: start;
        }

        /* Values in two columns, designation left, number right. The line
           sits under every row, because the list ends open at the bottom
           and the next section begins there anyway. */
        [data-part="stats"] {
            grid-area: stats;
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
            gap: 0.25rem 1rem;
            margin: 0.5rem 0 0;

            > div {
                display: flex;
                justify-content: space-between;
                gap: 0.5rem;
                padding-block-end: 0.25rem;
                border-block-end: 1px solid oklch(33% 0.014 var(--hue-surface));
            }

            dt { color: var(--ink-quiet); }
            dd {
                margin: 0;
                color: var(--ink-bright);
                font-variant-numeric: tabular-nums;
            }
        }

        /* The origin kind colours the edge, but carries the information in
           the text: whoever cannot tell the colour apart reads it. */
        [data-part="origins"] {
            grid-area: orig;
            display: grid;
            gap: 0.25rem;

            p {
                margin: 0;
                padding-inline-start: 0.75rem;
                border-inline-start: 2px solid oklch(33% 0.014 var(--hue-surface));
            }

            [data-kind="crafted"],
            [data-kind="by_product"] { border-inline-start-color: var(--link); }

            b { color: var(--ink-bright); font-weight: 500; }
            i { color: var(--ink-quiet); font-style: normal; }
            i::before { content: " · "; }
        }

        [data-part="inputs"] {
            grid-area: input;
            display: flex;
            flex-wrap: wrap;
            gap: 0.25rem 0.75rem;
            margin: 0;
            padding: 0;
            list-style: none;

            li { display: flex; align-items: center; gap: 0.25rem; }

            /* In the ingredient list the symbol is a mark beside the number and no
               stage — 32 px, as in the tool card on the phone. */
            [data-type="game-icon"] { inline-size: 2rem; block-size: 2rem; }

            [data-part="amount"] {
                color: var(--ink-bright);
                font-variant-numeric: tabular-nums;
            }
        }

        /* Narrow: the symbol stands above the name instead of beside it. With
           a 100 px symbol and 9 rem of values nothing is left for the name
           otherwise. */
        @container (width < 20rem) {
            :scope {
                grid-template-areas: "icon" "title" "marks" "stats" "orig" "input";
                grid-template-columns: 1fr;
            }
            [data-part="stats"] { grid-template-columns: 1fr; }
        }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: The sheet of an item - the popover, its head, the level choice, the
   close symbol. Extracted from slice_items at the third caller ({T0073S8},
   {K0054}); the rules are those of 1 and 2 September, unchanged. The shape
   of the sections in it (lines, values, crafting) still lies in
   slice_items.css, because the single page needs the same rules for its own
   dossier markup - the scope there carries three roots since {T0073S8}. */

@layer widgets.item_sheet {
    @scope ([data-type="item-sheet"]) {
        /* ---- The sheet -------------------------------------------------- */

        /* In the top layer, so above everything — without a single z-index.
           That is the gain over the overlay that stood here before: there
           the stacking order had to be right by hand, and it was right only
           on the second try.

           The place is set by the script: it remembers where the tile lies
           and lets the sheet grow from there. Without script it stands
           centred — that is the fallback, not the rule. */
        :scope {
            /* Centred and fixed in size: 400 wide, at most 900 tall (Owner,
               1 September 2026). A popover centres itself if one lets it —
               `inset: 0` with `margin: auto` says it once more expressly,
               because any own position statement otherwise cancels the
               browser's default. Exactly there the sheet sat in the top left
               corner before.

               The height is an upper bound and no rule: a sheet with two
               statements should not carry nine hundred pixels of emptiness.
               And on a small screen the screen wins. */
            position: fixed;
            inset: 0;
            margin: auto;

            /* **Belt and braces.** In the top layer the sheet lies above
               everything and needs no `z-index` — but exactly that could not
               be relied on: on closing it drops out of the top layer, and
               `overlay` does not bridge the moment everywhere. For the blink
               of an eye it then lay behind the tiles (Owner, 1 September
               2026).

               If it drops out, it is still `position: fixed` — and then the
               `z-index` decides. This one is so high that nothing in this
               design reaches it; the tiles stand on `auto`.

               The line is no solution of the problem but a precaution for
               the case that the solution fails. Where a property does not
               bridge exactly the one moment in which it is needed, a second
               belongs beside it — and it stays, even if it looks superfluous. */
            z-index: var(--sheet-layer);
            inline-size: var(--sheet-inline-size);
            max-block-size: var(--sheet-max-block-size);
            overflow-y: auto;
            padding: 0;

            /* The golden frame all around, three pixels (Owner, 1 September 2026).
               The tile carries the corner as an announcement — »something
               pops up here«; the opened carries the same colour as a closed
               line. Announcement and fulfilment in the same language. */
            border: var(--sheet-edge) solid var(--gold);

            background: var(--surface-4);
            color: var(--ink);

            /* No inner shadow any more. The pixel edge says »raised face«, and a
               sheet does not need that: it lies in the top layer above
               everything, and the drop shadow says it more clearly. Two
               statements about the same thing, and the one ran as a light
               line left and dark right through the picture (Owner,
               1 September 2026). */
            box-shadow: var(--sheet-shadow);

            /* Only fade in, no drive-up. The way from the tile to the sheet
               needed a script setting three variables; with the renunciation
               of that the only script file of the page drops.

               `allow-discrete` keeps the element alive on closing long
               enough for the fade-out to be seen at all — without it a
               popover vanishes without a transition. */
            /* Half a second, soft (Owner, 1 September 2026). Not longer: a sheet
               one closes should not still be there when one clicks the next
               tile.

               `translate` stands beside it because the sheet can be dragged
               — during the dragging itself the script switches the
               transition off, otherwise it would run after the mouse instead
               of hanging on it. */
            /* **The resting value is invisible, not visible.** `opacity: 1` stood
               here, and with that there was nothing to fade out on closing:
               the sheet went from one to one, vanished abruptly after half a
               second — and lay fully visible under the tiles in that half
               second, because `overlay` had nothing to hold for lack of a
               change.

               The closed state is the resting state; the sheet becomes
               visible only through `:popover-open`. `@starting-style`
               additionally says where the fade-in begins — without it there
               would only be the way back. */
            opacity: 0;

            /* `transition-behavior` stands written out and not as
               `allow-discrete` in the shorthand. Both are valid, but the
               shorthand is not treated the same everywhere — and if it
               fails, the sheet leaves the top layer at once on closing,
               while it is still fading out. For half a second it then lies
               under the tiles it just covered (Owner, 1 September 2026).

               `overlay` is the property that holds this: it describes the
               membership of the top layer, and only if it transitions along
               does the sheet stay on top until it is invisible. */
            transition-property: opacity, overlay, display;
            transition-duration: 500ms;
            transition-timing-function: ease;
            transition-behavior: allow-discrete;
        }

        :scope:popover-open { opacity: 1; }

        /* While fading out the sheet accepts no more clicks: what is half
           transparent is no control any more — otherwise one would still
           hit »Zum Planer« in that half second. */
        :scope:not(:popover-open) { pointer-events: none; }

        @starting-style {
            :scope:popover-open { opacity: 0; }
        }

        /* **A hole in the backdrop for the tile the mouse stands over.**

           The tiles stay clickable while a sheet is open — that is wanted,
           one so changes from one item to the next. Only the tile under the
           pointer was seen only darkened (Owner, 1 September 2026).

           The mask consists of two rectangles: the first covers the whole
           area, the second stands where the tile lies, and
           `mask-composite: exclude` subtracts it. What remains is the
           backdrop with a hole.

           Lifting an element above the backdrop would not work: it lies in
           the top layer, and nothing gets there that is not itself a
           popover. So nothing is covered, but cut out.

           The four values are set by the script; without it the hole stays
           at zero size and the backdrop is simply whole. */
        :scope::backdrop {
            background: var(--sheet-scrim);
            opacity: 0;

            /* The mask needs only coverage, no colour — and still OKLCH, because
               no hex stands in the stylesheet (AW10, {C0022}). */
            mask-image: linear-gradient(oklch(0% 0 0) 0 0), linear-gradient(oklch(0% 0 0) 0 0);
            mask-repeat: no-repeat, no-repeat;
            mask-position: 0 0, var(--hole-x, -9999px) var(--hole-y, -9999px);
            mask-size: 100% 100%, var(--hole-width, 0) var(--hole-height, 0);
            mask-composite: exclude;
            transition-property: opacity, overlay, display;
            transition-duration: 500ms;
            transition-timing-function: ease;
            transition-behavior: allow-discrete;
        }

        :scope:popover-open::backdrop { opacity: 1; }

        @starting-style {
            :scope:popover-open::backdrop { opacity: 0; }
        }

        @media (prefers-reduced-motion: reduce) {
            :scope,
            :scope::backdrop {
                transition-duration: 1ms;
            }
        }

        /* The head is the handle: there one grabs a window, since there have
           been windows. `touch-action: none` prevents the browser from
           reading the dragging as scrolling and intercepting the movement. */
        :scope > header {
            cursor: grab;
            touch-action: none;
            user-select: none;
            position: sticky;
            inset-block-start: 0;
            display: grid;
            grid-template-columns: auto 1fr;
            align-items: center;
            gap: 0.75rem;
            /* On the right as much room that the name does not run under the close
               symbol: its width plus its safety margin. */
            padding: 0.875rem calc(var(--space-open) * 2 + 0.75rem) 0.875rem 1rem;
            background: var(--surface-4);

            [data-type="game-icon"] {
                grid-area: 1 / 1;
                inline-size: 64px;
                block-size: 64px;
            }

            /* The level on the icon ({T0023S4}): the same grid cell as the
               symbol, set into the top right — no absolute offset (AW5). The
               digit comes from `--level`, and that is set by the chosen pill
               further down; without a choice the one stands. White for every
               level as on the design: the rank is carried by the name. */
            [data-part="level"] {
                grid-area: 1 / 1;
                justify-self: end;
                align-self: start;
                padding: 0.1em 0.25em;
                background: var(--surface-4);
                color: var(--ink-bright);
                font-size: var(--text-small);
                font-style: normal;
                font-weight: 500;
                line-height: 1;
                font-variant-numeric: tabular-nums;
            }

            [data-part="level"]::after { content: var(--level, "1"); }

            /* The name in the colour of its rank: white, green, blue, magenta
               ({T0023S4}, 02-valheim.md). `--rank` is set by the chosen
               pill; without it the light ink stays, and that is the first rank. */
            b {
                grid-area: 1 / 2;
                color: var(--rank, var(--ink-bright));
                font-size: 1.0625rem;
                font-weight: 500;
                line-height: 1.25;
            }
        }

        /* ---- The level choice ({T0023S4}) ------------------------------- */

        /* **One condition, no pair** ({K0005}): which level holds, the chosen
           pill says, and every selector here hides exactly what does not
           belong to it. Nothing is taken back — the active level keeps the
           display type its own rule gives it, whether row, grid or number
           in the text. Without pills the one holds: the resting value is
           the first level.

           Without script: a radio button and `:has()` at the root of the
           sheet can do that. The page still carries exactly one script, and
           that only drags.

           `:has()` on the sheet and not on the slice: the choice of one
           sheet may not switch another, and a selector hanging on the slice
           would run over all 472 sheets on every click. */
        :scope {
            &:not(:has([data-rank] > :checked)) [data-level]:not([data-level="1"]),
            &:has([data-rank="1"] > :checked) [data-level]:not([data-level="1"]),
            &:has([data-rank="2"] > :checked) [data-level]:not([data-level="2"]),
            &:has([data-rank="3"] > :checked) [data-level]:not([data-level="3"]),
            &:has([data-rank="4"] > :checked) [data-level]:not([data-level="4"]) {
                display: none;
            }

            /* The name and the mark follow the choice. The one needs no rule: it
               is the resting value of both. */
            &:has([data-rank="2"] > :checked) { --rank: var(--rank-2); --level: "2"; }
            &:has([data-rank="3"] > :checked) { --rank: var(--rank-3); --level: "3"; }
            &:has([data-rank="4"] > :checked) { --rank: var(--rank-4); --level: "4"; }

            /* An ingredient that only accrues from a higher level on is missing
               below it: bone fragments come to the leather armour only with
               the update. */
            &:not(:has([data-rank] > :checked)) [data-from],
            &:has([data-rank="1"] > :checked) [data-from],
            &:has([data-rank="2"] > :checked) [data-from="3"],
            &:has([data-rank="2"] > :checked) [data-from="4"],
            &:has([data-rank="3"] > :checked) [data-from="4"] {
                display: none;
            }

            /* At the highest level the update line drops instead of standing
               empty: the line carries the highest level as data-max. */
            &:has([data-rank="2"] > :checked) [data-max="2"],
            &:has([data-rank="3"] > :checked) [data-max="3"],
            &:has([data-rank="4"] > :checked) [data-max="4"] {
                display: none;
            }
        }

        /* The close symbol sits in the corner, not in the head line: there one
           looks for it, and there it stands in every window. Four pixels of
           safety margin to the frame (Owner, 1 September 2026).

           Twice as large as an ordinary symbol button — it is the only
           control that is always there in the sheet, and a sheet without a
           visible exit makes one uneasy.

           Grey like the side text, golden on pointing like the frame (Owner,
           1 September 2026). It first stood the other way round — gold, red
           on pointing —, and that was one step too loud: the close symbol is
           the most self-evident control in the sheet and need not court
           attention. Whoever looks for it finds it; whoever points at it
           gets the frame's colour as confirmation. */
        /* The close symbol is no handle — otherwise one would drag the sheet
           when wanting to close it. */
        /* Only the position now — size and colour are carried by the role
           »close« in the element ({T0084}). They stood here and nowhere
           else, which is why the map sheets carried a smaller symbol. */
        :scope [data-type="icon-button"] {
            cursor: pointer;
            position: absolute;
            inset-block-start: 4px;
            inset-inline-end: 4px;
            z-index: 1;
        }


        :scope [data-part="dossier"] {
            padding: 0 1rem 1rem;
        }

        :scope header a {
            color: inherit;
            text-decoration: none;
        }

        :scope header a:where(:hover, :focus-visible) {
            text-decoration: underline;
            text-underline-offset: 3px;
        }

    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: The tile of an item - symbol, name, golden corner, hover.
   Extracted from slice_items at the second caller ({T0073S2}, {K0054});
   the rules are those of 1 September, unchanged. */

@layer widgets.item_tile {
    @scope ([data-type="item-tile"]) {
    /* ---- The tile --------------------------------------------------- */

    /* A <button> and no <article>: it switches something, and what switches
       is a button. With that come keyboard, focus ring and the role for the
       screen reader for free. */
    :scope {
        position: relative;
        display: grid;
        grid-template-columns: auto 1fr;
        align-items: center;
        column-gap: 0.75rem;
        inline-size: 100%;
        block-size: 79px;
        padding: 6px 0.75rem;
        border: 0;
        background: var(--surface-4);
        color: inherit;
        font: inherit;
        text-align: start;
        cursor: pointer;

        box-shadow:
            inset  2px  2px 0 oklch(33% 0.014 var(--hue-surface)),
            inset -2px -2px 0 oklch(12% 0.010 var(--hue-surface));

        [data-type="game-icon"] {
            inline-size: 50px;
            block-size: 50px;
        }

        b {
            color: var(--ink-bright);
            font-weight: 500;
            line-height: 1.2;
            overflow-wrap: break-word;
            hyphens: none;
        }
    }

    /* **The golden corner: the sign for »pops up«.**

       Fifteen pixels in each direction, five pixels strong, above the
       existing edge — that stays unchanged beneath (Owner, 1 September
       2026). Two edges on a box without a face give the angle; an SVG would
       be one more file for two strokes.

       **This language holds throughout for everything that pops up.**
       Whoever needs it on another component does not copy the rule but
       takes the line here as the model — and when it is needed a third
       time, an element is made of it ({K0054}).

       `pointer-events: none`, because it lies over the tile and a click on
       it must stay a click on the tile. */
    :scope::after {
        content: "";
        position: absolute;
        inset-block-start: 0;
        inset-inline-end: 0;
        inline-size: 15px;
        block-size: 15px;
        border-block-start: 5px solid var(--gold);
        border-inline-end: 5px solid var(--gold);
        pointer-events: none;
    }

    /* On hovering a touch of gold over the face — ten percent (Owner,
       1 September 2026). It is the same colour as the corner and the frame
       of the sheet, only so faint that it tints the face instead of
       colouring it: the tile stays a tile and does not become a button.

       `color-mix` and no second layer: an overlay with `opacity` would lie
       over the symbol and take its colour with it. */
    :scope:where(:hover, :focus-visible) {
        background: color-mix(in oklab, var(--gold) 10%, var(--surface-4));
    }

    /* On hovering a touch of gold over the face — ten percent (Owner,
       1 September 2026). The same colour as corner and frame, only so faint
       that it tints the face instead of colouring it: the tile stays a tile
       and does not become a button.

       `color-mix` and no second layer: an overlay with `opacity` would lie
       over the symbol and take its colour with it.

       The tone suffices for both cases. With an open sheet the mask punches
       a hole into the backdrop anyway, and through the hole the same hover
       is seen — it needs no second treatment for the case that something
       is open right now. */
    :scope:where(:hover, :focus-visible) {
        background: color-mix(in oklab, var(--gold) 10%, var(--surface-4));
    }

    :scope:where(:hover, :focus-visible)::after {
        border-color: oklch(from var(--gold) calc(l + 0.08) c h);
    }

    :scope:where(:focus-visible) {
        outline: 2px solid var(--link);
        outline-offset: -2px;
    }

    }
}

@layer widgets.item_tile {
    @scope ([data-type="item-tile"]) {
        /* The mark of the last patch ({T0078}, shape changed by the owner on
           9 September 2026 after the 1.0 run).

           Before, a red badge with ground and pixel edge like the beta on
           the logo — that was too loud: after a patch with 211 new items the
           list stands full of signals, and a warning sign stuck on every
           third tile warns of nothing any more. Now only the word, small, in
           the house's action colour, without ground.

           »Geaendert« drops entirely: that an item has other values
           interests nobody looking at the list — being new does.

           It sits bottom right, where the golden corner top right does not
           stand; absolutely positioned so that it does not wrap the name and
           the tile does not become two lines. */
        [data-part="signal"] {
            position: absolute;
            inset-block-end: var(--space-close);
            inset-inline-end: var(--space-close);

            /* Measured against the tile ground (--surface-4, oklch 86 % light and
               21 % dark): the primary colour carries in the dark with 7.68
               to 1, in the light only with 1.51 — there the same tone stands
               darkened and reaches 4.53.

               **Darkened means computed, not copied** (AW10, {K0004}).
               `oklch(45% 0.15 145)` stood here, so the hue of the resolved
               `--action`; now chroma and hue come from the primary value and
               only the lightness stands fixed ({T0096}). */
            color: light-dark(oklch(from var(--link) 0.45 c h), var(--link));

            font-family: "Press Start 2P", monospace;
            font-size: 0.5rem;
            line-height: 1;
            letter-spacing: 0.04em;
            text-transform: uppercase;
        }

        /* The containing block already stands above: the tile has always been
           relatively positioned, because the golden corner needs it too. */
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: Metrics side by side, separated by lines. */

@layer widgets.metric_row {
    @scope ([data-type="metric-row"]) {
        :scope {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
            gap: var(--space-open);
        }

        /* The dividing line sits before every number except the first, not
           after every one except the last: so it stays right when a number
           is hidden or the row wraps. A border on the last cell would be a
           line on the outer edge. */
        > [data-type="metric"] + [data-type="metric"] {
            padding-inline-start: var(--space-open);
            border-inline-start: 1px solid var(--surface-4);
        }

        /* On the phone the fourth does not stand. It stays in the markup — what
           is missing is only the room, and that comes back from screen on. */
        > [data-type="metric"]:nth-child(n + 4) { display: none; }

        @media (min-width: 60rem) {
            > [data-type="metric"]:nth-child(n + 4) { display: grid; }
        }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: The notice band — gold on dark ground, across the stage. */

@layer widgets.notice_band {
    @scope ([data-type="notice-band"]) {
        /* **Gold, not light blue** ({T0097}, {K0234}). The design provided a
           light blue frame, and light blue would have been a fourth role
           colour — the palette has three, and {T0096} enforced them. Gold
           already says »mind this« in the house and stands on the map under
           the switches for exactly such a note.

           A ground of its own, not only a frame: the band lies on the
           stage, and that is orange, striped and light. A text without a
           face would not be readable there — the same calculation as with
           the map panel, which therefore stands at 85 percent opacity. */
        :scope {
            display: grid;
            grid-template-columns: 1fr auto;
            align-items: center;
            gap: var(--space-close);
            /* **One measure to all sides** (Owner, 12 September 2026: small on
               the left, large on the right, medium top and bottom — »das
               sieht sehr unprofessionell aus«). It was `snug` on the left,
               `tight` on top and on the right what the button brings by
               itself.

               `--space-close` carries the factor 0.618 and is so the step of
               the golden ratio itself; it lies at 8.7, 9.3 and 10.5 points
               depending on width and hits the named eight on the narrow one
               almost exactly. The same step as at the golden line of the
               filter bar ({T0093}) — a spacing meant to be called »eight
               points« twice is better called the same twice. */
            padding: var(--space-close);
            background: oklch(12% 0.012 260 / 0.94);
            /* The same edge as a sheet ({T0097}, Owner on 12 September 2026:
               »genau so breit wie vom popover«). The token carries it for
               everything that lies on the page — a hairline stood here, and
               beside a sheet that looked like another house order. */
            border: var(--sheet-edge) solid var(--gold);
            color: var(--ink-bright);

            /* **It floats above the page and so above every grid** (Owner,
               12 September 2026: »der toast schwebt über der seite also auch
               über deren raster«).

               Before, it lay as a grid child in the stage's row. That worked
               where a stage stands — and on the map page it pushed the
               options panel down, because the stage is hidden in full screen
               and the band so got a row of its own. A note that shifts the
               control panel costs more than it serves.

               `fixed` instead of `sticky`: it should stay in its place and
               claim no room, on no page and in no grid. The `z-index` lies
               below the head, which carries 6 — an announcement does not
               cover the navigation. */
            position: fixed;
            inset-block-start: calc(var(--head-height) + var(--space-close));
            inset-inline: 0;
            margin-inline: auto;
            z-index: 5;
            /* Narrow enough that the stage beside it is still to be seen: it is
               the picture of the page, and a band that covers it takes more
               away than it says. */
            inline-size: min(46rem, calc(100% - 2 * var(--gutter)));
        }

        /* **The base size of the page, not the small one** (Owner,
           12 September 2026: »es soll ja auch von Personen gelesen werden
           die Probleme mit den Augen haben«). `--text-small` stood here, and
           that is the size for labels and trimmings — an announcement
           somebody **must** read belongs in the size of the running text. */
        [data-part="word"] {
            margin: 0;
            font-size: var(--text);
        }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: The game list — one line with separator dots. */

@layer widgets.page_footer_nav {
    @scope ([data-type="page-footer-nav"]) {
        ul {
            display: flex;
            flex-wrap: wrap;
            gap: var(--space-close);
            font-family: ui-monospace, monospace;
            font-size: 0.625rem;
            letter-spacing: 0.1em;
            text-transform: uppercase;
        }

        /* Flex, not grid: the list is a single stack that wraps, and exactly
           for that flex is the exception that stays allowed. A grid would
           have to know the column count, which depends on the text here. */

        li { color: var(--ink-quiet); }

        /* The separator dot does not belong in the text but between the entries
           — otherwise a screen reader reads it along. */
        li + li::before {
            content: "·";
            margin-inline-end: var(--space-close);
            color: var(--ink-quiet);
        }

        /* The link brings focus and state; here stands only that it sits more
           muted in the footer \({K0054}\). */
        [data-type="link"] { color: var(--ink-quiet); }
        [data-type="link"]:where(:hover, :focus-visible) { color: var(--link); }

        /* A coming game is no link and does not look like one either. */
        span { color: var(--ink-quiet); }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: A search field with a button — arrangement, nothing more. */

@layer widgets.search_form {
    @scope ([data-type="search-form"]) {
        /* Field and button carry their shape themselves. Here stands only how
           they lie to each other — unlike the seed input, where the button
           is twice as wide as the field: there START is the target, here
           the field is the main thing and the button only the confirmation. */
        form {
            display: grid;
            grid-template-columns: minmax(0, 1fr) auto;
            gap: 0.625rem;
            align-items: end;
        }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: The seed input — label above field and button. */

@layer widgets.seed_form {
    @scope ([data-type="seed-form"]) {
        /* A ground of its own as soon as something lies behind. The form is a
           control and must stand on the picture, not in it: the label is
           muted and needs dark ground, otherwise it falls to 1.1 to 1 —
           measured with tools/contrast.py.

           Darkening the gradient further on the right instead would have
           covered the picture exactly where it should be visible. A face of
           its own costs nothing and separates control from picture.

           It is translucent so that the picture beneath stays visible, and
           exactly as far as the contrast allows: measured, the label already
           falls under AA at 88 percent opacity when it stands in the muted
           colour. Hence it moves one step higher — 72 percent face and
           lighter type together give more contrast than 94 percent face and
           muted.

           Both deviate from the design, and both are measured: its own
           combination would come to 3.4 to 1. It meets no AA at this point;
           we do, and the deviation stands here — as AW22 demands. */
        :scope {
            padding: 1rem 1.125rem 1.125rem;
            background: oklch(8% 0.008 260 / 0.72);
            /* **Flex and no named tracks** \({T0103}\). A grid stood here with
               `grid-template-areas: "input action random"` and three fixed
               columns. That carried exactly three children — and the third
               button was the fourth: the selector `[data-type="button"]`
               assigned Start AND Super Seed the same area, both landed at
               x 261, y 161 and lay exactly on top of each other \({K0232}: a
               selector that hits too much\).

               Naming a fourth track would not have solved it, because the
               same form stands on the landing page with only three children
               and would have carried an empty column there. Flex counts no
               tracks: three children spread over three, four over four, and
               Start and Random become narrower by themselves — exactly what
               was asked for here \(Owner, 12 September 2026\). */
            display: flex;
            flex-wrap: wrap;
            gap: 0.4375rem 0.625rem;
            /* At the bottom edge: the field carries its label above, the button
               has none. Both are equally tall because the height stands on
               the element — here only flushing is left. */
            align-items: end;
        }

        /* Field and button write themselves — here stands only how they lie to
           each other. What stood at this place before has moved to
           elements/field and elements/button when the search on the item
           page needed the same ({K0054}). */
        /* The field takes most of the room and wraps first: it carries an
           input, the buttons only a word. */
        [data-type="field"] { flex: 1 1 12rem; }
        /* The buttons share the rest in equal parts. The base is chosen so
           that three of them still stand side by side at 711 points of
           panel width and wrap below that instead of squeezing the type. */
        [data-type="button"] { flex: 1 1 7rem; }
        /* The random button in gold: it says »shows more«, not »does what you
           typed« ({K0032}). A link in the shape of the button — the same
           element. It may stay narrower than the two that trigger
           something: its word is shorter and its role the lighter. */
        [data-type="button"][href] { flex: 0 1 6rem; }

        /* **The compact form** ({T2001S6}): in the map options drawer the
           form stands on the drawer's own ground, so the face and its
           padding go; the field takes the row and the two signs keep their
           square - nothing wraps, because a sign is as wide as it is tall
           and the drawer is never narrower than the three together. */
        :scope[data-shape="compact"] {
            padding: 0;
            background: none;
            flex-wrap: nowrap;
            gap: var(--space-snug);
        }
        :scope[data-shape="compact"] [data-type="button"] { flex: 0 0 auto; }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: Entry cards — landscape on the phone, portrait from screen on. */

@layer widgets.tool_card {
    @scope ([data-type="tool-card"]) {
        /* On the phone one row: icon left, title and short text beside it,
           the chevron right. From screen on portrait with the long text and
           the way below. Only the areas change — that is the whole rebuild
           \({K0009}\). */
        :scope {
            position: relative;
            display: grid;
            grid-template-areas:
                "icon title act"
                "icon text  act";
            grid-template-columns: auto 1fr auto;
            align-items: center;
            column-gap: 0.875rem;
            row-gap: 0.25rem;
            padding: 0.875rem 1rem;
            background: var(--surface-4);

            /* The same pixel edge as button and mark, only in the surface tones:
               two inner shadows instead of a frame. A frame would lie
               outside and shift the face by two pixels; the edge lies inside
               and leaves the card where it is. Measured from the design. */
            box-shadow:
                inset  2px  2px 0 oklch(33% 0.014 var(--hue-surface)),
                inset -2px -2px 0 oklch(12% 0.010 var(--hue-surface));
        }

        /* 48 px, measured from the design — not the element's standard size,
           because the card gives it more weight than a list. */
        /* On the phone 32 px, from screen on 48: in the row the icon is a mark
           beside the title, in the portrait card the stage above it. */
        [data-type="game-icon"] { grid-area: icon; inline-size: 2rem; }
        [data-type="heading"]   { grid-area: title; }
        /* 14.5 px and one step more muted than the page's running text: in the
           card the description is trimmings to the title, not the text one
           came for. Both measured from the design; 14.5 lies between two
           steps of the ladder, and AW22 sides with the design. */
        [data-type="prose"] {
            grid-area: text;
            font-size: 0.90625rem;
            line-height: 1.6;
            color: oklch(65% 0.008 var(--hue-surface));
            max-inline-size: none;
        }

        /* The long text stands only from screen on, the short one only below. */
        [data-type="prose"]:nth-of-type(2) { display: none; }

        /* The heading carries 13 px here, not the size of its level: in the
           card it is a label and no announcement. Measured from the design
           \({K0058}\). */
        [data-type="heading"] {
            font-size: var(--text-label);
            text-transform: uppercase;
        }

        [data-type="link"] {
            grid-area: act;
            justify-self: end;
            font-family: ui-monospace, monospace;
            font-size: 0.6875rem;
            letter-spacing: 0.1em;
            text-transform: uppercase;

            /* Pushed down so that the way sits on the same line on all cards,
               however long the description. */
        }

        /* On the phone the link carries only its chevron: the wording stands in
           the card above, and reading the same twice is no service. */
        [data-type="link"] { font-size: 0; }
        [data-type="link"] > span { font-size: 1rem; margin-inline-start: 0; }

        /* The link stretches its clickable area over the whole card. That is
           the reason for position:relative above — and the reason why title
           and icon are not linked themselves. */
        [data-type="link"]::after {

        /* The call of a card whose page does not exist yet. It stands in the
           same place as the link so that the cards stay equally tall — only
           quieter and without a pointer ({K0079}). */
        [data-state="soon"] { color: var(--ink-quiet); cursor: default; }
            content: "";
            position: absolute;
            inset: 0;
        }

        /* On hovering the card is pressed: the edge turns round, dark top left
           and light bottom right, as with the input field \({K0062}\). In
           addition the face takes on a touch of the link colour — six
           percent, just enough to notice without being able to name it.

           No coloured frame: a frame says „selected", and selected the card
           is not. It is pressed, and that is an action, no property. */
        :scope:has([data-type="link"]:where(:hover, :focus-visible)) {
            background: color-mix(in oklab, var(--surface-4) 94%, var(--link));
            box-shadow:
                inset  2px  2px 0 oklch(12% 0.010 var(--hue-surface)),
                inset -2px -2px 0 oklch(33% 0.014 var(--hue-surface));
        }

        /* The transition runs only over the face; the edge jumps. A shadow
           that turns soft would look like glowing instead of pressure. */
        :scope {
            transition: background 90ms ease-out;
        }

        @media (prefers-reduced-motion: reduce) {
            :scope { transition: none; }
        }

        /* Portrait from screen on: the icon moves above the title, the card
           becomes one column. Only the areas change — that is the whole
           rebuild \({K0009}\). */
        @media (min-width: 60rem) {
            :scope {
                grid-template-areas:
                    "icon"
                    "title"
                    "text"
                    "act";
                grid-template-columns: 1fr;
                grid-template-rows: auto auto auto 1fr;
                align-items: start;
                gap: 1rem;
                padding: 1.625rem;
            }

            [data-type="game-icon"] { inline-size: 3rem; }

            [data-type="prose"]:nth-of-type(1) { display: none; }
            [data-type="prose"]:nth-of-type(2) { display: block; }

            [data-type="link"] {
                justify-self: start;
                font-size: var(--text-micro);
                margin-block-start: auto;
            }

            [data-type="link"] > span { font-size: inherit; margin-inline-start: 0.35em; }
        }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: The footer band — one surface step deeper than the content above. */

@layer slices.page_footer {
    @scope ([data-slice="page-footer"]) {
        :scope {
            padding-block: 1.5rem;
            row-gap: 1rem;
            border-block-start: 1px solid oklch(26% 0.014 var(--hue-surface));

            /* The face changes, not a border: bands separate over the ground, not
               over lines \({K0031}\). One step deeper than the content
               above, so that the footer steps back without vanishing. */
            background: var(--surface-1);
        }

        /* Links in the footer carry the footer colour, not the link colour.
           --link is green and means »leads away«; in the content that is an
           offer, in the footer it would be a shout. The footer is the
           quietest band of the page, and two compulsory links in it are no
           invitation but information \({T0077} QA\). They stay recognisable
           by the underline and by lightening on hovering. */
        /* The notice in the size of the rest of the footer ({T0077}, Owner on
           9 September 2026). It stood one step larger, because it once was
           the signature; now it is the one line that stands alone centred
           in the map view, and there it should fall into line and not
           stand out. */
        [data-type="footer-legal"] small {
            font-size: 0.65625rem;
            letter-spacing: 0.08em;
        }

        a[data-type="link"] {
            color: var(--ink-quiet);
            font-family: ui-monospace, monospace;
            font-size: 0.65625rem;
            letter-spacing: 0.08em;
            text-transform: uppercase;
            /* Underlined, because they would otherwise be recognisable as links
               by nothing any more: the colour is now the footer's, and
               colour alone may not be it anyway \(AW3, WCAG 1.4.1\). The
               spacing keeps the line clear of the capitals. */
            text-decoration: underline;
            text-underline-offset: 0.3em;
            text-decoration-thickness: from-font;
        }

        a[data-type="link"]:where(:hover, :focus-visible) {
            color: var(--ink);
        }

        [data-type="prose"] {
            font-family: ui-monospace, monospace;
            font-size: 0.65625rem;
            letter-spacing: 0.08em;
            text-transform: uppercase;
            color: var(--ink-quiet);
            max-inline-size: none;
        }

        /* The donate button stands under the name and takes only its own
           width: stretched it would be a white bar across the whole band
           instead of a button ({T0134}). */
        > [data-type="donate-button"] {
            justify-self: start;
        }

        /* From tablet on name and game list stand side by side, the donate
           button under the name, the legal notice below across the whole
           width — it is the quietest line and should not fight with the
           navigation over the same room. */
        @media (min-width: 35rem) {
            :scope {
                grid-template-areas:
                    "name   games"
                    "donate ."
                    "legal  legal";
                /* The gutter is computed, not given as 1fr: beside an inner 1fr three
                   flexible tracks would otherwise share the room and push the
                   content to the middle instead of the edges. */
                grid-template-columns:
                    [full-start] var(--edge)
                    [content-start] auto var(--space-wide) 1fr
                    [content-end] var(--edge)
                    [full-end];
            }

            > [data-type="prose"]            { grid-column: 2; grid-row: 1; }
            > [data-type="page-footer-nav"]  { grid-column: 4; grid-row: 1; justify-self: end; }
            > [data-type="donate-button"]    { grid-column: 2; grid-row: 2; }
            > [data-type="footer-legal"]     { grid-column: 2 / 5; grid-row: 3; }
        }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: The head — the row of mark, targets and language, below it the stage. */

@layer slices.page_header {
    @scope ([data-slice="page-header"]) {
        /* Two rows, four areas. The targets stand in the middle and are
           centred there — not by space-between but by a 1fr track between
           two auto tracks. The difference counts: with space-between the
           middle moves as soon as the ends differ in width, and exactly
           that happens when the language choice changes from DE to Русский.

           The head carries its own ground and dividing line: it sets itself
           off from the page before the bar below begins. Measured from the design. */
        /* **The column grid stands once and is read twice** ({T0095}): the
           head needs it for the stage, the sticking bar for its own areas.
           Two copies of the same seven tracks would be the sort of
           repetition where the second differs some day (AW11). A custom
           property may carry named grid lines. */
        /* **The head stays put** (Owner, 12 September 2026), and for that it
           has shrunk to what belongs to the controls: mark, targets,
           language, below them the progress bar. The stage stands beside it
           instead of in it.

           The sticking hangs on that. `position: sticky` holds only within
           the containing block, and as long as the stage lay in the head,
           that was the head itself — the bar stood, measured after 787
           points of scrolling, at -787. Now the containing block is the
           `<body>`, and that reaches as far as the page. */
        :scope {
            position: sticky;
            inset-block-start: 0;
            /* Above everything in the band; the map sets its markers to 5. */
            z-index: 6;

            grid-template-areas:
                ".    logo badge menu options toggle .   "
                "fort fort fort  fort fort    fort   fort";
            grid-template-columns:
                [full-start] var(--edge)
                [content-start] auto auto 1fr auto auto
                [content-end] var(--edge)
                [full-end];
            align-items: center;
            row-gap: 0;
            background: var(--surface-3);
            border-block-end: 1px solid oklch(30% 0.014 260);
        }

        /* The inner room sits on the children of the bar, not on the band: the
           stage bar runs below it across the full width and may not inherit
           it. */
        > :where([data-type="header-logo"], nav, [data-type="header-options"]) {
            padding-block: 1rem;
        }

        /* Mark and language lie in the content track, not at an edge with a
           margin. The difference carries far: --edge computes with 100
           percent, and that resolves on use against the surrounding box —
           with a margin so against the element's grid cell and not against
           the band. On the desk the mark therefore stuck at 40 px while the
           content below began at 240. The same trap as a derivation in
           :root, which is computed once there and comes out the same
           everywhere. */
        [data-type="header-logo"]    { grid-area: logo; }

        /* The spacing stands here and not as column-gap on the band: a gap
           between all tracks would push the language choice off the edge
           too. The measure is that of the mark itself, so that symbol, word
           mark and beta box stand in one rhythm. */
        /* No padding-block like the neighbours: the box is the visible face
           itself, and a padding would let it grow instead of its cell. The
           line height comes from the mark beside it, align-items sets it in. */
        [data-type="beta-badge"]     { grid-area: badge; margin-inline-start: 0.75rem; }
        [data-type="header-options"] { grid-area: options; }

        :scope > nav > button { grid-area: toggle; }
        :scope > nav > ul     { grid-area: menu; }

        /* The reading progress ({T0095}, Owner on 12 September 2026): a strip
           between the target row and the stage that grows along while one
           reads.

           **The resting state is empty, not full.** That is the whole
           precaution: `animation-timeline: scroll()` is not Baseline —
           Chrome and Edge since 115, Safari since 26, Firefox stable keeps
           it behind a flag to this day. Where it is missing, the animation
           does not run, and the element stays as it stands here: squashed
           to zero and so invisible. A bar that stood full without support
           would be worse than none — it would claim something.

           `scale` and not `inline-size`: the one runs on the compositor,
           the other through the layout — and that at every scroll step
           (AW18). `transform-origin` at the start of the line, so that it
           grows from the left; `inset-inline-start` would be the
           alternative and would go through the layout again. */
        [data-part="progress"] {
            grid-area: fort;
            /* The primary colour, the same as the menu item one line above. The
               bar carried a second green for one day, and the owner saw it
               at once — meanwhile there is no second one any more ({T0096}). */
            background: var(--link);
            transform-origin: 0 50%;
            scale: 0 1;

            /* **Without support no height either.** Squashed to zero the strip
               would be invisible, but its grid row still five points tall —
               in Firefox the head would then stand five points lower than
               elsewhere, for nothing. The height therefore comes only with
               the feature, measured: Firefox 0 px, Chromium 5 px. */
            block-size: 0;
        }

        /* **No retreat at `prefers-reduced-motion`**, and that is a deliberate
           reading of AW18: the rule means movement that happens **without
           doing anything** — parallax, autoplay, transitions. This bar
           moves exclusively in sync with the reader's scrolling and so in
           the same category as the scrollbar beside it. Switching it off
           would take information away and protect from nothing. If the
           owner sees it differently, it is one line.

           The query stands here anyway — it switches the **transitions**
           off, should the strip ever grow one. */
        @supports (animation-timeline: scroll()) {
            [data-part="progress"] {
                /* Five points tall, and that is the allowed exception from AW16: a
                   line whose measure is its appearance. */
                block-size: 5px;
                animation: reading-progress linear;
                animation-timeline: scroll(root block);
            }
        }

        @media (prefers-reduced-motion: reduce) {
            [data-part="progress"] { transition: none; }
        }

        @media (min-width: 60rem) {
            :scope > nav > ul { justify-self: center; }
        }
    }
}

/* The squashing as a picture: from zero to whole, linear over the scroll
   way. A `@keyframes` knows no scope — it therefore stands beside it, in
   the same layer, and carries the package name in its own name, so that it
   does not collide with a foreign one ({T0095}). */
@layer slices.page_header {
    @keyframes reading-progress {
        to { scale: 1 1; }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: The intro — text on the left, seed on the right, side by side from screen on. */

@layer slices.slice_intro {
    @scope ([data-slice="intro"]) {
        :scope {
            padding-block: 3rem 2.75rem;
            row-gap: var(--space-close);

            /* Aligned at the bottom, as the design: the seed line so sits on the
               same baseline as the last text line beside it, instead of
               starting at the top and pulling the band taller. */
            align-items: end;

            /* Four layers, read from top to bottom:

               1. the scanline grid from `--interlace`. It stood here written
                  out and so was the tacit model for every further grid —
                  the biome bar copied it and missed the values. Now there
                  is one definition, and both read it \({K0097}\).
               2. the gradient that carries the text — opaque on the left,
                  translucent to the right. It darkens where one reads, and
                  leaves the picture where only the seed button sits.
               3. the picture itself, through image-set: the browser takes
                  AVIF where it can, otherwise WebP. Three widths stand ready,
                  the choice is made by the width query further down.
               4. the base colour in case no picture loads — then the text
                  stands on it and stays readable. */
            background:
                var(--interlace),
                linear-gradient(to right,
                    oklch(12% 0.01 260 / 0.94) 0%,
                    oklch(12% 0.01 260 / 0.86) 38%,
                    oklch(12% 0.01 260 / 0.52) 72%,
                    oklch(12% 0.01 260 / 0.38) 100%),
                image-set(url("/press/skepp-860.avif") type("image/avif"),
                          url("/press/skepp-860.webp") type("image/webp")),
                var(--surface-2);
            background-size: var(--interlace-size), auto, cover, auto;

            /* The picture's content sits on the right — dragon and crew. Without
               that a narrow window shows only water. */
            background-position: center, center, right center, center;
            background-repeat: repeat, no-repeat, no-repeat, repeat;
        }

        /* Base: everything one under the other. The seed stands last, because
           one first reads what the site can do, and then types. */
        [data-type="seed-form"] { margin-block-start: var(--space-open); }

        /* From screen on the seed moves right beside the text — the design's
           arrangement. The slice's tracks are shared for that: the content
           track carries two columns instead of one. */
        /* Larger versions only where they are needed: 48 kB on the phone
           against 186 on the desk. */
        @media (min-width: 35rem) {
            :scope {
                background-image:
                    var(--interlace),
                    linear-gradient(to right,
                        oklch(12% 0.01 260 / 0.94) 0%,
                        oklch(12% 0.01 260 / 0.86) 38%,
                        oklch(12% 0.01 260 / 0.52) 72%,
                        oklch(12% 0.01 260 / 0.38) 100%),
                    image-set(url("/press/skepp-1280.avif") type("image/avif"),
                              url("/press/skepp-1280.webp") type("image/webp"));
            }
        }

        @media (min-width: 60rem) {
            :scope {
                background-image:
                    var(--interlace),
                    linear-gradient(to right,
                        oklch(12% 0.01 260 / 0.94) 0%,
                        oklch(12% 0.01 260 / 0.86) 38%,
                        oklch(12% 0.01 260 / 0.52) 72%,
                        oklch(12% 0.01 260 / 0.38) 100%),
                    image-set(url("/press/skepp-1920.avif") type("image/avif"),
                              url("/press/skepp-1920.webp") type("image/webp"));
                grid-template-areas:
                    "chip   ."
                    "head   ."
                    "text   seed";
                grid-template-columns:
                    [full-start] var(--edge)
                    [content-start] minmax(0, 1fr) 4.375rem 26.25rem
                    [content-end] var(--edge)
                    [full-end];
            }

            /* Label and heading run across both columns: the heading is the
               announcement of the page and should not wrap at three fifths
               while room stays free beside it. Only the sentence below
               shares the line with the seed. */
            > [data-type="chip"]      { grid-column: 2 / 5; }
            > [data-type="heading"]   { grid-column: 2 / 5; }
            > [data-type="prose"]     { grid-column: 2; }
            > [data-type="seed-form"] { grid-column: 4; margin-block-start: 0; }
            > [data-type="chip"], > [data-type="heading"] { align-self: start; }
        }
    }
}

/* Copyright 2026 Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: An item as a page — head with description, below it the dossier in
   two columns. */

@layer slices.slice_item {
    @scope ([data-slice="item"]) {
        :scope {
            display: grid;
            /* **Only rows, no columns.** `gap` also lays itself between the grid
               columns of the page layout and pushed the content column
               eleven points to the right - the block then stood beside the
               line where head and footer begin. */
            row-gap: var(--space-close);
            padding-block: var(--space-close);
            /* A limit for the width (Owner, 7 September 2026: »ich finde es dehnt
               sich zu weit«). On a wide screen the number column pulled the
               values to the right edge, and between the label and its number
               lay half a hand's width of nothing.

               64 rem are a good thousand points: the origin keeps its line
               length, the four level columns stand without crowding, and the
               eye finds from the label to the number. Left aligned and not
               centred - the page below it is too. */
            /* The container for the queries below: without it they measure the
               next ancestor with `container-type`, and there is none here —
               the two columns stayed one. */
            container-type: inline-size;
        }

        /* **The limit holds for the rows, not the block** (Owner, 7 September
           2026). The block carries the columns of the page grid, and
           whoever makes it narrower shrinks them with it: the content then
           stood left of the line where head, banner and footer begin. */
        :scope > * { max-inline-size: 64rem; }

        /* The head: symbol, name, region — and on the right the description.
           All on one line centred to the symbol (Owner, 7 September 2026):
           the name stood above and the region far below, because the symbol
           spanned two rows.

           The name does not wrap: it has the width of the page, and a wrap
           in the title would look like tightness where there is none. */
        [data-part="item-head"] {
            display: flex;
            align-items: center;
            gap: 1.5rem;
            flex-wrap: wrap;

            [data-type="game-icon"] {
                inline-size: 64px;
                block-size: 64px;
                flex: none;
            }

            [data-type="heading"] {
                margin: 0;
                white-space: nowrap;
            }
        }

        [data-part="item-title"] { flex: none; }

        [data-part="item-region"] {
            margin: 0;
            color: var(--gold);
            font-size: var(--text-small);
        }

        /* The description takes the rest of the line and begins only where
           the head ends. **In the head and only there**: `data-part="text"`
           is also the name of the crafting sentence, and that stands in the
           dossier. */
        /* The text as a line of its own, across between title and numbers.
           **Only as a direct child**: `data-part="text"` is also the name of
           the crafting sentence, and that stands in the dossier.

           The line length is capped: above 75 characters the eye finds the
           start of the next line badly, and this text is read and not
           skimmed. */
        :scope > [data-part="text"] {
            margin: 0;
            max-inline-size: 75ch;
            color: var(--ink);
            line-height: 1.6;

            /* The game's flavour text is a quote: italic and in the quotation
               marks of its language (Owner, 7 September 2026). The marks
               come from the language table and stand in the text - `q` sets
               its own, and those would be the browser's. */
            q { font-style: italic; quotes: none; }
        }

        /* The sentence for the search, one line under the quote: it belongs
           to it and yet is not the same - muted, because it does not tell of
           the item but of the entry. */
        [data-part="seo"] {
            margin: 0;
            max-inline-size: 75ch;
            color: var(--ink);
            line-height: 1.6;
        }

        /* The button bottom right, as wide as its word. */
        [data-part="planner"] {
            display: flex;
            justify-content: flex-end;
            margin: 0;

            /* The button otherwise takes the width of its row: in the sheet that
               is right, here the row is the page. */
            [data-type="button"] { inline-size: auto; }
        }

        /* The dossier in two columns as soon as there is room (Owner,
           7 September 2026): the origin on the left, the values on the
           right. On the phone one under the other — the same order, one
           column.

           `align-items: start`, so that the shorter column does not grow
           along, and `column-gap` larger than the line spacing, because two
           columns without air look like a table. */
        /* Two columns as soon as there is room; on the phone one under the
           other. Origin and crafting on the left, the values on the right -
           one element each, so that the height of one column does not shift
           the other. */
        [data-part="columns"] {
            display: grid;
            row-gap: var(--space-close);
        }

        [data-part="left"],
        [data-part="right"] {
            display: grid;
            row-gap: var(--space-close);
            align-content: start;
        }

        /* Between origin and crafting a golden line, above it air up to the
           imagined line under the last entry. */
        [data-part="left"] [data-kind="crafting"] {
            border-block-start: 1px solid var(--gold);
            /* From the line to the heading --space-close, 10.5 points (Owner,
               7 September 2026: »um 8px«, laid onto the scale). The
               governing rule is the one of the same wording in
               slice_items.css: it lies in the same layer, comes later and wins. */
            padding-block-start: var(--space-close);
        }

        @container (min-width: 45rem) {
            [data-part="columns"] {
                grid-template-columns: repeat(2, minmax(0, 1fr));
                align-items: start;
            }

            /* And between the columns a golden line: the left edge of the right
               column, with air on both sides. */
            [data-part="right"] {
                border-inline-start: 1px solid var(--gold);
                padding-inline-start: 1.5rem;
                margin-inline-start: 1.5rem;
            }
        }

        /* The values as a table without lines ({T0056S1}, Owner, 7 September
           2026): one column per level, the head carries its colour mark.

           The spacing between the columns is one rem, sixteen points, as the
           owner named it. The page's ladder falls with the golden ratio and
           offers 10.5 (`--space-close`) and 27.5 (`--space-open`) here -
           neither hits, and between two number columns ten is too tight and
           twenty-seven too wide. The row gap stays from the ladder. */
        [data-part="values"] {
            inline-size: 100%;
            border-collapse: separate;
            border-spacing: 1rem var(--space-tight);
            margin-inline: calc(var(--space-close) * -1);

            th, td {
                padding: 0;
                text-align: end;
                font-weight: 500;
                font-variant-numeric: tabular-nums;
                color: var(--ink-bright);
            }

            /* The label on the left, in the ink colour: it is the question, the
               number is the answer. */
            tbody th {
                text-align: start;
                font-weight: 400;
                color: var(--ink);
            }

            /* The head: the level in its rank colour, small and centred above its
               column — the same colours as the pills in the sheet. */
            thead th {
                text-align: end;
                font-size: var(--text-label);
                color: var(--rank, var(--ink));
            }

            thead th[data-rank="1"] { --rank: var(--rank-1); }
            thead th[data-rank="2"] { --rank: var(--rank-2); }
            thead th[data-rank="3"] { --rank: var(--rank-3); }
            thead th[data-rank="4"] { --rank: var(--rank-4); }

            /* The identifier: an input, no number — fixed-width type and a blank
               line above it, as in the sheet ({T0039S1}). */
            tr[data-kind="id"] td {
                font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
                font-size: var(--text-small);
                font-weight: 400;
                user-select: all;
            }

            tr[data-kind="id"] th,
            tr[data-kind="id"] td { padding-block-start: 1lh; }
        }

        /* The way back with a golden angle (Owner, 7 September 2026): a
           chevron to the left, in the same colour as the corners of the
           tiles. It stands as a pseudo-element and not in the text - a sign
           that is no word belongs neither in the language table nor in the
           reading stream. */
        [data-part="back"] {
            margin: 0;
            /* One step larger on the scale (Owner, 7 September 2026: »ca. 6 px
               groesser«): the base size instead of --text-small, so 17
               instead of 13.4 points, and the chevron with 1.45 em at 25
               instead of 19 - that hits the six points. The next step,
               --text-large, made the chevron 30 points and tore the way back
               out of the scale of the page. Both on one centre line. */
            font-size: var(--scale-base);

            a {
                display: inline-flex;
                align-items: center;
                gap: 0.4em;
                color: var(--ink);
                text-decoration: none;
            }

            a::before {
                content: "\2039";
                color: var(--gold);
                font-size: 1.45em;
                line-height: 1;
            }

            /* The line on pointing under the word, not under the chevron: hence
               on the span and not on the link. */
            a:where(:hover, :focus-visible) { color: var(--ink-bright); }

            a:where(:hover, :focus-visible) span {
                text-decoration: underline;
                text-underline-offset: 3px;
            }
        }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: The items overview — one expandable block per region. */

@layer slices.slice_items {
    /* Two roots, one shape ({T0056S1}): the dossier stands in the sheet of
       the overview and on an item's own page, and it is the same. The
       alternative would have been to copy sixty rules - and then two
       dossiers look different after the next rebuild. What is the single
       page's own stands in slice_item.css. */
    @scope ([data-slice="items"], [data-slice="item"], [data-slice="tree"]) {
        :scope {
            padding-block: 2.5rem;
            display: grid;
            row-gap: 1.25rem;
        }

        :scope > * { grid-column: content; }

        [data-part="intro"] {
            margin: 0.5rem 0 0;
            max-inline-size: var(--measure);
            color: var(--ink-quiet);
        }

        /* The number beside the region stays the total, even when the filter
           hides something. Counting it along would only work with script,
           and a number that is sometimes right would be worse than one that
           always means the same. */

        /* The hits stand above everything else, because they are the answer to
           the question just asked. */
        /* The same tiles and sheets as in the region lists, in the same grid —
           the grid is the rule below that holds for every
           [data-part="grid"]. Here stands only the status line. */
        [data-part="results"] {
            p {
                margin: 0 0 0.5rem;
                color: var(--ink-quiet);

                /* »Suchergebnisse: 19 · Holz« — the label in the same type and colour
                   as the number, the colon in the CSS as with every label in
                   the sheet (Owner, 2 September 2026). */
                span::after { content: ":"; }
            }
        }

        /* ---- The region block ------------------------------------------ */

        /* The biome colour stands as a property of its own on the block, so
           that bar, face and subheadings take the same. The token for the
           Black Forest is called --biome-forest, our key blackforest — that
           is the only place where the two names diverge, and it is resolved
           here instead of in the data.
           
           This block got lost in the rebuild of the filter: I cut blocks by
           their position in the text instead of by their name, and the cut
           lay too far. After that --biome was empty, the gradient `none` and
           the face grey — the rule still stood in the CSS, it only had
           nothing left to colour. */
        /* Before the first region double the grid gap (Owner, 2 September
           2026): the search and its hits are a question, the regions are the
           stock, and between the two lies more than between two rows.
           Measured 20 px before, now 40 — on the first `details`, whether
           the control strip stands before it or the hit list. A margin,
           because the block carries a face with an edge and a padding would
           enlarge it; the regions keep their spacing among themselves
           through a margin anyway. */
        [data-part="region"]:first-of-type { margin-block-start: 1.25rem; }

        [data-part="region"] {
            --biome: var(--surface-4);
            margin-block-end: 1.25rem;

            /* The picture strip as the second background layer of the bar,
               directly under the scanline grid.

               The Deep North stood here long without — »there is nothing to
               photograph there«, it said, and that was true: until Valheim
               1.0 the region was not built out and reported itself as
               mountain ({K0207}, GetBiome returned Mountain above 0.4).
               Since the 1.0 patch it is a biome of its own with 48 items,
               and the owner supplied the photograph on 9 September. */
            &[data-biome="meadows"] > summary     { background-image: var(--interlace), var(--bar-carrier), linear-gradient(var(--lightening), var(--lightening)), url("/biome/meadows.webp"), var(--biome-gradient); }
            &[data-biome="blackforest"] > summary { background-image: var(--interlace), var(--bar-carrier), linear-gradient(var(--lightening), var(--lightening)), url("/biome/blackforest.webp"), var(--biome-gradient); }
            &[data-biome="swamp"] > summary       { background-image: var(--interlace), var(--bar-carrier), linear-gradient(var(--lightening), var(--lightening)), url("/biome/swamp.webp"), var(--biome-gradient); }
            &[data-biome="mountain"] > summary    { background-image: var(--interlace), var(--bar-carrier), linear-gradient(var(--lightening), var(--lightening)), url("/biome/mountain.webp"), var(--biome-gradient); }
            &[data-biome="plains"] > summary      { background-image: var(--interlace), var(--bar-carrier), linear-gradient(var(--lightening), var(--lightening)), url("/biome/plains.webp"), var(--biome-gradient); }
            &[data-biome="mistlands"] > summary   { background-image: var(--interlace), var(--bar-carrier), linear-gradient(var(--lightening), var(--lightening)), url("/biome/mistlands.webp"), var(--biome-gradient); }
            &[data-biome="ashlands"] > summary    { background-image: var(--interlace), var(--bar-carrier), linear-gradient(var(--lightening), var(--lightening)), url("/biome/ashlands.webp"), var(--biome-gradient); }
            &[data-biome="deepnorth"] > summary   { background-image: var(--interlace), var(--bar-carrier), linear-gradient(var(--lightening), var(--lightening)), url("/biome/deepnorth.webp"), var(--biome-gradient); }
            &[data-biome="ocean"] > summary       { background-image: var(--interlace), var(--bar-carrier), linear-gradient(var(--lightening), var(--lightening)), url("/biome/ocean.webp"), var(--biome-gradient); }

            &[data-biome="meadows"]     { --biome: var(--biome-meadows); }
            &[data-biome="blackforest"] { --biome: var(--biome-forest); }
            &[data-biome="swamp"]       { --biome: var(--biome-swamp); }
            &[data-biome="mountain"]    { --biome: var(--biome-mountain); }
            &[data-biome="plains"]      { --biome: var(--biome-plains); }
            /* The Mistlands had a lightening of twelve percent here — they lay
               at 37.7 mean lightness against about 98 on average. The
               owner's source brings the exposure along and lies at 103.0;
               the layer would now lighten twice. The mechanism stays, the
               value goes. */

            &[data-biome="mistlands"]   { --biome: var(--biome-mistlands); }
            &[data-biome="ashlands"]    { --biome: var(--biome-ashlands); }
            &[data-biome="deepnorth"]   { --biome: var(--biome-deepnorth); }
            &[data-biome="ocean"]       { --biome: var(--biome-ocean); }
        }

        /* ---- The bar ---------------------------------------------------- */

        /* The direct child, not every summary below. Without the `>` every
           card head and every section line carried the biome strip —
           visible only once the cards themselves became <details>. A
           selector that reaches too far is right as long as there is only
           one target. */
        [data-part="region"] > summary {
            position: relative;
            display: flex;
            justify-content: space-between;
            align-items: center;
            gap: 1rem;
            /* The aspect ratio of the owner's sources: 4717x646. Before, 120
               pixels stood here fixed, and the picture was cropped to that
               measure by `cover` — with a view set by hand that means
               discarding it. The bar now follows the picture.

               **The width always follows the track, never the picture.**
               With a minimum height and an aspect ratio the browser
               converted the minimum height into a minimum width — 120 pixels
               times 7.3 is 876 —, and the bar ran over to the right below an
               880 pixel window ({C0021}). `max-inline-size: 100%` binds it to
               its track; below that the ratio yields, the minimum height
               stays, and `cover` crops at the sides — with a panorama the
               right direction. The minimum height is small, just so that
               picture and label stay readable (Owner, 2 September 2026):
               4.5 rem, one pixel line of the label with air, no px. */
            aspect-ratio: 4717 / 646;
            max-inline-size: 100%;
            min-block-size: 4.5rem;
            padding: 0 1.25rem;
            cursor: pointer;
            overflow: hidden;
            color: var(--ink-bright);

            /* Without a picture the biome gradient stays — the Deep North has no
               photograph, because there is nothing to see there. */
            /* The text carrier, as a layer of its own — exactly the role the
               second gradient plays in the hero.

               It was missing here, and that was the real error behind the
               too dark grid: the grid carried the work it is not made for.
               Turned up to 28 percent it darkened the whole picture so that
               the type would carry — and was no longer recognisable as a
               grid. Two tasks in one layer, and both done badly.

               Separated, each can do what it should: the grid rasters at 14
               percent, the carrier carries where text stands. Unlike in the
               hero at both edges, because here the region name stands on the
               left and the count on the right; the middle stays free and
               shows the photograph ({K0097}). */
            --bar-carrier: linear-gradient(90deg,
                oklch(10% 0.01 260 / 0.88) 0%,
                oklch(10% 0.01 260 / 0.62) 22%,
                oklch(10% 0.01 260 / 0.30) 42%,
                oklch(10% 0.01 260 / 0.30) 58%,
                oklch(10% 0.01 260 / 0.62) 78%,
                oklch(10% 0.01 260 / 0.88) 100%);

            --biome-gradient: linear-gradient(90deg,
                color-mix(in oklab, var(--biome) 35%, var(--surface-0)),
                color-mix(in oklab, var(--biome) 12%, var(--surface-0)));

            /* Without a picture grid and gradient stay — the Deep North has no
               photograph. The regions with one push their layer in between;
               the order and the sizes hold for both cases, because the last
               size is repeated for every further layer. */
            /* A lightening layer every region can set for itself. It lies under
               the text carrier and above the picture and is composited with
               `screen`: that lifts the shadows and leaves the lights, instead
               of laying a grey veil over it.

               For all regions it is transparent and does nothing — it stands
               in the base rule anyway, so that the number of layers is the
               same everywhere and the lists below fit both cases. */
            --lightening: transparent;

            background-image:
                var(--interlace),
                var(--bar-carrier),
                linear-gradient(var(--lightening), var(--lightening)),
                var(--biome-gradient);
            background-blend-mode: normal, normal, screen, normal, normal;
            /* Three values, although only two layers lie here: the regions with a
               photograph push the picture in between, and CSS repeats
               cyclically with too few values — the gradient would then have
               got the grid's measure and been tiled. Surplus values, on the
               other hand, are simply not used. */
            background-size: var(--interlace-size), auto, auto, cover, cover;
            background-position: center, center, center, center, center;
            background-repeat: repeat, no-repeat, no-repeat, no-repeat, no-repeat;

            box-shadow:
                inset  2px  2px 0 oklch(33% 0.014 var(--hue-surface)),
                inset -2px -2px 0 oklch(12% 0.010 var(--hue-surface));
        }

        /* The scanline grid comes from `--interlace` and lies as a layer in
           the bar's `background` — not as an overlay in a `::before`.

           It stood here written out, with one dark line in two at 28
           percent instead of one in three at 14: three times as dark, and
           because with a two-pixel tile line and gap run into each other on
           a double-resolution screen, nothing of the grid arrived. Only a
           dark picture. The comment beside it claimed »as in the hero of the
           landing page« and so covered the error \({K0097}\).

           The cross-check belongs to it: the strips themselves are fine. A
           fresh run of vgarun site render biome-strips gives 88.1 mean lightness,
           served are 87.9 — that is the WebP compression. The script darkens
           at no point. */

        [data-part="region-name"],
        [data-part="count"] {
            position: relative;
            text-shadow: 0 1px 3px oklch(0% 0 0 / 0.8);
        }

        /* The pixel font on the region bar: it is the heading of the block,
           and what comes below subordinates itself to it. Before, it carried
           the base type and the category below the pixel font — that turned
           the hierarchy upside down. */
        [data-part="region-name"] {
            font-family: "Press Start 2P", monospace;
            /* The same size and line height as `heading` at level 3 — the
               category stood there before, and the owner wants it unchanged
               one level higher. A number of its own here would mean setting
               the same type at two sizes in two places. */
            font-size: var(--text-large);
            line-height: 1.55;
            color: var(--ink-bright);
        }

        /* The number with its unit, on the right. The unit is smaller and
           muted: it says what was counted, and steps back behind the number. */
        /* Bound to `summary`, otherwise the group's rule wins: it stands
           further down and is just as specific, and at a tie the order
           decides. The count on the strip therefore stood muted instead of
           white. */
        [data-part="region"] > summary [data-part="count"] {
            display: flex;
            align-items: baseline;
            gap: 0.5em;
            margin-inline-start: auto;
            /* White like the region name: both stand on the same strip and belong
               together. Muted it would be a second level here that does
               not exist. */
            color: var(--ink-bright);
            font-family: "Press Start 2P", monospace;
            font-size: var(--text-large);
            line-height: 1.55;
            font-variant-numeric: tabular-nums;

            i { font-style: normal; }
        }

        /* On the phone the word drops and the number stays. It stands there
           alone and needs no explanation — the room is scarcer than the
           information is worth. Mobile first: the resting value is without
           the word, from 30 rem it is added — only `min-width`, as AW7
           demands; the query stood here the other way round, bounded above
           ({C0022}). */
        [data-part="count"] i { display: none; }

        @media (min-width: 30rem) {
            [data-part="count"] i { display: inline; }
        }

        /* ---- The open area ---------------------------------------------- */

        [data-part="body"] {
            position: relative;
            /* 1 rem and not 1.25: the sum does not work out otherwise. The content
               area is 1360 wide at a 1440 px window; minus 2×16 leaves 1328,
               and 5×256 + 4×12 is exactly 1328. */
            padding: 1rem;
            background: var(--surface-1);
            box-shadow:
                inset  2px  2px 0 oklch(33% 0.014 var(--hue-surface)),
                inset -2px -2px 0 oklch(12% 0.010 var(--hue-surface));
            overflow: hidden;
        }

        /* The biome colour as a gradient: strongest at the top, slightly
           slanted, transparent towards the bottom. No pattern, no movement —
           the face should colour the region and not point to itself.
           
           Moving lines stood here. They were too loud for a face on which
           one reads: what moves draws the eye, and the eye was needed here
           for card names.
           
           170 degrees instead of 180: just so slanted that the edge does
           not lie level and the gradient does not look like a strip. */
        [data-part="body"]::before {
            content: "";
            position: absolute;
            inset: 0;
            /* Half as strong as at first — 13 instead of 26 percent at the top.
               The owner: the face should be coloured, not painted.
               
               And it is not removed when a text on it has too little
               contrast: then the text becomes bold or larger. Taking a given
               background away to meet a contrast rule solves the wrong thing. */
            background: linear-gradient(
                170deg,
                color-mix(in oklab, var(--biome) 13%, transparent) 0%,
                color-mix(in oklab, var(--biome)  7%, transparent) 35%,
                color-mix(in oklab, var(--biome)  3%, transparent) 70%,
                transparent 100%);
            pointer-events: none;
        }

        [data-part="body"] > * { position: relative; }

        /* ---- The subheading per category --------------------------------- */

        [data-part="group"] {
            display: flex;
            justify-content: space-between;
            align-items: baseline;
            gap: 1rem;
            margin: 1.25rem 0 0.5rem;
            padding-block-end: 0.25rem;
            border-block-end: 1px solid color-mix(in oklab, var(--biome) 30%, transparent);

            &:first-child { margin-block-start: 0; }

            /* **An exception from the element, expressly here.** `heading`
               carries the pixel font; at this level it would be too loud,
               because the region bar above already carries it. So the base
               type, muted and in capitals — and only here. Everywhere else
               `heading` stays as it is ({K0054}). */
            /* --ink and not --ink-quiet: on the biome face the quiet step stood at
               4.29 to 1 and so under AA — measured on a plain ground, so
               certain and not estimated. The face stays as it is; the type
               is corrected ({K0096}). */
            [data-type="heading"] {
                font-family: inherit;
                font-size: 0.9375rem;
                font-weight: 400;
                color: var(--ink);
                text-transform: uppercase;
                letter-spacing: 0.08em;
            }

            /* The count here without the pixel font, for the same reason — but
               with the same structure: spacing between number and unit, and
               the unit upright. Both stood only on the bar, because its rule
               became specific, and here »27Items« stuck together in italics. */
            [data-part="count"] {
                display: flex;
                align-items: baseline;
                gap: 0.5em;
                font-family: inherit;
                font-size: 0.9375rem;
                /* The same step as the heading beside it, for the same reason: it
                   stands on the same face. */
                color: var(--ink);
                font-variant-numeric: tabular-nums;

                i { font-style: normal; }
            }
        }

        /* The filter switches whole categories, not single cards.
           
           The categories are no accordion — they stand open one under the
           other, and the switch hides the block with its heading. Six rules
           instead of a library, and the state stands in the form.
           
           **Reversed default:** what is not switched on is gone. Before,
           everything was visible as long as nobody chose anything; now it
           is the resources, because the checkbox carries `checked`. Whoever
           switches all off sees nothing — that is honest and reversible. */
        [data-part="group"],
        [data-part="group"] + [data-part="grid"] { display: none; }


        /* Heading and grid belong together: what vanishes is the pair. The
           cards in it need no rule of their own — they lie in the grid and
           go with it. */
        &:has(#k-material:checked) [data-part="group"][data-kind="material"],
        &:has(#k-tool:checked)     [data-part="group"][data-kind="tool"],
        &:has(#k-armour:checked)   [data-part="group"][data-kind="armour"],
        &:has(#k-weapon:checked)   [data-part="group"][data-kind="weapon"],
        &:has(#k-food:checked)     [data-part="group"][data-kind="food"],
        &:has(#k-building:checked) [data-part="group"][data-kind="building"],
        &:has(#k-misc:checked)     [data-part="group"][data-kind="misc"] {
            display: flex;
        }

        &:has(#k-material:checked) [data-part="group"][data-kind="material"] + [data-part="grid"],
        &:has(#k-tool:checked)     [data-part="group"][data-kind="tool"]     + [data-part="grid"],
        &:has(#k-armour:checked)   [data-part="group"][data-kind="armour"]   + [data-part="grid"],
        &:has(#k-weapon:checked)   [data-part="group"][data-kind="weapon"]   + [data-part="grid"],
        &:has(#k-food:checked)     [data-part="group"][data-kind="food"]     + [data-part="grid"],
        &:has(#k-building:checked) [data-part="group"][data-kind="building"] + [data-part="grid"],
        &:has(#k-misc:checked)     [data-part="group"][data-kind="misc"]     + [data-part="grid"] {
            /* Must be `grid` and the same as in the grid's base rule.

               This line stood on both values within an hour and was wrong
               once each time: when columns were built, the `grid` here
               ignored the `columns`; when the grid came back, the `block`
               ignored the column setting and laid all cards one under the
               other — `grid-template-columns` stood there and did nothing,
               because the display type was another.

               **The visible state repeats the display type of the base
               rule.** Whoever changes the one and forgets the other sees it
               only in the picture ({K0099}). */
            display: grid;
        }

        /* The same pixel edge as card and button, so that a region block
           belongs to the language and does not stand beside it. */
        [data-part="region"] {
            background: var(--surface-1);
            box-shadow:
                inset  2px  2px 0 oklch(33% 0.014 var(--hue-surface)),
                inset -2px -2px 0 oklch(12% 0.010 var(--hue-surface));
        }

        /* The marker stays: it says without colour and without movement
           whether open or closed, and that is the only information that
           counts here. */
        [data-part="count"] {
            color: var(--ink-quiet);
            font-variant-numeric: tabular-nums;
        }

        /* The grid carries cards of 256 px. At a 1440 px window the content
           keeps 1360, minus the padding 1328 — and 5×256 + 4×12 is exactly
           that.
           
           The first version reckoned with 1440 and a 16 px gutter, came to
           1344 and was so 24 too wide: four cards stood per row, each
           stretched to 318. It was measured only on the picture, not on the
           markup — there a too wide minmax looks just like a fitting one.
           
           auto-fill instead of a fixed column count: narrower it becomes
           fewer by itself, without a breakpoint per step. */
        /* Grid, and the open card overlays instead of pushing.

           For a moment it was columns. The reason for that was right — in
           the grid the row height is that of the tallest card, so an opened
           one turned four neighbours into empty boxes. The price was not
           worth it: on every click the column set redistributed its cards,
           and the whole list jumped. The owner: »so fluppt die liste immer
           hin und her«.

           Now the card keeps its cell, and its dossier lays itself over what
           stands below. Nothing moves, nothing is re-sorted.

           My objection against it was thought the wrong way round: what is
           covered is not what one looks at next — one looks at the card one
           has just opened. What one sees with the column set, on the other
           hand, on every click, is the re-sorting. */
        [data-part="grid"] {
            display: grid;
            /* min(256px, 100%): below 256 pixels of track — at a 320 pixel window
               the area keeps 245 — the card yields instead of protruding
               over the edge. The width run saw it only once the bar no
               longer overflowed before it ({C0021}). */
            grid-template-columns: repeat(auto-fill, minmax(min(256px, 100%), 1fr));
            gap: 12px;
            /* The cards do not stretch to the row height — without that an open
               card would pull its neighbours along in length. */
            align-items: start;
            margin: 0;
            padding: 0;
        }

        /* ---- The tile is a widget ({T0073S2}) ---------------------------- */
        /* Its rules stand in widgets/item_tile/item_tile.css; here only what
           the grid of the items page wants of it remains: nothing. */
        /* ---- The sections in the sheet ----------------------------------- */

        /* The dividing line stands above every section except the first: so
           it separates and does not frame. */
        /* On the own page no grey hairline (Owner, 7 September 2026): in the
           sheet it separates two sections one under the other, there origin
           and values stand side by side and a golden line separates them.
           The exception stands here, because both rules lie in the same
           layer and the later wins. */
        /* On the own page the crafting shows the base level ({T0056S1}): in
           the sheet the pill chooses which level is visible, on the page
           there is none any more - and without a choice the quantities of
           all four levels ran into each other, 2, 3, 5 and 8 wood became
           »2358 Holz«. Stands here, because the rules for data-level lie in
           this file and in the same layer the later wins. */
        :scope[data-slice="item"] [data-kind="crafting"] [data-level]:not([data-level="1"]) {
            display: none;
        }

        /* The testers' mark under the crafting ({T0062S3}): gold, because it
           says that somebody saw it in the game - that is what should be
           remembered here. Holds in the sheet as on the page. */
        [data-part="section"] [data-kind="checked"] {
            color: var(--gold);
        }

        :scope[data-slice="item"] [data-part="section"] + [data-part="section"] {
            border-block-start: none;
            padding-block-start: 0;
        }

        /* Except between origin and crafting: a golden line there (Owner,
           7 September 2026). Stands here, because the rule above would
           otherwise clear it away again - the same file, the same layer. */
        :scope[data-slice="item"] [data-part="section"] + [data-part="section"][data-kind="crafting"] {
            /* More specific than the switch-off above, otherwise that wins - and
               then no line stands there at all, neither grey nor gold. */
            border-block-start: 1px solid var(--gold);
            /* Air between line and heading: --space-close, 10.5 points (Owner,
               7 September 2026, »um 8px« laid onto the scale). The
               switch-off above set it to 0; slice_item.css never got its
               turn. */
            padding-block-start: var(--space-close);
        }

        [data-part="section"] + [data-part="section"] {
            border-block-start: 1px solid oklch(from var(--ink-quiet) l c h / 0.20);
        }

        [data-part="head"] {
            display: flex;
            align-items: baseline;
            gap: 0.5rem;
            margin: 0;
            padding-block: 0.75rem 0.375rem;
        }

        [data-part="title"] {
            color: var(--ink-bright);
            font-size: 0.8125rem;
            font-weight: 500;
            text-transform: uppercase;
            letter-spacing: 0.07em;
        }

        [data-part="aside"] {
            min-inline-size: 0;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
            margin-inline-start: auto;
            color: var(--ink);
            font-size: 0.75rem;
        }

        /* Two tracks: one for labels, one for what stands behind them. The
           ordinary rows span both; the cost rows lay themselves in by
           subgrid and so share the anchor line — »Kosten insgesamt« and
           »Update auf Stufe 4« differ in length, and the ingredients still
           begin at the same place (Owner, 2 September 2026, after the
           design). The gap between label and ingredient and between the
           ingredients is the same: about 20 pixels, the owner's setting; it
           lies between --space and --space-open, hence here as a value of
           its own and not as a token. */
        [data-part="rows"] {
            --gap: 1.25rem;
            display: grid;
            grid-template-columns: max-content 1fr;
            column-gap: var(--gap);
            row-gap: 0.3125rem;
            padding-block-end: 0.5rem;
            font-size: 0.8125rem;
            line-height: 1.45;

            > * { grid-column: 1 / -1; }

            /* Eight pixels more between running text and list, in both
               directions — as a rule, even when running text follows a list
               again (Owner, 2 September 2026). The rule hangs on the
               transition and not on one line: a sentence before the costs, a
               sentence after the values, both the same half step. Hidden
               siblings — the sentences of the other levels — are of the same
               kind as the visible one, hence `+` carries across them too. */
            [data-part="text"] + :not([data-part="text"]),
            :not([data-part="text"]) + [data-part="text"] {
                padding-block-start: 0.5rem;
            }
        }

        /* **One grey in the sheet** (Owner, 2 September 2026): everything
           muted carries `--ink`, the lighter of the two steps — labels,
           sentences, the region top right, the versions, the costs and the
           quiet notes. Before, three degrees stood side by side, muted,
           quiet and quieter, and on a sheet of 400 pixels that read as
           disorder, not as order. What stands out does so through white and
           gold, not through a third grey. */
        /* **Two sorts of names, two colours, one weight.**

           Places carry gold — regions, find places, workbenches. The same
           colour as corner and frame: what is gold belongs to this sheet and
           leads on. Items carry white, in the same type weight (Owner,
           1 September 2026).

           The weight stays the same, because the difference is the kind and
           not the rank. Whoever has read the colour once recognises at the
           next sheet at once what is a place and what a thing. */
        /* `row` is a line with a label — »Produkt: Schmiede« —, `text` is
           running text: the sentence for the station, and what {T0020}
           still brings in sentences. Both share colour and markup; they are
           kept apart because the spacing hangs on the transition (see rows). */
        [data-part="row"],
        [data-part="text"] {
            margin: 0;
            color: var(--ink);

            /* The label in front, muted and with a colon: it says what kind of
               line follows. The colon stands in the CSS and not in the text
               — in seven languages it would otherwise be maintained seven
               times, and in French a narrow space would stand before it. */
            span:first-child::after { content: ":"; }
            span:first-child { margin-inline-end: 0.375rem; margin-inline-start: 0; }

            b { color: var(--gold); font-weight: 500; }

            /* The comma of the enumeration: muted and upright. It separates the
               names and does not belong to them — hence neither gold nor
               bold. The pull-back is small: 0.2 em pulled the comma into the
               letter before it, because the space in the markup is narrower
               than thought. A comma that sticks to the word reads like a typo. */
            em {
                margin-inline-start: -0.25ch;
                color: var(--ink);
                font-style: normal;
            }

            /* Items: white, same weight. No line here carries one yet — with the
               ingredients from {T0020} there will be. The rule already
               stands so that it is not struck as dead code. */
            b[data-kind="item"] { color: var(--ink-bright); }

            i {
                display: block;
                color: var(--ink);
                font-size: 0.75rem;
            }
        }

        [data-part="stat"] {
            display: flex;
            align-items: baseline;
            gap: 0.5rem;
            margin: 0;
            color: var(--ink);

            b {
                margin-inline-start: auto;
                color: var(--ink-bright);
                font-weight: 500;
                font-variant-numeric: tabular-nums;
            }

            /* The number per level, upright. Until {T0023S4} the slope stood here
               as a green »+50« beside it; now it is computed in, and the
               choice shows the level's value. */
            b i { font-style: normal; }
        }

        /* The identifier as the last line of the values ({T0039S1}): the
           prefab name the game's console accepts. Unlike everything above it
           is no number but an input — fixed-width type so that it reads as
           such, and selectable so that one can grab it with the mouse. It is
           up to thirty characters long and may wrap instead of bursting the
           line. */
        /* One blank line of spacing above the identifier (Owner, 7 September
           2026): it belongs to the values but is none — the spacing says so
           without drawing a line. `lh` is exactly one line height; the
           statement above it carries the value for browsers that do not
           know the unit yet. */
        /* The name in the head of the sheet leads to the own page
           ({T0056S1}): no tone of its own, no permanent line — only on
           pointing an underline, and that says enough (Owner, 7 September
           2026). */
        [data-part="stat"][data-kind="id"] {
            margin-block-start: 1.4em;
            margin-block-start: 1lh;
        }

        [data-part="stat"][data-kind="id"] b {
            font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
            font-size: var(--text-small);
            font-weight: 400;
            user-select: all;
            text-align: end;
            word-break: break-word;
        }

        /* The versions: label on the left, the pills on the right — a grid
           with two tracks, the row of pills in it a real row. */
        [data-part="versions"] {
            display: grid;
            grid-template-columns: auto 1fr;
            align-items: center;
            column-gap: 0.5rem;
            margin: 0;
            /* Eight pixels more air to the values below (Owner, 2 September
               2026): the pills are controls, the numbers are information,
               and half a step separates the one from the other. As padding
               of the row, not as a margin — the grid's gap stays as it is. */
            padding-block-end: 0.5rem;
            color: var(--ink);

            > span:first-child::after { content: ":"; }

            [data-part="pills"] {
                display: flex;
                justify-content: end;
                gap: 0.375rem;
            }
        }

        /* ---- The crafting ({T0023S3}) ------------------------------------ */

        /* The sentence for the station is a line like the origin's and takes
           its rules: place golden, thing white, rest muted. It begins with
           text and not with a label, hence the colon of the line rule does
           not apply here. */

        /* The cost line: label in front, behind it the ingredients. Two
           columns, so that the ingredients wrap at an anchor line — the
           second line begins under the first ingredient, not under the
           label (design, sketch). */
        [data-part="cost"] {
            display: grid;
            grid-template-columns: subgrid;
            align-items: baseline;
            margin: 0;
            color: var(--ink);

            > span:first-child::after { content: ":"; }

            /* The label changes per level: one <i> per level, upright and in
               the colour of the label. Which is visible, the level choice
               from {T0023S4} decides. */
            > span:first-child i { font-style: normal; }
        }

        /* Eight pixels more air between the total costs and the update
           (Owner, 2 September 2026) — the same half step as under the
           versions: two pieces of information one should not read as one
           list. As padding of the second line: if it drops at the highest
           level, the air drops with it. */
        [data-part="cost"] + [data-part="cost"] { padding-block-start: 0.5rem; }

        /* **At the highest level the update line drops.** There is no level five,
           so the line stands without a label and its ingredients without their
           numbers — the owner saw it on the axe (23 September 2026, {T0146S6}).
           The rule is written in `widgets.item_sheet` as well and loses there:
           this file sets the display of the line, and of two layers the later
           one wins, whatever the selectors weigh. So it stands here, beside the
           rule it has to beat. */
        [data-type="item-sheet"]:has([data-rank="2"] > :checked) [data-max="2"],
        [data-type="item-sheet"]:has([data-rank="3"] > :checked) [data-max="3"],
        [data-type="item-sheet"]:has([data-rank="4"] > :checked) [data-max="4"] {
            display: none;
        }

        /* The ingredients as a wrapping row: flex, because it is a real
           one-dimensional row, and not nested — every ingredient is inside a
           piece of running text, symbol beside number and name. */
        [data-part="parts"] {
            display: flex;
            flex-wrap: wrap;
            align-items: baseline;
            gap: 0.125rem var(--gap);

            /* The yield behind the ingredients: »ergibt 20«. Smaller, but in the
               quiet step and not below it — 0.75 rem is no large text, and
               the quiet step is the last that holds AA on this face
               (03-tokens.md). */
            > i {
                color: var(--ink);
                font-style: normal;
                font-size: 0.75rem;
            }
        }

        /* An ingredient: symbol, number, name — white like every item in the
           sheet, in the same weight as the places ({K0032}, 03-tokens.md).
           The symbol is set by the element as large as an icon; here it
           stands in the line and takes its measure — the same exception as
           in the head and on the tile, expressly at the caller. */
        /* An ingredient is a piece of running text: symbol, number, name on
           one line, and the line lies on the baseline of the label. No grid
           and no flex in it — as a grid the symbol was the first element,
           its lower edge became the baseline of the ingredient, and the name
           stood a quarter step above the label (Owner, 2 September 2026). */
        [data-part="part"] {
            white-space: nowrap;

            /* The symbol in running text: base.css sets every picture as a block,
               here it stands in the line. The size follows the type, not the
               element — 1.25 em, so the line height minus some air —, and
               sits with 0.3 em below the baseline so that it frames the cap
               height of the name and does not let the line grow. Not
               pixelated: the element rasters because it enlarges; here a 128
               is shrunk to 16, and nearest neighbour makes crumbs of that.
               All expressly at the caller ({K0054}). */
            [data-type="game-icon"] {
                display: inline-block;
                inline-size: 1.25em;
                block-size: 1.25em;
                vertical-align: -0.3em;
                image-rendering: auto;
            }

            b {
                color: var(--ink-bright);
                font-weight: 500;
                font-variant-numeric: tabular-nums;
            }

            /* The quantity per level, upright. */
            i { font-style: normal; }
        }

        /* In the sheet the button takes the width of its tile; on the own
           page that would be the width of the page, and there it stands on
           the right and as wide as its word ({T0056S1}). The scope holds for
           both roots since the cut — the exception therefore stands here and
           not where it would have to compete against this rule in the same
           layer. */
        [data-part="planner"] {
            margin: 0.875rem 0 0;

            [data-type="button"] { inline-size: 100%; }
        }

        /* The tick behind »Im Bauplan« stood here from {T0073S8} to
           {T0073S11}; since {T0073S12} the button is a link without state
           (Owner: one item in the planner, no tick). */

        :scope[data-slice="item"] [data-part="planner"] [data-type="button"] {
            inline-size: auto;
        }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: The legal pages — readable, unobtrusive, without ornament. */

@layer slices.slice_legal {
    @scope ([data-slice="legal"]) {
        :scope {
            display: grid;
            gap: var(--space-open);

            /* 68 characters. A legal text is read from top to bottom and not
               skimmed; the line may not be longer than the eye carries back
               without a jump. In ch, because the limit is a number of
               characters and no length. */
            max-inline-size: 68ch;
            margin-inline: auto;
            padding-block: var(--space-open);
        }

        > h1 {
            font-size: var(--text-large);
            margin: 0;
        }

        section {
            display: grid;
            gap: var(--space-tight);
        }

        h2 {
            /* `--text-base` does not exist and never did: the heading inherited its
               size, and that is the running text ({T0146S6}). Now it says so. */
            font-size: var(--text);
            margin: 0;
            color: var(--ink-bright);
        }

        p { margin: 0; }

        /* The lead carries the note on the governing language version and
           therefore stands quieter than the text, but before it. */
        [data-part="intro"] { color: var(--ink-quiet); }

        [data-part="updated"] {
            color: var(--ink-quiet);
            margin-block-start: var(--space-open);
        }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: The palette of the world map — **the** source both sides read from
   ({T0027S18}).

   Up to here these eighteen colours stood in `weltgenerator/src/karte.rs`,
   compiled into Rust. The sheet did not know them: a legend would have had
   to write them a second time, and a second colour scheme would only exist
   with newly computed tiles.

   Now they stand here, and `vgarun valheim create constants` generates
   `world-generator/src/palette.rs` from them — the same way `tools/place-kinds.py`
   goes for the place kinds: one source, one product, no two truths. Whoever
   changes a colour changes it here and runs the tool.

   **Why CSS of all things and not a data table.** Because the second side is
   a browser. As custom properties the colours are available to every
   element without anybody transferring them — a legend, a map key, later
   the colouring of a meaning layer in the canvas.

   The names are those of the Rust constants in lower case; the tool expects
   them so. */
:root {
    /* Water and edges */
    --map-water-shallow: #607a8e;
    --map-water-deep: #263648;
    --map-border: #767c86;
    --map-deep-sea: #181e2a;
    --map-shallows: #cebA84;

    /* The biomes */
    --map-meadows: #60873e;
    --map-blackforest: #305428;
    --map-swamp: #6a4e34;
    --map-plains: #b29856;
    --map-mistlands: #403c46;
    --map-mist-wood: #4a3860;
    --map-ashlands: #642820;
    --map-mountain: #e2e6ea;
    --map-deepnorth: #bbdfff;

    /* What lies above */
    --map-snow: #e2e6ea;
    /* The shore ice of the Deep North ({T0074}) and its rim (Owner,
       10 September 2026). The same colour as the land area of the North:
       ice is ice, whether it carries or floats. The rim says where a floe
       ends - without it, it blurs into the land behind. */
    --map-ice: #bbdfff;
    --map-ice-rim: #3376b1;
    --map-lava: #e85e1a;
    --map-forest: #1e401e;
    --map-hot-sea: #b02c18;
    --map-shadow: #1e1812;
    --map-wave: #483422;
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: The map band — bar and window; the window takes what height the
   page leaves and shows the whole disc. */

@layer slices.slice_map {
    @scope ([data-slice="map"]) {
        :scope {
            grid-template-rows: auto auto minmax(0, 1fr);
            row-gap: var(--space-close);
            padding-block: var(--space-close) 0;
            min-block-size: 0;
        }

        /* The title in white, not in the ink colour of the other headings: it
           names the seed, and that should shine like the game name on the
           stage (Owner, 3 September 2026). */
        [data-type="heading"] {
            color: oklch(99% 0.01 90);
        }

        /* The window ends at the edge of the content band like every other
           content element (Owner, 3 September 2026). It is the map space:
           what lies in it is clipped, and the tile layer moves in it
           ({T0026S9}).

           It is at the same time a grid with exactly one cell ({T0028S1}):
           the handle for the large view lays itself in with place-self
           instead of with an offset at the corner. The layers in it stay
           absolutely positioned — they carry the script's matrix and belong
           in no cell. */
        [data-type="map-window"] {
            display: grid;
            grid-template: minmax(0, 1fr) / minmax(0, 1fr);
            position: relative;
            min-block-size: 0;
            overflow: hidden;
            background: var(--ground);
            cursor: grab;
            touch-action: none;
            overscroll-behavior: contain;
        }

        /* The window is focusable (arrows, plus, minus): the focus ring inside,
           so that overflow hidden does not cut it off. */
        [data-type="map-window"]:focus-visible {
            outline: 2px solid var(--link);
            outline-offset: -2px;
        }

        /* The tile layer: a point without a size of its own that the script
           moves by transform; the tiles hang on it with fixed pixel
           measures. The markers lie in the same layer and so in the same
           coordinate space — the same matrix moves map and place, so a
           place stays on its point, at every zoom (K5). */
        [data-type="map-tiles"],
        [data-type="map-markers"] {
            position: absolute;
            inset-block-start: 0;
            inset-inline-start: 0;
            transform-origin: 0 0;
            will-change: transform;
        }

        /* The marker layer computes in screen points, not in map space: so a
           marker keeps its size while the map zooms, and still sits on its
           point. On panning the script moves the layer; on zooming it sets
           scale and origin here ({T0145}), and every marker computes its
           position from that - three numbers instead of a thousand
           transforms per zoom step. */
        [data-type="map-markers"] {
            inset: 0;
            transform: none;
            z-index: 2;
            pointer-events: none;
            --scale: 0;
            --origin-x: 0;
            --origin-y: 0;
        }

        [data-type="map-marker"] {
            position: absolute;
            /* **The size stands as a number, not as a length** ({T0036S12}): a
               size rule sets only --symbol, and width, height and the
               centring margins follow from it.

               **And the growth is a second number** ({T0045S1}): under the
               mouse --growth stands at 4, the box grows by four points, the
               margins by two per side, and the centre stays on the point.
               Not through scale: the property acts before transform, but
               the marker sits on its place by transform: translate, and so
               the offset was scaled along - the symbol moved instead of
               growing. The owner saw it on the first try. */
            --symbol: 28;
            --growth: 0;
            /* **And the field growth is the third number** ({T0144}, Owner on
               21 September 2026: »8px groesser als bei einer einzelnode«):
               the marker of a resource field stands at --field 8 and is so
               eight points larger than the single node of its kind, whatever
               size that has - no number per kind, one rule. */
            --field: 0;
            inline-size: calc((var(--symbol) + var(--growth) + var(--field)) * 1px);
            block-size: calc((var(--symbol) + var(--growth) + var(--field)) * 1px);
            margin-block-start: calc((var(--symbol) + var(--growth) + var(--field)) * -0.5px);
            margin-inline-start: calc((var(--symbol) + var(--growth) + var(--field)) * -0.5px);
            /* **The position is computed by the stylesheet** ({T0145}): --point-x
               and --point-y are the place as a fraction of the world, set
               once per marker; scale and origin come from the layer. It is
               the same formula as toScreen in the script, origin + place *
               scale, only that a zoom step now changes three numbers on the
               layer and not a thousand transforms on the markers. */
            --point-x: 0;
            --point-y: 0;
            transform: translate(
                calc((var(--origin-x) + var(--point-x) * var(--scale)) * 1px),
                calc((var(--origin-y) + var(--point-y) * var(--scale)) * 1px)
            );
            /* The order of the layers ({T0045S1}, Owner on 6 September 2026):
               the spawn at the very top, below it the bosses, below them the
               traders, below them everything else - and a marker under the
               mouse above all. The values stand at the layers; here the floor. */
            z-index: 1;
            pointer-events: auto;
            /* Every marker accepts the mouse, boss and spawn too ({T0045S1}) -
               before, every layer set that for itself, and these two were
               missing. The container above stands on none, so that the map
               stays draggable between the markers. */
            background-size: contain;
            background-repeat: no-repeat;
            filter: drop-shadow(0 1px 2px oklch(12% 0.01 260 / 0.7));
        }

        /* The label above the symbol ({T0026S38}): white, with a dark rim, so
           that it stands on light as on dark ground. It hangs on the marker
           and therefore keeps its screen size like it - fourteen points,
           however far the map is zoomed.

           The size stands in px and not in a step of the scale: the owner
           ordered it so, and a map label follows the map, not the running
           text of the page. */
        /* The WebGL marker layer ({T2001S2}): the icons are drawn by the card
           from one atlas, and a marker node exists only where a sign is to be
           read - the bosses, the summits, a field under the pointer, the
           marker under the pointer. The node keeps its place and its box, so
           the sign sits where it sat; only its picture is not drawn twice.
           Three attributes beat the two of every kind rule, so no rule below
           has to know. */
        [data-type="map-markers"][data-mode="gl"] [data-type="map-marker"] {
            background-image: none;
        }
        /* The card's canvas lies under the marker layer and over the tiles:
           the same box and the same stacking as the markers, earlier in the
           document, so the signs stand on the icons. The script sizes it in
           device pixels; the stylesheet gives it the window. */
        [data-type="map-canvas"] {
            position: absolute;
            inset: 0;
            inline-size: 100%;
            block-size: 100%;
            z-index: 2;
            pointer-events: none;
        }
        /* The grip ({T2001S2}): the one element whose cursor says »grabbing«
           while a drag runs. `cursor` inherits, so a value on the window is
           recomputed for every element beneath it - 6 072 field paths among
           them, 57 ms at the press and 51 ms at the release, the two longest
           frames of a pan. The grip lies over the map and is a pointer target
           only while the script says so; the events reach the window through
           the pointer capture, and the sheets and tools stay above it. */
        [data-type="map-grip"] {
            position: absolute;
            inset: 0;
            z-index: 3;
            pointer-events: none;
            cursor: grabbing;
        }
        [data-type="map-grip"][data-on] {
            pointer-events: auto;
        }
        [data-type="map-markers"][data-mode="gl"] [data-type="map-marker"]::before {
            display: none;
        }
        [data-type="map-marker-name"] {
            position: absolute;
            inset-block-end: 100%;
            inset-inline-start: 50%;
            transform: translateX(-50%);
            margin-block-end: 0.2em;
            font-size: 14px;
            font-weight: 700;
            line-height: 1.1;
            white-space: nowrap;
            color: oklch(99% 0 0);
            text-shadow:
                0 1px 2px oklch(12% 0.01 260 / 0.9),
                0 0 3px oklch(12% 0.01 260 / 0.8);
        }

        /* The bosses carry their own symbol and a red label - so one tells
           them from the spawn in passing. */
        /* The bosses stand a third larger than the spawn: they are the points
           one looks at first, and their symbol carries more inner drawing
           ({T0027S3}). The margins go along, so that the symbol stays on
           its point and not beside it. */
        [data-type="map-marker"][data-layer="bosses"] {
            background-image: url("/icons/map-boss.webp");
            --symbol: 37;
        }

        [data-type="map-marker"][data-layer="bosses"] [data-type="map-marker-name"] {
            color: oklch(72% 0.19 25);
        }

        /* The bosses lie above the traders and below the spawn ({T0045S1},
           Owner on 6 September 2026). That replaces the order of
           4 September, by which the boss layer always lay on top and even a
           touched marker stayed below it: now the mouse lifts every marker
           above everything, so that one sees what one touches.

           Without the rule the order in the document would decide, and that
           comes from the places file: the bosses stand in front there,
           because the game places them first - and would so lie under every
           grave that comes later. */
        [data-type="map-marker"][data-layer="bosses"] {
            z-index: 3;
        }


        /* The dungeons stand by the hundreds on the map. Their symbol is
           therefore smaller than the bosses', and their name appears only
           when the mouse stands on it - standing labels would be a wall of
           text with a thousand points (Owner, 4 September 2026)
           ({T0027S6}). */
        [data-type="map-marker"][data-layer="dungeons"],
        [data-type="map-marker"][data-layer="caves"] {
            background-image: url("/icons/map-dungeon.webp");
            --symbol: 22;
        }

        /* The winding tunnels in the measure of the other dungeons - one goes
           in, so the symbol carries as much weight as a crypt. */
        /* The summits ({T0112S3}, Owner on 15 September 2026, after the model
           of a hiking map): a small black triangle on the peak, below it the
           height in metres, no name. The triangle is a form, no picture - a
           black box with clip-path on the ::before, so that the label beside
           it is not cut off too. Small, because there are dozens per world
           and the number leads the word. */
        [data-type="map-marker"][data-kind="summit"] {
            --symbol: 12;
            background-image: none;
        }

        [data-type="map-marker"][data-kind="summit"]::before {
            content: "";
            position: absolute;
            inset: 0;
            background-color: oklch(12% 0.01 260);
            clip-path: polygon(50% 0, 100% 100%, 0 100%);
        }

        /* The height stands permanently under the triangle ({T0121}, Owner on
           15 September 2026: »das label immer angezeigt - also ohne hover«).
           T0112 S5 had put it under the mouse; taken back. */
        [data-type="map-marker"][data-kind="summit"] [data-type="map-marker-name"] {
            inset-block-end: auto;
            inset-block-start: 100%;
            margin-block-end: 0;
            margin-block-start: 0.15em;
            font-size: 11px;
            font-weight: 600;
        }

        [data-type="map-marker"][data-kind="tunnel"] {
            background-image: url("/icons/map-tunnel.webp");
            /* 36: first 30, then 40, then four points back (Owner, 9 September
               2026). Plus a new symbol, the glowing maw instead of the stone
               gate. 24 since {T2001S13}, a third smaller, then 18 on the owner's
               look the same day (28 September 2026). */
            --symbol: 18;
        }

        /* Two kinds of entrance, and the map says since {T0036S3} which: the
           green-barred crypt of the swamp (`crypt`, in the game
           SunkenCrypt) and the burial chamber of the Black Forest (`grave`,
           the three Crypts). Before, both carried the symbol of their layer
           and looked the same - the difference between a passage into the
           swamp and one into the forest stood nowhere. The mines of the
           Mistlands stay with the layer symbol. */
        [data-type="map-marker"][data-kind="crypt"] {
            background-image: url("/icons/map-crypt.webp");
        }

        [data-type="map-marker"][data-kind="grave"] {
            background-image: url("/icons/map-grave.webp");
        }

        /* The third kind of the layer and the last without a symbol of its
           own: the dwarf mines of the Mistlands ({T0036S5}). So the layer
           says what it shows - green gate in the swamp, grey dolmen in the
           forest, violet gate in the Mistlands. */
        [data-type="map-marker"][data-kind="mine"] {
            background-image: url("/icons/map-mine.webp");
        }

        /* `display: none` and not `opacity: 0` ({T0027S10}): a transparent
           label is still laid out and measured, and with four thousand
           markers this text layout is more expensive than anything else on
           the layer. The price is the transition - a label that appears
           instead of one that fades in. */
        [data-type="map-marker"][data-layer="dungeons"] [data-type="map-marker-name"],
        [data-type="map-marker"][data-layer="caves"] [data-type="map-marker-name"] {
            display: none;
        }

        [data-type="map-marker"][data-layer="dungeons"]:hover [data-type="map-marker-name"],
        [data-type="map-marker"][data-layer="caves"]:hover [data-type="map-marker-name"] {
            display: block;
        }


        /* The caves carry their own symbol ({T0027S7}); everything else -
           size, hover, hiding - they share with the dungeons. */
        [data-type="map-marker"][data-layer="caves"] {
            background-image: url("/icons/map-cavern.webp");
        }

        /* **After** the layer rule and not before ({T0036S6}): both selectors
           weigh the same - one type and one attribute -, and at equal
           weight the later wins. The first version stood earlier in the
           sheet, and the troll caves kept carrying the symbol of the layer.
           Seen on the living object, not read in the sheet ({K0145}).

           Two kinds, one symbol: a cave in the rock looks like a cave in
           the rock, whether a troll lives in it or a morning. The ice cave
           of the mountains keeps the layer symbol - an entrance in the snow
           is something other than one in the rock. */
        [data-type="map-marker"][data-kind="troll-cave"],
        [data-type="map-marker"][data-kind="morning-cave"] {
            background-image: url("/icons/map-cave.webp");
        }


        /* The traders are three persons and not one sort: Haldor, Hildir and
           the swamp witch therefore each carry their own symbol - the first
           layer in which the picture does not hang on the layer but on the
           kind ({T0027S9}). Their name appears on pointing, as with dungeons
           and caves: thirty points are too many for standing labels and too
           few to leave them mute.

           They stand at thirty-two points (Owner, 5 September 2026, before
           nineteen): with ten Haldors, ten Hildirs and a handful of witches
           per world they are the rarest marks of the map and cover nobody -
           at nineteen they drowned between the towers. */
        [data-type="map-marker"][data-layer="traders"] {
            --symbol: 32;
            z-index: 2;
        }

        /* Hildir's quest places ({T0044S1}): cave, crypt and fortress, three
           each. They stand with the traders because they belong to Hildir. */
        [data-type="map-marker"][data-kind="quest"] {
            background-image: url("/icons/map-quest.webp");
        }

        /* Dyrnwyn's quest places ({T0046S1}): the three places of the mystery
           where blade, hilt and tip of the sword lie. An errand like
           Hildir's, hence in this layer. */
        [data-type="map-marker"][data-kind="dyrnwyn"] {
            background-image: url("/icons/map-dyrnwyn.webp");
        }

        [data-type="map-marker"][data-kind="haldor"] {
            background-image: url("/icons/map-haldor.webp");
        }

        [data-type="map-marker"][data-kind="hildir"] {
            background-image: url("/icons/map-hildir.webp");
        }

        [data-type="map-marker"][data-kind="witch"] {
            background-image: url("/icons/map-witch.webp");
        }

        [data-type="map-marker"][data-layer="traders"] [data-type="map-marker-name"] {
            display: none;
        }

        [data-type="map-marker"][data-layer="traders"]:hover [data-type="map-marker-name"] {
            display: block;
        }


        /* The filter bar carries two rows (Owner, 4 September 2026: »schiebe
           die Optionen, die wir haben, in die obere Reihe - es passen ja
           zwei Reihen untereinander - und mache als erstes unten rechts den
           Switch fuer Rohstoffe«). Five columns, as many as the first row
           has switches; the sixth gets the last column of the second row.
           That stands here and not in the strip itself: that is a widget
           and belongs to the item page too ({K0095}). */
        /* The map options ({T2001S6}, section 11 of the map programme): the
           heading in the pixel type, the seed form, and the categories in an
           area that scrolls on its own under a thin golden scrollbar - as iOS
           draws its own, in our gold (owner, 28 September 2026). The
           browser's bar took room and looked like nothing of ours. */
        [data-type="map-options"] > [data-type="heading"] {
            /* The large step and not the title's: the drawer is 24 rem wide
               and the title step put »Kartenoptionen« past its edge; at the
               large step the longest word of every language of the site
               stands inside it at every scale, and a title of several words
               wraps at a space as the seed heading does. */
            font-size: var(--text-large);
            padding-block: var(--space-tight) var(--space-snug);
            text-shadow: 1px 1px 0 oklch(0% 0 0 / 0.9), -1px 1px 0 oklch(0% 0 0 / 0.9);
        }
        [data-type="map-options"] [data-part="categories"] {
            min-block-size: 0;
            overflow-block: auto;
            scrollbar-width: thin;
            scrollbar-color: var(--gold) transparent;
            padding-inline-end: var(--space-tight);
        }
        /* What the orbital view holds back, it also says ({T0052}): the line
           at the top of the drawer ({T2001S9}, owner, 28 September 2026). It
           stands always - it names a rule, and a line that came and went
           would move every switch below it at the threshold -; in the quiet
           colour of the captions on the descent, in gold while the map is in
           orbit, where it concerns the reader. No switch is disabled any
           more: a switch set in orbit shows on the descent. */
        [data-type="map-options"] [data-part="options-note"] {
            margin: 0 0 var(--space-tight);
            font-size: var(--text-micro);
            text-align: start;
            color: var(--ink-quiet);
        }
        :scope[data-orbit] [data-type="map-options"] [data-part="options-note"] {
            color: var(--gold);
        }
        /* The treasures ({T0027S14}): every place that carries a chest in the
           game - surveyed from the location prefabs. The symbol stands on
           the place, not on the chest: where exactly it stands in it hangs
           on the rotation with which the game sets the location. */

        [data-type="map-marker"][data-layer="treasures"] {
            --symbol: 15;
        }

        /* The frozen ships of the North ({T0074}). Larger than the ice pond:
           a ship in the ice is a mark one steers to, a pond one that one
           passes by. */
        [data-type="map-marker"][data-kind="frost-ship"] {
            background-image: url("/icons/map-frost-ship.webp");
            --symbol: 34;
        }

        /* The shipwrecks ({T0044S1}): a hundred per world on coasts and in
           shallows. They stood as a chest like everything in this layer. */
        [data-type="map-marker"][data-kind="wreck"] {
            background-image: url("/icons/map-wreck.webp");
        }

        /* The wells with treasure ({T0044S1}): at the bottom a box waits -
           fifty per world, in the mountains and in the swamp. */
        [data-type="map-marker"][data-kind="well"] {
            background-image: url("/icons/map-well.webp");
        }

        /* The runestones ({T0050}): 695 per world in all biomes, reported
           missing by the beta testers. Layer size, because frequent. */
        [data-type="map-marker"][data-kind="runestone"] {
            background-image: url("/icons/map-runestone.webp");
        }

        /* The stone clearing ({T2001S8}): the clearing in the Black Forest
           where the mystical stone lies, about ten per world. Rare and a
           place one goes to, so larger than the layer's fifteen: 22 first,
           the crypts' size, then four points smaller on the owner's look
           (28 September 2026: »4px kleiner und mit leichtem hellblauem
           Glow«). The glow needed room in the file, so the stone fills 0.76
           of it now instead of 0.92, and the box is 22 so that the stone
           itself stays at those 18 points. */
        [data-type="map-marker"][data-kind="stone-clearing"] {
            background-image: url("/icons/map-stone-clearing.webp");
            --symbol: 22;
        }

        [data-type="map-marker"][data-kind="chest"] {
            background-image: url("/icons/map-chest.webp");
        }

        /* The axe-head chest ({T0127}): the same chest with a purple glow, at
           the two meadow houses whose chest can roll a head of the early
           axe. */
        [data-type="map-marker"][data-kind="axe-chest"] {
            background-image: url("/icons/map-axe-chest.webp");
            /* At maypole size ({T0128}): the layer draws at 15, and at 15 px the
               glow is a rim of two pixels. At 26 it carries, measured in a
               visual comparison 15 / 26 / 30 on meadow ground. */
            --symbol: 26;
        }

        [data-type="map-marker"][data-layer="treasures"] [data-type="map-marker-name"] {
            display: none;
        }

        [data-type="map-marker"][data-layer="treasures"]:hover [data-type="map-marker-name"] {
            display: block;
        }

        /* The spawns ({T0027S13}): where the game resupplies enemies. So far
           the dragon nest of the mountains; the owner keeps filling the
           layer. Size and behaviour like the resources. */

        [data-type="map-marker"][data-layer="spawns"] {
            --symbol: 15;
        }

        /* The ice pond - a landmark without loot, hence smaller than
           everything one enters. */
        [data-type="map-marker"][data-kind="ice-pond"] {
            background-image: url("/icons/map-ice-pond.webp");
            /* 28: from 24 to 32 and back again to 28 (Owner, 9 September 2026). */
            --symbol: 28;
        }

        /* The Monument of Torment of the Ashlands ({T0036S14}): the stone the
           charred skeletons come from - 350 per world and so the most
           frequent kind of this layer. */
        [data-type="map-marker"][data-kind="monument"] {
            background-image: url("/icons/map-monument.webp");
        }

        /* The nests of the greylings ({T0036S13}): the second kind in the
           layer of the spawners, and the only one of the Black Forest. */
        [data-type="map-marker"][data-kind="greydwarf"] {
            background-image: url("/icons/map-greydwarf.webp");
        }

        [data-type="map-marker"][data-kind="nest"] {
            background-image: url("/icons/map-nest.webp");
        }

        /* The vulture nests of the Ashlands ({T0040S1}): 350 per world, as
           frequent as the monument next door, and the fourth kind of this
           layer. It lies flat on the ground, so hangs rightly with its
           centre on the point. */
        [data-type="map-marker"][data-kind="vulture-nest"] {
            background-image: url("/icons/map-vulture-nest.webp");
        }

        /* The draugr graves of the swamp ({T0042S1}): the same 200 places
           that stand as a chest in the layer of the treasures — here as
           what comes out of them. A place with two keys stands in two layers. */
        [data-type="map-marker"][data-kind="draugr"] {
            background-image: url("/icons/map-draugr.webp");
            --symbol: 16;
        }

        [data-type="map-marker"][data-layer="spawns"] [data-type="map-marker-name"] {
            display: none;
        }

        [data-type="map-marker"][data-layer="spawns"]:hover [data-type="map-marker-name"] {
            display: block;
        }


        /* The resources stand as large as the settlements - a third smaller
           than the dungeons (Owner, 4 September 2026: »die Icons der
           Ressourcen alle ca 1/3 kleiner«). There are many, and they should
           not cover the map. */
        [data-type="map-marker"][data-layer="resources"] {
            --symbol: 15;
        }

        /* The petrified frost troll: a resource place in the measure of the tar pit. */
        [data-type="map-marker"][data-kind="frost-troll"] {
            background-image: url("/icons/map-frost-troll.webp");
            /* 36: from 27 via 36, 48 and 40 to here (Owner, 9 and 10 September
               2026). The petrified troll is a resource place one steers to. */
            --symbol: 36;
        }

        [data-type="map-marker"][data-kind="tar"] {
            background-image: url("/icons/map-tar.webp");
        }

        [data-type="map-marker"][data-kind="flametal"] {
            background-image: url("/icons/map-flametal.webp");
        }

        /* The Mistlands yield two resources one sees from afar: the giant
           skeletons the black marble comes from, and the giant swords
           ({T0027S11} QA). */
        [data-type="map-marker"][data-kind="skeleton"] {
            background-image: url("/icons/map-skeleton.webp");
        }

        [data-type="map-marker"][data-kind="sword"] {
            background-image: url("/icons/map-sword.webp");
        }

        /* The surtlings sit at the fire holes of the swamp, the bees in eleven
           of the thirteen wooden houses - both looked up in the prefab
           ({T0027S11} QA). The houses so stand in two layers. */
        [data-type="map-marker"][data-kind="surtling"] {
            background-image: url("/icons/map-surtling.webp");
        }

        [data-type="map-marker"][data-kind="bees"] {
            background-image: url("/icons/map-bee.webp");
        }

        /* The vegetation ({T0027S12}): ores, shoots, armours. They stand only
           from the middle level on and only in the window - eighty thousand
           points are no marker carpet but a view. Silver since {T0144};
           obsidian is computed but still waits for its symbol. */
        /* The vegetation symbols carry no drop shadow: they stand by the
           thousands in the picture, and every shadow is a rasterisation of
           its own. On the finest level that cost, measured, a third of the
           time per picture ({T0027S12}). */
        [data-type="map-marker"][data-kind="copper"],
        [data-type="map-marker"][data-kind="tin"],
        [data-type="map-marker"][data-kind="silver"],
        [data-type="map-marker"][data-kind="scrap-iron"],
        [data-type="map-marker"][data-kind="armour"] {
            filter: none;
        }

        /* The two ores of the Black Forest stand a third smaller than the
           other resources - 10 instead of 15 points (Owner, 4 September
           2026). They are the densest of the layer: tin alone ten thousand
           times. */
        [data-type="map-marker"][data-kind="copper"],
        [data-type="map-marker"][data-kind="tin"],
        [data-type="map-marker"][data-kind="scrap-iron"],
        [data-type="map-marker"][data-kind="silver"],
        [data-type="map-marker"][data-kind="sulfur"] {
            /* The mushroom at the metals' size too ({T2004}, the owner on 29
               September 2026: »die icons sollten die grösse haben wie metalle
               auch«), without a cluster's growth. */
            --symbol: 10;
        }
        /* The fruit four points larger, the owner's second look the same evening:
           »die fruchticons doch 4px grösser« - the berries and the grapes -, and
           four more on 30 September 2026, »bitte die fürchte icons um ebenfalls 4px
           vergrössern - man erkennt sie ja kaum«. */
        [data-type="map-marker"][data-kind="blueberry"],
        [data-type="map-marker"][data-kind="raspberry"],
        [data-type="map-marker"][data-kind="cloudberry"],
        [data-type="map-marker"][data-kind="wine"] {
            --symbol: 18;
        }
        /* The ancient armour and swords six points above their layer's 15, the
           owner on 30 September 2026: »das bild bitte um 6px vergrössern das ist
           ja winzig«, and six more the same day: »das symbol für die antiken
           teile noch mal um 6px grösser«, then two back: »die antiken sachen
           2px kleiner«. */
        [data-type="map-marker"][data-kind="armour"],
        [data-type="map-marker"][data-kind="sword"] {
            --symbol: 25;
        }

        /* The guck ({T0047S3}): at the infested tree of the swamp that
           carries it in its prefab - 700 per world, a place, no folding. It
           stands in the size of its layer. */
        [data-type="map-marker"][data-kind="guck"] {
            background-image: url("/icons/map-guck.webp");
        }

        [data-type="map-marker"][data-kind="copper"] {
            background-image: url("/icons/map-copper.webp");
        }

        [data-type="map-marker"][data-kind="tin"] {
            background-image: url("/icons/map-tin.webp");
        }
        /* Silver ({T0144}): one vein per mountain zone, hundreds per world,
           never folded. It stood in the size of its layer, 15, until the owner
           set it with the other ores on 29 September 2026 ({T2004}): »in dem
           ticket bitte das silber icon so gross wie auch kupfer ud zink sind
           skalieren« - 10, with the rule above. */
        [data-type="map-marker"][data-kind="silver"] {
            background-image: url("/icons/map-silver.webp");
        }
        /* Scrap iron ({T2004}): the muddy scrap piles of the swamp, every node,
           at the copper nodes' size - the owner's »gleiche grösse wie kupfer
           nodes und natürlich auch die bereichsfläche«. */
        [data-type="map-marker"][data-kind="scrap-iron"] {
            background-image: url("/icons/map-scrap-iron.webp");
        }
        /* The berries and the mushroom ({T2004}) are areas only and have no
           node; their area shows the icon once, and the sign names them. */
        [data-type="map-marker"][data-kind="blueberry"] {
            background-image: url("/icons/map-blueberry.webp");
        }
        [data-type="map-marker"][data-kind="raspberry"] {
            background-image: url("/icons/map-raspberry.webp");
        }
        [data-type="map-marker"][data-kind="cloudberry"] {
            background-image: url("/icons/map-cloudberry.webp");
        }
        [data-type="map-marker"][data-kind="mushroom"] {
            background-image: url("/icons/map-mushroom.webp");
            /* Four points over the metals' 10, the owner on 30 September 2026: »die
               pilze sind noch genauso klein«. */
            --symbol: 14;
        }
        /* The grapes at the large charred ruins ({T2004}), the owner's draft of
           29 September 2026 with »gleiches bis bei beeren würde ich sagen«. */
        [data-type="map-marker"][data-kind="wine"] {
            background-image: url("/icons/map-wine.webp");
        }
        /* The sulfur arches ({T2006}): the owner's icon at the metals' size, »icon
           gleiche grösse wie bei metallen«. The cliffs: the owner's picture, »das icon
           für die cliffs«, at the same size. */
        [data-type="map-marker"][data-kind="sulfur"] {
            background-image: url("/icons/map-sulfur.webp");
        }
        [data-type="map-marker"][data-kind="cliff"] {
            background-image: url("/icons/map-cliff.webp");
            /* Four points over the metals' 10, the owner on 30 September 2026:
               »können wir die icons für den felsen noch um 4px vergössern«. */
            --symbol: 14;
        }
        /* The fern of the charred tower ruins ({T2004}), the owner's draft of 29
           September 2026, at twice the metals' 10 - »farn icon doppelt so gross
           und auch leicht weiss glow«. */
        [data-type="map-marker"][data-kind="fern"] {
            background-image: url("/icons/map-fern.webp");
            --symbol: 20;
        }
        /* The boar and the deer on their hunting ground ({T2004}): the owner's
           drafts of 29 September 2026, once per area as the berries, at twice
           their size - »die tiericons doppelt so gross« -, the deer four points
           more, »das hirschicon 4px grösser«. The neck has none yet and its area
           stays a sign. */
        [data-type="map-marker"][data-kind="deer"] {
            background-image: url("/icons/map-deer.webp");
            --symbol: 24;
        }
        [data-type="map-marker"][data-kind="boar"] {
            background-image: url("/icons/map-boar.webp");
            --symbol: 20;
        }
        /* The neck wears the game's own trophy icon from our data ({T2007}, the owner
           on 30 September 2026: »für neck vorkommen dsa icon nehmen aus der datenbank
           für den neck trophy«), at the boar's 20. */
        [data-type="map-marker"][data-kind="neck"] {
            background-image: url("/icons/map-neck.webp");
            --symbol: 20;
        }
        /* The resource fields ({T0144}, Owner on 21 September 2026: »wie aus
           atlanten, wo rohstoffe im boden als flaechiger bereich dargestellt
           werden«): an SVG layer above the tiles, viewBox in world metres,
           moves along with the tile layer. It shows itself with the
           resources switch; without it, it is gone like the markers of the
           layer. The colour is the resource's, not one of the three role
           colours of the site - the sheet has its own palette ({K0234}).
           The stroke width stays the same on zooming (vector-effect), the
           area is translucent so that the terrain beneath stays readable. */
        [data-type="map-fields"] {
            position: absolute;
            inset: 0;
            inline-size: 100%;
            block-size: 100%;
            /* Above all tile levels: the script gives every level its number as
               z-index (0, 1, 2); with 1 the layer lay under the tiles of
               levels 1 and 2 - seen on 21 September 2026, 605 paths in the
               DOM and nothing in the picture. */
            z-index: 10;
            pointer-events: none;
            overflow: visible;
        }
        [data-type="map-fields"][hidden],
        [data-type="map-fields"] path[hidden] {
            display: none;
        }
        /* The area accepts the pointer - the layer itself does not, so that
           the map stays draggable between the fields -, and under the
           pointer it stands out; its text (»Zinnerz · 37«) then stands in
           the readout under the pointer. A marker in the field centre is
           gone (Owner, 21 September 2026: »bei zinn keine zinn ore mehr
           abbilden, dafuer wenn man ueber den bereich hovert den text«). */
        [data-type="map-fields"] path {
            --field: oklch(70% 0.05 250);
            /* A field is a collection of rings - islands and holes -, evenodd
               draws a hole as a hole. */
            fill-rule: evenodd;
            fill: color-mix(in oklch, var(--field) 28%, transparent);
            stroke: var(--field);
            stroke-width: 1.5;
            stroke-dasharray: 5 3;
            vector-effect: non-scaling-stroke;
            pointer-events: visiblePainted;
        }
        /* The standing out hangs on data-shown, which the script sets on
           exactly one path - not on :hover ({T0145}): with path:hover every
           pointer movement was a style question over all 5 755 paths. */
        [data-type="map-fields"] path[data-shown] {
            fill: color-mix(in oklch, var(--field) 55%, transparent);
            stroke-dasharray: none;
        }
        /* On level 1 only the clusters ({T0145}): a single zone of 24 m is two
           pixels there, and four fifths of the paths are single zones - on
           zooming the browser otherwise rasters them for nothing. */
        [data-type="map-fields"][data-level="1"] path[data-single] {
            display: none;
        }
        /* The marker of a field ({T0144}, Owner on 21 September 2026: »in
           clusterbereichen doch die ore wieder anzeigen, aber 8px groesser
           als bei einer einzelnode« - and the same for everything the
           resource layer shows and hides): the symbol of the kind, eight
           points larger than its single node through --field in the base
           rule (tin 10 → 18, guck 15 → 23, leviathan 22 → 30), the label
           with the number on pointing at the marker or at the area (the
           script then sets data-shown). */
        /* The map first, then the icons ({T2001S13}, owner, 28 September 2026):
           while the picture of the view does not stand yet, the markers, their
           canvas and the field outlines stay invisible, and come in together
           with a short fade the moment it stands. The script takes the
           attribute once and never sets it again. */
        [data-type="map-window"][data-markers="waiting"] :is([data-type="map-markers"], [data-type="map-canvas"], [data-type="map-fields"]) {
            opacity: 0;
        }
        :is([data-type="map-markers"], [data-type="map-canvas"], [data-type="map-fields"]) {
            transition: opacity 300ms ease-out;
        }
        @media (prefers-reduced-motion: reduce) {
            :is([data-type="map-markers"], [data-type="map-canvas"], [data-type="map-fields"]) { transition: none; }
        }
        [data-type="map-marker"][data-field] {
            --field: 8;
        }
        /* Since {T2001S11} the cluster's nodes stand as nodes (owner, 28
           September 2026: »punktgenau«); its marker keeps only the sign with
           the count, and draws no symbol of its own - unless its kind is an
           area only ({T2004}): then the area shows the kind's icon once. */
        [data-type="map-marker"][data-field]:not([data-area]) {
            background-image: none;
        }
        [data-type="map-marker"][data-field][data-area] {
            --field: 0;
        }
        [data-type="map-marker"][data-field][data-shown] [data-type="map-marker-name"] {
            display: block;
        }
        /* A node inside a cluster takes no pointer (owner, 28 September 2026:
           its own sign over the cluster's count read wrongly): the outline
           beneath answers instead, with the count. */
        [data-type="map-marker"][data-quiet] {
            pointer-events: none;
        }
        /* The colour of a field is the dominant colour of its symbol (Owner,
           21 September 2026) - computed by vgarun valheim create constants from the
           archive picture of the kind, anew after every swap of a symbol,
           like the glow. What stands here is generated; the default above
           applies to a kind without a symbol. */
        /* FIELD COLOURS BEGIN - generated by vgarun valheim map create field-colours, not by hand */
        [data-type="map-fields"] path[data-kind="surtling"] {
            --field: oklch(67% 0.266 317);
        }
        [data-type="map-fields"] path[data-kind="cliff"] {
            --field: oklch(21% 0.008 276);
        }
        [data-type="map-fields"] path[data-kind="tin"] {
            --field: oklch(83% 0.022 264);
        }
        [data-type="map-fields"] path[data-kind="copper"] {
            --field: oklch(49% 0.081 46);
        }
        [data-type="map-fields"] path[data-kind="scrap-iron"] {
            --field: oklch(36% 0.005 106);
        }
        [data-type="map-fields"] path[data-kind="silver"] {
            --field: oklch(69% 0.115 248);
        }
        [data-type="map-fields"] path[data-kind="flametal"] {
            --field: oklch(42% 0.086 25);
        }
        [data-type="map-fields"] path[data-kind="leviathan"] {
            --field: oklch(42% 0.007 49);
        }
        [data-type="map-fields"] path[data-kind="guck"] {
            --field: oklch(49% 0.160 142);
        }
        [data-type="map-fields"] path[data-kind="tar"] {
            --field: oklch(37% 0.005 301);
        }
        [data-type="map-fields"] path[data-kind="sulfur"] {
            --field: oklch(56% 0.109 86);
        }
        [data-type="map-fields"] path[data-kind="armour"] {
            --field: oklch(50% 0.077 66);
        }
        [data-type="map-fields"] path[data-kind="skeleton"] {
            --field: oklch(22% 0.004 264);
        }
        [data-type="map-fields"] path[data-kind="frost-troll"] {
            --field: oklch(40% 0.070 254);
        }
        [data-type="map-fields"] path[data-kind="sword"] {
            --field: oklch(50% 0.077 66);
        }
        [data-type="map-fields"] path[data-kind="blueberry"] {
            --field: oklch(37% 0.058 266);
        }
        [data-type="map-fields"] path[data-kind="raspberry"] {
            --field: oklch(43% 0.144 25);
        }
        [data-type="map-fields"] path[data-kind="cloudberry"] {
            --field: oklch(73% 0.144 77);
        }
        [data-type="map-fields"] path[data-kind="mushroom"] {
            --field: oklch(46% 0.175 28);
        }
        [data-type="map-fields"] path[data-kind="wine"] {
            --field: oklch(72% 0.121 116);
        }
        [data-type="map-fields"] path[data-kind="fern"] {
            --field: oklch(48% 0.080 111);
        }
        [data-type="map-fields"] path[data-kind="deer"] {
            --field: oklch(43% 0.071 57);
        }
        [data-type="map-fields"] path[data-kind="boar"] {
            --field: oklch(50% 0.047 63);
        }
        [data-type="map-fields"] path[data-kind="neck"] {
            --field: oklch(46% 0.059 127);
        }
        [data-type="map-fields"] path[data-kind="bees"] {
            --field: oklch(65% 0.130 84);
        }
        /* FIELD COLOURS END */
        /* The hunting ground ({T2004}): the owner gave its three colours on 29
           September 2026: »rehe - ein angenehme orangbraun«, »schweine eine
           graubraun«, »nixen ein fichtengrün« - and on seeing the boar's took it
           to »hintergrund wildschein ist ein helles grau«, asked and answered
           »Soll hellgrau werden«; the neck's lighter, »nixe bitte heleres grün
           für das cluster grün«. They stand over the colour the tool reads from
           the icon. */
        [data-type="map-fields"] path[data-kind="deer"] {
            --field: oklch(64% 0.13 55);
        }
        [data-type="map-fields"] path[data-kind="boar"] {
            --field: oklch(86% 0.005 250);
        }
        [data-type="map-fields"] path[data-kind="neck"] {
            --field: oklch(66% 0.10 160);
        }
        /* The cloudberries, the blueberries and the raspberries take the owner's
           colour over their icon's (29 September 2026: »moltebbeeren bitte
           hellgelbe bereichsfarbe«; »blaubeeren brauchen ein helleres blau, man
           sieht keinen unterscied zum unterliegenden dunkelwald«; »hintergrund von
           himbeerclustern in rosa«). */
        [data-type="map-fields"] path[data-kind="cloudberry"] {
            --field: oklch(90% 0.13 98);
        }
        [data-type="map-fields"] path[data-kind="blueberry"] {
            --field: oklch(72% 0.14 255);
        }
        [data-type="map-fields"] path[data-kind="raspberry"] {
            --field: oklch(78% 0.12 350);
        }
        /* The grapes and the ferns of the Ashlands, 29 September 2026: »die
           weintrauben hintergrund der cluster in lindgrün«, »die clusterfarbe für
           farne ist helles lila«. */
        [data-type="map-fields"] path[data-kind="wine"] {
            --field: oklch(86% 0.17 125);
        }
        [data-type="map-fields"] path[data-kind="fern"] {
            --field: oklch(80% 0.10 305);
        }
        /* The scrap iron's areas, the owner's »scrap iron hellgrau als
           clusterfarbe«, 29 September 2026. */
        [data-type="map-fields"] path[data-kind="scrap-iron"] {
            --field: oklch(84% 0.005 250);
        }
        /* The flametal's areas, the owner on 30 September 2026: »flametal cluster
           hintergrund bitte in hellem stahlblau«. */
        [data-type="map-fields"] path[data-kind="flametal"] {
            --field: oklch(80% 0.06 240);
        }
        /* The skeletons' areas, the owner on 30 September 2026: »dessen cluster
           background bitte ebenfalls in zartem hellgrau«. */
        [data-type="map-fields"] path[data-kind="skeleton"] {
            --field: oklch(90% 0.004 250);
        }
        /* The ancient armour and swords of the Mistlands, the owner on 30
           September 2026: »der hintergrund des clustes wird dann auch in dem
           grünspangrün des kupferrostes« - verdigris. */
        [data-type="map-fields"] path[data-kind="armour"],
        [data-type="map-fields"] path[data-kind="sword"] {
            --field: oklch(68% 0.09 185);
        }
        /* The sulfur arches ({T2006}): »die bitte schwefelgelb als region
           einzeichnen«. */
        [data-type="map-fields"] path[data-kind="sulfur"] {
            --field: oklch(88% 0.17 105);
        }

        [data-type="map-marker"][data-kind="armour"] {
            background-image: url("/icons/map-helmet.webp");
        }

        /* The leviathan ({T0094}): the back with the barnacles, rising out of
           the water. It stands larger than the other resources, because the
           scale is a rank and no taste — rare and decisive is large. Chitin
           exists only here, and it is needed before the plains.

           The rule stands **after** the layer rule, otherwise it carries
           its fifteen points ({K0157}). And unlike copper and tin it keeps
           its drop shadow: the two ores do without because they stand by the
           thousands in the picture and every shadow costs a rasterisation
           of its own — leviathans are a handful per world, and on dark water
           the shadow is what sets the grey back off from the ground at all. */
        [data-type="map-marker"][data-kind="leviathan"] {
            background-image: url("/icons/map-leviathan.webp");
            --symbol: 22;
        }

        [data-type="map-marker"][data-layer="resources"] [data-type="map-marker-name"] {
            display: none;
        }

        [data-type="map-marker"][data-layer="resources"]:hover [data-type="map-marker-name"] {
            display: block;
        }


        /* Settlements and ruins ({T0027S10}): the full layer, with over four
           thousand points the densest of the map. Their symbols therefore
           stand a third smaller than the dungeons' - 15 instead of 22 points
           (Owner, 4 September 2026: »diese sind 1/3 kleiner als die anderen
           Icons darzustellen«). Six groups, six pictures: camp, harbour,
           dvergr building, wooden hut, outpost, fortress. */
        [data-type="map-marker"][data-layer="settlements"] {
            --symbol: 15;
        }

        [data-type="map-marker"][data-kind="morkhalla"] {
            background-image: url("/icons/map-morkhalla.webp");
            /* 32 since {T2001S13}, a third smaller than 48, then 24 on the
               owner's look the same day (28 September 2026). */
            --symbol: 24;
        }

        /* The woodcutters' camp: axe in the stump. In the measure of the camp,
           with which it shares the layer. */
        [data-type="map-marker"][data-kind="wood-camp"] {
            background-image: url("/icons/map-wood-camp.webp");
            --symbol: 29;
        }

        /* The two villages of the meadows ({T0043S1}): the same village of
           three decayed houses, once quiet and once with green glowing
           windows. Up to here they stood under the symbol of the log hut. */
        /* The maypole ({T0112}): one to two per world, at a farm of the
           meadows, all year round. Rare and sought, so larger than the
           village it stands at; the picture comes from the owner. */
        [data-type="map-marker"][data-kind="maypole"] {
            background-image: url("/icons/map-maypole.webp");
            --symbol: 26;
        }

        /* The maypole stands without a layer and so always ({T0119}); its
           label however only under the mouse as with settlements and summits
           ({T0120}, Owner on 15 September 2026): two maypoles at the same
           farm otherwise carried two labels on top of each other. */
        [data-type="map-marker"][data-kind="maypole"] [data-type="map-marker-name"] {
            display: none;
        }

        [data-type="map-marker"][data-kind="maypole"]:hover [data-type="map-marker-name"] {
            display: block;
        }

        [data-type="map-marker"][data-kind="village"] {
            background-image: url("/icons/map-village.webp");
            /* 33 since {T0047S5} - before 29, 22 and 20, Owner on 6 September. */
            --symbol: 33;
        }

        [data-type="map-marker"][data-kind="infested"] {
            background-image: url("/icons/map-infested.webp");
            /* 33 since {T0047S5} - before 29, 22 and 20, Owner on 6 September. */
            --symbol: 33;
        }

        [data-type="map-marker"][data-kind="camp"] {
            background-image: url("/icons/map-camp.webp");
            /* 29 instead of 30 since {T0047S4}, Owner on 6 September; 25 since
               {T2001S13}, four points smaller, then 20 on the owner's look the
               same day (28 September 2026). */
            --symbol: 20;
        }

        [data-type="map-marker"][data-kind="harbour"] {
            background-image: url("/icons/map-dverger-harbor.webp");
        }

        /* The dvergr harbour stays at thirty-two points (Owner, 5 September
           2026: »aber nicht die Dvergerhaefen, die passen so«). */
        [data-type="map-marker"][data-kind="harbour"] {
            --symbol: 32;
        }

        /* Fuling camp and dvergr settlement stand at thirty points - exactly
           between the base rule and the harbour (Owner: 35, 32, 28 - »och
           noe, jetzt sind die Dvergersiedlungen wieder so winzig« -, then 32
           and finally »genau dazwischen waere es ja«). They are places where
           somebody lives, and so stand above the hut but below the outpost,
           which is the landmark. */
        [data-type="map-marker"][data-kind="dvergr"] {
            --symbol: 30;
        }

        [data-type="map-marker"][data-kind="dvergr"] {
            background-image: url("/icons/map-dverger-home.webp");
        }

        /* The infested dvergr buildings ({T0067}): the decayed watchtower
           with green glow and spears where seekers stand. The same thirty
           points as the settlement - the same rank, another state. The rule
           stands behind the layer rule, otherwise the layer wins ({K0157}). */
        [data-type="map-marker"][data-kind="dvergr-infested"] {
            background-image: url("/icons/map-dverger-infested.webp");
            --symbol: 30;
        }

        /* The empty dvergr buildings ({T0069}): watchtower 3 decayed and the
           lighthouse. The same thirty points, the same rank - and since
           {T2007} the settlement's picture, the owner on 30 September 2026:
           »"verlassene dvergersiedlung" bekommt das gleiche icon wie
           dverger-siedlung - idr sind sie garnicht verlassen«. */
        [data-type="map-marker"][data-kind="dvergr-empty"] {
            background-image: url("/icons/map-dverger-home.webp");
            --symbol: 30;
        }

        [data-type="map-marker"][data-kind="hut"] {
            background-image: url("/icons/map-hut.webp");
        }

        /* The outpost stood larger than the base rule - 35 instead of 28 points
           (Owner, 5 September 2026, after 37, 32 and the return to 28 with a new
           drawing), then 32; since {T2004} it stands at the base rule's 28. It is
           the landmark in foreign territory and may stand above the hut, but
           below the boss symbol (37). */
        [data-type="map-marker"][data-kind="post"] {
            background-image: url("/icons/map-post.webp");
            /* 28 since {T2004}, the owner on 29 September 2026: »das icon für
               aussenposten 4px kleiner«; 32 since {T0047S5}, before 34 and 35 -
               Owner on 6 September. */
            --symbol: 28;
        }

        /* The buildings of the Ashlands stand twice as large as the other
           symbols of the map - since 5 September 54 points, a third more
           than the 40 of the day before (Owner, 4 September 2026: »die gehen
           dort voellig unter«). The ground of the Ashlands is red-brown and
           restless, and a grey symbol vanishes in it. So the fortress is
           larger than the boss symbols, and that is wanted: they are the
           largest structures of the game. */
        [data-type="map-marker"][data-kind="fortress"] {
            --symbol: 54;
        }

        /* Mörkhalla, the Jotun fortress of the North ({T0074}). It shares the
           layer with the fortress of the Ashlands, but stands six points
           smaller (Owner, 10 September 2026): its symbol carries more.
           The ruins stand a third smaller (Owner, 5 September 2026). With
           sixty-eight in one view they are the most frequent structure of
           the Ashlands, and what is frequent may not be as large as what is
           rare: the fourteen fortresses should be found between them. */
        [data-type="map-marker"][data-kind="ruin"] {
            --symbol: 27;
        }

        /* Two structures, two symbols ({T0027S10} QA): CharredFortress is the
           fortress one takes - twenty in the world and placed with priority
           -, FortressRuins are the hundred decayed ones. They first stood
           under one picture, and so the map did not show which is which
           (Owner, 4 September 2026). */
        [data-type="map-marker"][data-kind="fortress"] {
            background-image: url("/icons/map-fortress.webp");
        }

        [data-type="map-marker"][data-kind="ruin"] {
            background-image: url("/icons/map-ruin.webp");
        }
        /* Every ruin of the Ashlands wears the owner's picture of 30 September
           2026, »ab sofort dieses bild für alle ruinen in den ashlands
           verwenden«; the one standing in the water its own, »das hier für die
           ruinen auf dem wasser« ({T2006}), at the ruin's size. */
        [data-type="map-marker"][data-kind="sunken-ruin"] {
            background-image: url("/icons/map-sunken-ruin.webp");
            --symbol: 27;
        }

        [data-type="map-marker"][data-layer="settlements"] [data-type="map-marker-name"] {
            display: none;
        }

        [data-type="map-marker"][data-layer="settlements"]:hover [data-type="map-marker-name"] {
            display: block;
        }


        [data-type="map-marker"][data-kind="spawn"] {
            /* Under /icons and not under /karte: the map directory belongs to the
               service and is cleaned up ({T0026S12}) - a symbol in it would
               be gone some day. */
            background-image: url("/icons/map-spawn.webp");
            /* At the very top: there is one per world ({T0045S1}). */
            z-index: 4;
        }

        /* One picture per resolution level, all stacked at the full size of
           the layer ({T0026S11}). Which one sees, the opacity says; the
           change is a transition and comes only once the picture is loaded
           — so nothing flashes on zooming and nothing is missing. Pixelated
           when enlarged, as the map should be when one zooms beyond its
           level.

           `prefers-reduced-motion` switches the transition off: then the
           picture changes hard, which costs no information here (AW18). */
        /* `max-inline-size: none` is no ornament here: the site's base rule
           caps every picture to the width of its parent, and the tile layer
           has none — it is a point on which the tiles hang. Without this
           line all tiles are zero pixels wide (measured, 3 September 2026). */
        [data-type="map-tiles"] img {
            position: absolute;
            inset: 0;
            display: block;
            inline-size: 100%;
            block-size: 100%;
            max-inline-size: none;
            max-block-size: none;
            /* The initial value, because the first picture is always scaled up;
               from then on map.js sets it depending on the direction of the
               scaling — blocks when enlarging, smooth when shrinking
               ({T0026S13}). */
            image-rendering: pixelated;
            opacity: 0;
            transition: opacity 0.35s ease-out;
        }

        /* **A tile becomes visible when it is decoded** ({T0027S29}, before
           {T0027S17} QA). The rule above sets every picture of the layer to
           `opacity: 0`, because the script controls the opacity per level;
           the tiles in the container it switches singly with
           `data-present`, which comes only after `decode()`. So every tile
           fades in 0.35 s from the coarse underlay to the sharp map, instead
           of appearing hard at the moment of decoding - and the coarse level
           stays beneath until every tile in view is that far. */
        /* What one touches comes to the top and grows ({T0045S1}, Owner on
           6 September 2026) - every marker, whatever layer. The rule stands
           behind all layer rules and carries one attribute more, so that it
           beats each of them; before, every layer set its own hover value,
           and that lay under the bosses. The growth goes into the base
           rule, see there: four points, the centre stays. */
        [data-type="map-markers"] [data-type="map-marker"]:hover {
            z-index: 5;
            --growth: 4;
        }

        /* The readout under the pointer ({T0027S19}), as unobtrusive as a status
           line; it stands only when the mouse is over the map. Since {T2001S7}
           at the bottom right in two fields with a transparent gap of the
           close step (owner, 28 September 2026): left the place - the biome
           in gold, its metres in white, both bold -, right the position - the
           axes and the way to the spawn in white. Each value has a cell of the
           width of its longest form, so nothing moves while the pointer does;
           the digits are all of one width. On the phone the two stand one
           above the other. */
        [data-type="map-readout"] {
            position: absolute;
            inset-block-end: var(--space-tight);
            inset-inline-end: var(--space-tight);
            z-index: 3;
            display: flex;
            gap: var(--space-close);
            font-size: 12px;
            font-weight: 600;
            font-variant-numeric: tabular-nums;
            color: oklch(99% 0 0);
            pointer-events: none;
            white-space: nowrap;
        }
        [data-type="map-readout"] > span {
            display: grid;
            grid-auto-flow: column;
            column-gap: 0.35em;
            align-items: baseline;
            padding: 0.15rem 0.5rem;
            background: oklch(12% 0.01 260 / 0.65);
            border-radius: 2px;
        }
        /* Every value in a cell of fixed width (owner, 28 September 2026): the
           widths are the longest form each value can take, measured once by
           the script in the cell's own face; names stand at the start of
           their cell, numbers at its end, so only their length changes. The
           rem values are the resting width until the script has measured. */
        [data-type="map-readout"] [data-part="place"] {
            grid-template-columns: var(--cell-biome, 6rem) auto var(--cell-metres, 2.5rem);
        }
        [data-type="map-readout"] [data-part="position"] {
            grid-template-columns: var(--cell-axis, 3rem) auto var(--cell-axis, 3rem) auto var(--cell-way, 3.5rem);
        }
        [data-type="map-readout"] [data-part="biome"] {
            color: var(--gold);
            font-weight: 700;
        }
        [data-type="map-readout"] [data-part="metres"] {
            font-weight: 700;
        }
        /* The axes stand against their bar (owner, 28 September 2026): x at
           the end of its cell, y at the start of its own, so the pair reads
           as one - »692 | 0«. */
        [data-type="map-readout"] :is([data-part="metres"], [data-part="x"], [data-part="way"]) {
            text-align: end;
        }
        [data-type="map-readout"] [data-part="y"] {
            text-align: start;
        }
        @media (max-width: 34.99rem) {
            [data-type="map-readout"] {
                flex-direction: column;
                align-items: flex-end;
            }
        }

        [data-type="map-readout"][hidden] {
            display: none;
        }

        [data-type="map-tiles"] [data-tiles] img[data-present] {
            opacity: 1;
        }

        /* The container itself fades in like a level picture: if the fine
           level appears as a whole above the underlay, that is a transition
           from coarse to sharp and no jump ({T0027S29}). */
        [data-type="map-tiles"] [data-tiles] {
            transition: opacity 0.35s ease-out;
        }

        @media (prefers-reduced-motion: reduce) {
            [data-type="map-tiles"] img,
            [data-type="map-tiles"] [data-tiles] { transition: none; }
        }

        /* The loading layer: a blurred pane of glass over the whole map while
           the service computes, on it the line »[4/18] step« and the bar
           (Owner, 3 and 8 September 2026). It covers a half-finished map
           instead of showing it. The step list that stood here until
           {T0068S2} flashed up for milliseconds only and is gone. */
        [data-type="map-loading"] {
            position: absolute;
            display: grid;
            gap: var(--space-close);
            z-index: 3;
        }
        [data-type="map-loading"][data-form="full"] {
            inset: 0;
            place-content: center;
            justify-items: center;
            backdrop-filter: blur(8px) saturate(0.7);
            background: light-dark(oklch(100% 0 0 / 0.35), oklch(12% 0.01 260 / 0.45));
        }
        /* Afterwards: small in the upper right corner, without the pane - the
           map already stands, only reloading is left. The line on top, below
           it the bar, three points tall and as wide as the line: so the only
           edge that moves stays its fill ({T0068S2}). */
        /* The display area is always two hundred points wide, with or without
           the bar, however long the word ({T0068S4}, {T0068S5}, Owner): a box
           that breathes with the text wobbles at every step. First a
           hundred, then two hundred - the owner had meant two hundred from
           the start. The edge is added. */
        [data-type="map-loading"][data-form="corner"] {
            /* Below the row of the seed bar and the tools since {T2001S7}: the
               seed's name stays while the map loads, and the two tool
               buttons give way to nothing but air. */
            inset-block-start: calc(2.5rem + 2 * var(--space-close));
            inset-inline-end: var(--space-close);
            gap: var(--space-hair);
            justify-items: start;
            box-sizing: content-box;
            inline-size: 200px;
            padding: 0.25rem 0.5rem;
            background: light-dark(oklch(100% 0 0 / 0.7), oklch(12% 0.01 260 / 0.7));
            box-shadow: inset 1px 1px 0 oklch(33% 0.014 260), inset -1px -1px 0 oklch(12% 0.01 260);
        }
        /* The bar of the corner ({T0068S3}, Owner after the first look): as
           wide as the display area, three tall, no gold border around the
           empty part - that is a light grey, filled is gold. The text stands
           flush left above it, runs to the right beyond the bar and gets an
           ellipsis at the end when the corner gets too narrow. */
        [data-form="corner"] > progress {
            block-size: 3px;
            inline-size: 100%;
            border: 0;
            background: oklch(70% 0.01 260 / 0.35);
        }
        [data-form="corner"] > progress::-webkit-progress-bar {
            background: oklch(70% 0.01 260 / 0.35);
        }
        [data-form="corner"] [data-type="map-phase"] {
            text-align: start;
            inline-size: 100%;
            overflow: hidden;
            text-overflow: ellipsis;
        }
        [data-type="map-loading"][hidden] {
            display: none;
        }
        [data-type="map-phase"] {
            margin: 0;
            font-size: var(--text-small);
            letter-spacing: 0.04em;
            color: var(--ink-bright);
            text-align: center;
            white-space: nowrap;
            font-variant-numeric: tabular-nums;
        }
        /* The line after an error: not red but calmly different - the text
           says what is missing; the colour only says that it is no loading
           any more. */
        [data-type="map-loading"][data-error] [data-type="map-phase"] {
            color: var(--ink-quiet);
        }
        [data-type="map-loading"][data-error] > progress {
            visibility: hidden;
        }
    }
}

/* The handle for the large view ({T0028S1}).

   It lies in the one cell of the window, bottom right, and is a label over
   a checkbox: switched without script, like the layer filters. The checkbox
   lies beneath and stays focusable — it carries the state the has rule on
   the sheet reads.

   The symbol is two corner brackets, drawn from four inner shadows in the
   corners of an empty box: no SVG for four strokes, no second file, and it
   carries the theme colour by itself. In the large view the corners turn
   inwards — the same symbol then says »shrink«. */
@layer slices.slice_map {
    @scope ([data-slice="map"]) {
        /* The button group top right ({T0084}). It sits in the same grid cell
           as the full-screen switch, only at the other edge, and carries its
           look: same size, same pixel edges, same half-transparent ground.
           Two tools on one map should look like two buttons of the same
           device and not like two designs. */
        /* While the map computes, the step display lies in the same corner —
           and there would only be half a picture to save anyway. So the
           tools do not move aside, they wait: the script sets `hidden` on
           them where it switches the loading bar (showLevel). **Not by
           :has() on the map window** ({T0145}): the rule
           `[data-type="map-window"]:has([data-type="map-loading"]:not([hidden])) [data-type="map-tools"]`
           made Blink recompute all descendants of the window with a
           data-type at every change of the loading bar - per picture while
           panning -, 31 640 elements in eight pictures; on the phone
           seconds per move. */

        [data-type="map-tools"] {
            place-self: start end;
            z-index: 4;
            display: flex;
            gap: var(--space-close);
            margin: var(--space-close);
            /* The seed bar stands in the same row since {T2001S7}, and the row
               may not grow past the window. */
            min-inline-size: 0;
            max-inline-size: calc(100% - 2 * var(--space-close));
        }
        [data-type="map-tools"] > [data-type="map-tool"] { flex: 0 0 auto; }
        [data-type="map-tools"] > [data-type="map-tool"][hidden] { display: none; }

        [data-type="map-tool"] {
            display: grid;
            place-items: center;
            inline-size: 2.5rem;
            block-size: 2.5rem;
            padding: 0;
            border: 0;
            cursor: pointer;
            background: color-mix(in oklch, var(--surface-0) 72%, transparent);
            box-shadow:
                inset 2px 2px 0 oklch(from var(--surface-4) calc(l + 0.12) c h),
                inset -2px -2px 0 oklch(from var(--surface-0) calc(l - 0.06) c h);
        }

        [data-type="map-tool"]:hover {
            background: color-mix(in oklch, var(--link) 6%, var(--surface-0) 72%);
            box-shadow:
                inset 2px 2px 0 oklch(from var(--surface-0) calc(l - 0.06) c h),
                inset -2px -2px 0 oklch(from var(--surface-4) calc(l + 0.12) c h);
        }

        [data-type="map-tool"]:focus-visible {
            outline: 2px solid var(--link);
            outline-offset: 2px;
        }

        /* While the picture comes about, the button is not operable: a second
           click built a second picture and delivered both. */
        [data-type="map-tool"][aria-busy="true"] { cursor: progress; opacity: 0.6; }

        /* The symbols, drawn instead of set. Save: an arrow down over an open
           tray. Share: three dots, two connections. */
        [data-type="map-tool"] [data-part="symbol"] {
            inline-size: 1.125rem;
            block-size: 1.125rem;
            background-repeat: no-repeat;
        }

        [data-symbol="save"] {
            --stroke: var(--ink-bright);
            background:
                /* Shaft */
                linear-gradient(var(--stroke), var(--stroke)) 50% 0 / 2px 0.62rem,
                /* Tip, two slants */
                linear-gradient(45deg, transparent 46%, var(--stroke) 46% 54%, transparent 54%)
                    0.16rem 0.34rem / 0.4rem 0.4rem,
                linear-gradient(-45deg, transparent 46%, var(--stroke) 46% 54%, transparent 54%)
                    0.56rem 0.34rem / 0.4rem 0.4rem,
                /* Tray */
                linear-gradient(var(--stroke), var(--stroke)) 0 100% / 2px 0.34rem,
                linear-gradient(var(--stroke), var(--stroke)) 100% 100% / 2px 0.34rem,
                linear-gradient(var(--stroke), var(--stroke)) 0 100% / 100% 2px;
        }

        [data-symbol="share"] {
            --stroke: var(--ink-bright);
            --dot: 0.34rem;
            background:
                /* two connecting lines, then the three dots above */
                linear-gradient(-32deg, transparent 45%, var(--stroke) 45% 55%, transparent 55%)
                    0.1rem 0.1rem / 0.95rem 0.5rem,
                linear-gradient(32deg, transparent 45%, var(--stroke) 45% 55%, transparent 55%)
                    0.1rem 0.52rem / 0.95rem 0.5rem,
                radial-gradient(circle, var(--stroke) 48%, transparent 50%)
                    100% 0 / var(--dot) var(--dot),
                radial-gradient(circle, var(--stroke) 48%, transparent 50%)
                    100% 100% / var(--dot) var(--dot),
                radial-gradient(circle, var(--stroke) 48%, transparent 50%)
                    0 50% / var(--dot) var(--dot);
        }

        /* Info ({T2011S1}): the letter i, drawn like the two beside it - a dot
           over a stem on a foot, in the same stroke. */
        [data-symbol="info"] {
            --stroke: var(--ink-bright);
            --dot: 0.34rem;
            background:
                radial-gradient(circle, var(--stroke) 48%, transparent 50%)
                    50% 0 / var(--dot) var(--dot),
                linear-gradient(var(--stroke), var(--stroke)) 50% 100% / 2px 0.66rem,
                linear-gradient(var(--stroke), var(--stroke)) 50% 100% / 0.5rem 2px,
                linear-gradient(var(--stroke), var(--stroke)) 0.34rem 0.46rem / 0.25rem 2px;
        }

        /* The feedback sheet. Popover instead of an overlay of our own: the
           browser brings focus trap, closing with Escape and the top layer. */
        /* The two sheets of the map carry the shape of the item sheet
           ({T0084}): golden frame, dark face, sticky head with the close
           symbol on the right. A second look for the same thing would be a
           second language on the same page.

           The widget itself cannot be called: it demands symbol, name and
           way of an item, and an export dialogue has none of those. Taken
           over are therefore the measures and the tones, with this line as
           the proof of origin — whoever changes something there changes it
           here too. When a third caller comes, the shell is due as an
           element of its own ({K0054}). */
        [data-type="map-sheet"] {
            position: fixed;
            inset: 0;
            margin: auto;
            z-index: var(--sheet-layer);
            inline-size: var(--sheet-inline-size);
            max-block-size: var(--sheet-max-block-size);
            overflow-y: auto;
            padding: 0;
            border: var(--sheet-edge) solid var(--gold);
            background: var(--surface-4);
            color: var(--ink);
            box-shadow: var(--sheet-shadow);
        }

        [data-type="map-sheet"]::backdrop {
            background: var(--sheet-scrim);
        }

        [data-type="map-sheet"] > header {
            position: sticky;
            inset-block-start: 0;
            display: grid;
            align-items: center;
            gap: 0.75rem;
            padding: 0.875rem 1rem;
            background: var(--surface-4);
        }

        /* The close symbol sits in the corner, not in a grid column ({T0086},
           Owner on 11 September 2026). In the column the padding of the head
           pushed it inwards, and it stood a finger's width from the edge —
           in the item sheet it lies in the corner. The same position so that
           both sheets close the same way without one having to look first.

           Four points of distance, as there: the reference is the sheet
           that stands `fixed`, not the head. */
        [data-type="map-sheet"] [data-role="close"] {
            position: absolute;
            inset-block-start: 4px;
            inset-inline-end: 4px;
            z-index: 1;
        }

        [data-type="map-sheet"] > header b {
            font-size: var(--text-large, 1.125rem);
        }

        [data-type="map-sheet"] [data-part="dossier"] {
            padding: 0 1rem 0.5rem;
        }

        [data-type="map-sheet"] [data-part="head"] {
            display: flex;
            flex-wrap: wrap;
            gap: 0.5rem;
            align-items: baseline;
        }

        [data-type="map-sheet"] [data-part="title"] {
            font-weight: 700;
        }

        [data-type="map-sheet"] [data-part="aside"],
        [data-type="map-sheet"] [data-part="dimensions"] {
            font-size: var(--text-small, 0.875rem);
            color: var(--ink-muted, var(--ink));
        }

        /* The view around the spawn: it says at one glance which world is
           meant. Without a picture the field stays away instead of standing empty. */
        [data-type="map-sheet"] [data-part="preview"] {
            inline-size: 100%;
            /* 16:9 — the measure in which a shared picture is shown in the
               networks. The view lies around the spawn, not around the tile
               centre: otherwise the starting place drops out. */
            aspect-ratio: 16 / 9;
            margin-block-end: var(--space-close);
            background: var(--surface-0);
        }

        [data-type="map-sheet"] [data-part="text"] {
            inline-size: 100%;
            resize: none;
            font: inherit;
            font-size: var(--text-small, 0.875rem);
            padding: var(--space-close);
            border: 1px solid var(--surface-0);
            background: var(--surface-0);
            color: var(--ink);
        }

        /* The button sits at the bottom of the sheet and fills the width — the
           same gesture as in the item sheet, where the way into the building
           plan stands so. */
        /* The preview window in the save sheet ({T0084}, Owner on
           11 September 2026). The canvas is 4096 points wide and is shown
           here at sheet width — the browser scales that down, and because it
           does, one sees the tiles arrive without a second picture coming
           about anywhere.

           Square, because the world is. The face beneath is the darkest of
           the design: before the first tile lies, no white should shine there. */
        [data-type="map-sheet"] [data-part="picture"] {
            inline-size: calc(100% - 2rem);
            aspect-ratio: 1;
            margin: 0 1rem var(--space-close);
            background: var(--surface-0);
        }

        /* The loading state above the button ({T0084}, Owner on 11 September
           2026). It sits in the same margin as the button below, so that
           both form one edge, and takes the full width: the bar of the
           corner is a hundred points wide because there is no room there —
           here there is, and one percent per pixel reads more easily the
           longer the stretch. */
        [data-type="map-sheet"] [data-part="progress"] {
            margin: 0 1rem var(--space-close);
        }

        [data-type="map-sheet"] [data-part="progress"] [data-type="progress"] {
            inline-size: 100%;
        }

        [data-type="map-sheet"] [data-type="button"] {
            display: block;
            inline-size: calc(100% - 2rem);
            margin: 0 1rem 1rem;
        }

        /* The info sheet ({T2011S1}): running text under headings, in the shape
           of the two sheets beside it. A heading is never smaller than the
           text under it (owner, 1 October 2026: »wir müssen die überschriften
           schon mindestens gleich gross sein lassen«): the second level at the
           prose's size, the first one step of the scale above - not the page's
           h2, which is the stage's size and would break every second word in
           the sheet. The scrollbar is the house's, thin and gold, as on the
           map options. */
        [data-part="info-sheet"] {
            font-size: var(--text);
            scrollbar-width: thin;
            scrollbar-color: var(--gold) transparent;
        }

        /* Wider than a phone, the sheet is as wide as a line of 60 characters
           (owner, 1 October 2026: »so breit, dass zeilen bei 60 zeichen
           umbrechen«): 60ch of the running text, the dossier's padding and
           the frame - in ch of the sheet's own size, which is the prose's. */
        @media (min-width: 35rem) {
            [data-part="info-sheet"] {
                inline-size: min(calc(60ch + 2rem + 2 * var(--sheet-edge)), calc(100vw - 2rem));
            }
            [data-part="info-sheet"] [data-type="prose"] {
                max-inline-size: 60ch;
            }
        }

        [data-part="info-sheet"] [data-part="dossier"] {
            display: grid;
            gap: var(--space-snug);
            padding-block-end: 1rem;
        }

        [data-part="info-sheet"] [data-type="heading"] {
            margin: var(--space-snug) 0 0;
        }

        /* The sheet's first level in gold - the world's heading and the three
           of what holds for every world (owner, 1 October 2026: »alle dieses
           levels im popover sollten ja golden sein«); the second level, the
           lists' and the questions', stays the ink's. */
        [data-part="info-sheet"] [data-type="heading"]:is(h2) {
            font-size: var(--text-large);
            color: var(--gold);
        }
        [data-part="info-sheet"] [data-type="heading"]:is(h3) { font-size: var(--text); }

        [data-part="info-sheet"] [data-part="dossier"] > [data-type="heading"]:first-child {
            margin-block-start: 0;
        }

        [data-part="info-sheet"] [data-type="prose"] {
            margin: 0;
        }

        /* The line between two sections: one point, dashed, gold, with twice
           a step of the scale above and below - the owner asked for 8 points
           from the golden scale, the step nearest is --space-close, and on
           seeing it the owner asked for the double: 17.3 points on the phone, 18.5
           on the tablet, 21 on the screen. The grid's gap stands on both sides
           already; the margin brings it up, and the heading after the line
           keeps no margin of its own. */
        [data-part="info-sheet"] [data-part="rule"] {
            block-size: 0;
            margin-block: calc(2 * var(--space-close) - var(--space-snug));
            margin-inline: 0;
            border: 0;
            border-block-start: 1px dashed var(--gold);
        }

        [data-part="info-sheet"] [data-part="rule"] + [data-type="heading"] {
            margin-block-start: 0;
        }

        /* The biome shares as bars (owner, 1 October 2026): the name in the
           first column, as wide as the longest; the track in the second, 100
           per cent, filled to the share in the biome's colour; the figure at
           the track's right end in white - and in gold where the bar reaches
           under it. The metric element carries name and figure; here it gives
           up its own box, so both stand in the row's columns, and the rows
           share their columns through subgrid. */
        [data-part="info-sheet"] [data-part="shares"] {
            display: grid;
            grid-template-columns: max-content 1fr;
            gap: var(--space-tight) var(--space-snug);
        }

        [data-part="info-sheet"] [data-part="share"] {
            --fill: var(--biome-edge);
            grid-column: 1 / -1;
            display: grid;
            grid-template-columns: subgrid;
            align-items: center;
        }

        [data-part="share"] [data-type="metric"] { display: contents; }

        [data-part="share"] dt {
            grid-row: 1;
            grid-column: 1;
            font-family: inherit;
            font-size: var(--text);
            letter-spacing: normal;
            text-transform: none;
            color: var(--ink);
        }

        [data-part="share"] dd {
            grid-row: 1;
            grid-column: 2;
            padding-inline: var(--space-tight);
            font-size: var(--text-small);
            line-height: 1.6;
            text-align: end;
            color: var(--ink-bright);
            background: linear-gradient(to right,
                var(--fill) var(--share), var(--surface-0) var(--share));
        }

        [data-part="share"][data-over] dd { color: var(--gold); }

        /* The theme's biome colours, by the keys of the meaning layer;
           the Black Forest is the theme's forest. */
        [data-part="share"][data-biome="ocean"]       { --fill: var(--biome-ocean); }
        [data-part="share"][data-biome="meadows"]     { --fill: var(--biome-meadows); }
        [data-part="share"][data-biome="swamp"]       { --fill: var(--biome-swamp); }
        [data-part="share"][data-biome="mountain"]    { --fill: var(--biome-mountain); }
        [data-part="share"][data-biome="blackforest"] { --fill: var(--biome-forest); }
        [data-part="share"][data-biome="plains"]      { --fill: var(--biome-plains); }
        [data-part="share"][data-biome="ashlands"]    { --fill: var(--biome-ashlands); }
        [data-part="share"][data-biome="deepnorth"]   { --fill: var(--biome-deepnorth); }
        [data-part="share"][data-biome="mistlands"]   { --fill: var(--biome-mistlands); }

    }
}

/* The panel container ({T0028S3}).

   In the small view it is not there for the layout: `display: contents`
   lets its children stay direct grid children of the band, so nothing
   changes there. The tab belongs to the large view and does not stand here. */
@layer slices.slice_map {
    @scope ([data-slice="map"]) {
        [data-part="panel"],
        [data-part="content"] { display: contents; }
        [data-type="map-tab"] { display: none; }

        /* And its children need the track themselves ({T0028S3} QA): the base
           rule `[data-slice] > *` hits only direct children, and with
           `display: contents` the container is none any more — its children
           are. Without this line the heading lands in the gutter, 40 pixels
           wide and 214 tall, and the map in an implicit row of 61 pixels.
           Measured on the running page; the owner saw it before a
           screenshot showed it. */
        [data-part="content"] > * { grid-column: content; }
    }
}

/* The star and its sheet ({T0030S1}).

   The star stands beside the title in the small view and in the large one
   in the same grid cell at its end. It is a button without a face: the
   symbol carries the statement, and filled means remembered.

   Two stars from one symbol would be two files; one from two backgrounds
   is none. So it stands as text — a glyph of the typeface, coloured: empty
   in the ink colour, filled in gold. */
@layer slices.slice_map {
    @scope ([data-slice="map"]) {
        /* The seed bar ({T2001S7}): the seed's name and its star in one field at
           the top right of the map, beside download and share, as the owner's
           picture shows it - the tools' height, ground and pixel edge, the
           name in the pixel type, the star in gold as in the picture and
           filled when the world is remembered. It stood as the drawer's
           title row until then; the star keeps its anchor, and the sheet
           opens at it. The name gives way before the tools do: on a narrow
           window it is cut with an ellipsis, never wrapped. */
        [data-part="seed-bar"] {
            display: flex;
            align-items: center;
            gap: var(--space-snug);
            min-inline-size: 0;
            block-size: 2.5rem;
            padding-inline: var(--space-close) var(--space-snug);
            background: color-mix(in oklch, var(--surface-0) 72%, transparent);
            box-shadow:
                inset 2px 2px 0 oklch(from var(--surface-4) calc(l + 0.12) c h),
                inset -2px -2px 0 oklch(from var(--surface-0) calc(l - 0.06) c h);
        }

        [data-part="seed-bar"] > [data-type="heading"] {
            min-inline-size: 0;
            margin: 0;
            font-size: 1rem;
            line-height: 1;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
        }

        [data-part="seed-bar"] > [data-type="icon-button"] {
            flex: 0 0 auto;
            inline-size: auto;
            font-size: var(--text-large);
            line-height: 1;
            color: var(--gold);
            anchor-name: --map-star;
        }

        [data-part="seed-bar"] > [data-type="icon-button"]:hover {
            color: oklch(from var(--gold) calc(l + 0.08) c h);
        }

        /* On the phone the closed drawer - the random roll and the handle -
           stands at the top left of the same row, so the tools leave it its
           width: the roll with its edge and the handle, and a gap. The name
           then gives way with an ellipsis before anything overlaps. */
        @media (max-width: 34.99rem) {
            [data-part="seed-bar"] > [data-type="heading"] { font-size: 0.625rem; }
            /* The seed alone on the phone (owner, 28 September 2026): »Seed:«
               stays in the heading for a screen reader and a search engine,
               clipped to nothing for the eye. */
            [data-part="seed-bar"] [data-part="lead"] {
                position: absolute;
                inline-size: 1px;
                block-size: 1px;
                overflow: hidden;
                clip-path: inset(50%);
                white-space: nowrap;
            }
            [data-type="map-tools"] {
                max-inline-size: calc(100% - 4.75rem - 4 * var(--space-close) - var(--space-tight));
            }
        }

        /* The sheet hangs on the star: anchor positioning instead of a computed
           corner (platform-features.md, Newly since March 2026). If the
           anchor fails, it stands centred — that is the resting value of the
           top layer and no makeshift that covers something. */
        [data-type="map-fav"] {
            /* `position: fixed` stands there expressly, although the browser gives
               it to a popover itself — and `display` does **not** stand
               there. A `display: grid` at this point overrides the `display:
               none` with which the browser takes a closed popover out of the
               flow: author rules beat the browser's regardless of
               specificity. Measured: the closed sheet stood as the third
               grid row of 186 pixels in the panel, invisible and taking
               room. The grid comes only with the opening. */
            position: fixed;
            position-anchor: --map-star;
            position-area: block-end span-inline-start;
            position-try-fallbacks: flip-block, flip-inline;
            margin: var(--space-tight);
            inline-size: min(22rem, calc(100vi - 2 * var(--space-close)));
            gap: var(--space-snug);
            padding: var(--space-close);
            /* The token and not the number ({T0097}): it stood here written out,
               while the two sheets beside it in the same slice read
               `--sheet-edge`. The same value, two spellings — and the second
               would have been missing at the first change. */
            border: var(--sheet-edge) solid var(--gold);
            background: var(--surface-1);
            color: var(--ink-bright);

            /* The house form for everything that pops up ({K0101}): the resting
               value is closed, overlay transitions along, and the z-index
               stands beside it because overlay does not bridge the moment of
               closing everywhere. */
            opacity: 0;
            transition-property: opacity, overlay, display;
            transition-duration: 300ms;
            transition-behavior: allow-discrete;
            z-index: 9999;
        }

        [data-type="map-fav"]:popover-open { display: grid; opacity: 1; }

        @starting-style {
            [data-type="map-fav"]:popover-open { opacity: 0; }
        }

        @media (prefers-reduced-motion: reduce) {
            [data-type="map-fav"] { transition-duration: 1ms; }
        }

        [data-type="map-fav"] > header {
            display: grid;
            grid-template-columns: 1fr auto;
            align-items: center;
            gap: var(--space-snug);
        }

        /* The form: field, save, remove on one line, aligned at the bottom
           edge - the field carries its label above, the buttons do not. Their
           shape is brought by the building blocks; here stands only the
           arrangement. */
        [data-type="map-fav"] [data-part="remember"] {
            display: grid;
            grid-template-columns: 1fr auto auto;
            gap: var(--space-snug);
            justify-items: end;
        }

        /* The field has the whole line for itself, the buttons stand below on
           the right. On one line with both they left the field sixty pixels
           - measured in the picture, »Mein« was readable and nothing else. */
        [data-type="map-fav"] [data-part="remember"] > [data-type="field"] {
            grid-column: 1 / -1;
            justify-self: stretch;
        }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: The metrics band — set off, bounded above and below. */

@layer slices.slice_metrics {
    @scope ([data-slice="metrics"]) {
        :scope {
            padding-block: var(--space-open);
            background: var(--surface-2);

            /* Here a line and not only the face: the band sits between two bands
               of similar lightness, and without an edge the boundary would
               blur. The exception from {K0031} is reasoned, not convenient. */
            border-block: 1px solid var(--surface-4);
        }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: What is new — two columns, one under the other on a narrow screen. */

@layer slices.slice_news {
    @scope ([data-slice="news"]) {
        :scope {
            padding-block: var(--space-open);
        }

        /* The container sits in the content track of the page grid ({K0031})
           and carries its own in it. */
        [data-part="columns"] {
            grid-column: content;

            /* **`auto-fit` instead of a ladder of steps** (AW6). Two columns where
               there is room for two, otherwise one — and the grid finds the
               limit itself from the minimum width. A window query would have
               to guess it and would be wrong at every rebuild; exactly hence
               no `@media` stands here, although the neighbouring slice has one. */
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(min(22rem, 100%), 1fr));
            gap: var(--space-wide);
        }

        [data-part="column"] {
            display: grid;
            gap: var(--space-close);
            align-content: start;
        }

        /* No `list-style`: the enumeration lies in the matter and needs no dot
           in front — the date in front is the mark. */
        [data-part="list"] {
            display: grid;
            gap: var(--space-close);
            margin: 0;
            padding: 0;
            list-style: none;
        }

        [data-part="list"] > li {
            display: grid;
            grid-template-columns: auto 1fr;
            gap: var(--space-snug);
            align-items: baseline;
        }

        /* The date in the house's label type — monospace, so that dates
           standing one under the other stand one under the other. */
        [data-part="date"] {
            font-family: ui-monospace, monospace;
            font-size: var(--text-small);
            color: var(--ink-quiet);
            white-space: nowrap;
        }

        [data-part="word"] { color: var(--ink); }

        [data-part="empty"] {
            margin: 0;
            color: var(--ink-quiet);
        }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: The status window — one line per service, and the traffic light on it. */

@layer slices.slice_status {
    @scope ([data-slice="status"]) {
        /* **The spacing lies here and not between the slices** \({K0031}\):
           slices abut, and the spacing comes about in the slice as inner
           room. `--space-open` is 1.618 of the base step — the step of the
           ladder that carries a block of its own without setting it free
           like the intro \(that has `--space-wide`\). */
        :scope {
            padding-block: var(--space-open);
            padding-inline: var(--gutter);
        }

        /* Title on the left, player count on the right — and one under the
           other when narrow. `wrap` instead of a window query: the wrap
           falls when the room falls, and not at a guessed width \(AW6\). */
        [data-part="head"] {
            display: flex;
            flex-wrap: wrap;
            align-items: baseline;
            justify-content: space-between;
            gap: var(--space-close);
        }

        /* Title and state as a block, so that the state sits under the heading
           and not beside it — it belongs to the heading, not in the row
           with the player count. */
        [data-part="title"] {
            display: flex;
            flex-direction: column;
            gap: var(--space-hair);
        }

        /* Small print: it should be there when one looks for it, and not
           compete with the lines. */
        [data-part="as-of"] {
            margin: 0;
            color: var(--ink-quiet);
            font-size: var(--text-micro);
        }

        /* The number in the manner of the metrics band below: it is the
           statement, the word places it. */
        [data-part="players"] {
            display: flex;
            align-items: baseline;
            gap: var(--space-tight);
            margin: 0;
        }

        [data-part="number"] {
            color: var(--link);
            font-size: var(--text-large);
            font-weight: 600;
            font-variant-numeric: tabular-nums;
        }

        [data-part="players"] [data-part="word"] {
            color: var(--ink-quiet);
            font-size: var(--text-small);
        }

        [data-part="alt"] {
            margin-block: var(--space-close) 0;
            color: var(--ink-quiet);
            font-size: var(--text-small);
        }

        /* One line per service \(Owner, 12 September 2026\). The pair of name
           and state lies in a grid with two tracks: the name takes what it
           needs, the state stands to the right of it. `dl` brings its own
           spacings, which have no business here. */
        [data-part="services"] {
            margin-block: var(--space-close) 0;
        }

        [data-part="line"] {
            display: grid;
            grid-template-columns: 1fr auto;
            align-items: center;
            gap: var(--space-close);
            padding-block: var(--space-tight);
        }

        /* A line between the rows, none around them: the slice has its face,
           and a box in the box would be one too many \(AW1\). */
        [data-part="line"] + [data-part="line"] {
            border-block-start: 1px solid var(--surface-4);
        }

        [data-part="name"] {
            margin: 0;
            color: var(--ink);
        }

        [data-part="state"] {
            display: flex;
            align-items: center;
            gap: var(--space-tight);
            margin: 0;
        }

        [data-part="state"] [data-part="word"] {
            color: var(--ink-quiet);
            font-size: var(--text-small);
        }

        /* The light is round and small and carries **no** statement alone:
           beside it stands the word \(AW4\). Hence `aria-hidden` on the dot
           — it only repeats what already stands there, and a screen reader
           saying »dot« helps nobody. */
        [data-part="light"] {
            inline-size: 0.625rem;
            block-size: 0.625rem;
            border-radius: 50%;
            background: var(--light, var(--ink-quiet));
            flex: none;
        }

        /* **The three colours of the traffic light are the three of the
           palette** \({K0234}\): the primary green for »running«, the
           secondary gold for »disturbed«, and the dark red of the mark for
           »off« — computed from `--danger` and here instead of in `:root`,
           because AW10 and {K0004} want it so: a derived tone stands at the
           context, not at the root. No fourth value, no traffic-light
           palette of its own. What was not measured stays muted — it is no
           state of the service but one of our measurement. */
        [data-state="green"]  { --light: var(--link); }
        [data-state="yellow"]   { --light: var(--gold); }
        [data-state="red"]    { --light: oklch(from var(--danger) 0.50 c h); }

        /* The state colours the word too when it deviates from »running«:
           with four green lines nothing should shout, with one red line it
           should. */
        [data-state="yellow"]  [data-part="word"] { color: var(--gold); }
        [data-state="red"]   [data-part="word"] { color: var(--danger); }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: The tool cards — one column, two columns, three columns. */

@layer slices.slice_tools {
    @scope ([data-slice="tools"]) {
        /* A ground of its own, one step below the page, with an edge upwards:
           the band lifts the cards out without framing them. From the design. */
        :scope {
            padding-block: 2.5rem;
            background: var(--surface-1);
            border-block-start: 1px solid oklch(30% 0.014 var(--hue-surface));
        }

        /* The cards share the content track among themselves. Two things are
           needed for that: minmax\(0, 1fr\) instead of 1fr, so that a long
           word in the title does not stretch the track — and a computed
           gutter instead of minmax\(gutter, 1fr\), because otherwise five
           flexible tracks fight over the same room and the cards move to
           the middle \({K0057}\). */
        :scope > [data-type="tool-card"] {
            grid-column: content;
        }

        :scope {
            row-gap: 1.25rem;
        }

        @media (min-width: 35rem) {
            :scope {
                grid-template-columns:
                    [full-start] var(--edge)
                    [content-start] minmax(0, 1fr) 1.25rem minmax(0, 1fr)
                    [content-end] var(--edge)
                    [full-end];
            }
            :scope > [data-type="tool-card"] { grid-column: auto; }
            :scope > [data-type="tool-card"]:first-child { grid-column: 2; }
            :scope > [data-type="tool-card"]:nth-child(2) { grid-column: 4; }
            :scope > [data-type="tool-card"]:nth-child(3) { grid-column: 2 / 5; }
        }

        @media (min-width: 60rem) {
            :scope {
                grid-template-columns:
                    [full-start] var(--edge)
                    [content-start] minmax(0, 1fr) 1.25rem
                                    minmax(0, 1fr) 1.25rem minmax(0, 1fr)
                    [content-end] var(--edge)
                    [full-end];
            }
            :scope > [data-type="tool-card"]:nth-child(2) { grid-column: 4; }
            :scope > [data-type="tool-card"]:nth-child(3) { grid-column: 6; }
        }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: The building plan ({T0073S4}) - bar, gold strip, area, tree on the
   biome axis ({T0073S7}). */

@layer slices.slice_tree {
    @scope ([data-slice="tree"]) {
        :scope {
            /* The height of one row of the tree: the circle, one to two lines of
               name, little air - the owner found the first version too loose
               ({T0073S7}); before 6.5 and 7 rem. */
            --row: 4.5rem;
            padding-block: 2.5rem;
            display: grid;
            row-gap: 1.25rem;
            /* Three columns like the band itself (base.css: edge, content, edge)
               - an assignment with only one column laid everything into the
               edge track, and the page stood at 40 px width on the phone. */
            grid-template-areas:
                ". lead  ."
                ". board .";
        }

        :scope > * { grid-column: content; }

        [data-part="lead"] { grid-area: lead; }

        [data-part="intro"] {
            margin: 0.5rem 0 0;
            max-inline-size: 66ch;
            color: var(--ink-quiet);
        }

        /* ---- The area --------------------------------------------------- */

        /* The area is a container: the circles take their measure from its
           width (cqi), so that seven nodes in one row stand side by side on
           the phone too and nothing scrolls ({T0073S7}). */
        [data-part="board"] {
            grid-area: board;
            container-type: inline-size;
            display: grid;
            row-gap: 0.75rem;
            align-content: start;
            min-block-size: 20rem;
            padding: 1rem;
            /* The gold strip: two pixels, a hairline and therefore in px. */
            border-inline-start: 2px solid var(--gold);
            /* No ground of its own any more ({T0073S9}, Owner): the gradient
               pink, orange, purple from {T0073S4} is out, the colour is
               carried by the bands of the regions. */
        }

        [data-part="empty"] {
            display: grid;
            gap: 0.75rem;
            justify-items: start;
            align-self: center;
            padding: 1.5rem 0;

            p { margin: 0; color: var(--ink); }
            b { color: var(--ink-bright); font-weight: 500; }

            /* A target the database does not know ({T0073S13}): the bar that
               said it before no longer exists. */
            [data-part="missing"] {
                font-size: var(--text-small);
                color: var(--ink-quiet);

                b { color: var(--ink); font-weight: 500; }
            }
        }

        /* ---- The tree --------------------------------------------------- */

        /* The tree fills the area in width and grows downwards: more nodes
           mean more rows, never a scroll width (Owner: no scrollbar). */
        [data-part="tree"] {
            display: grid;
        }

        /* Bands, lines and nodes in **one** cell: the areas of the regions at
           the bottom, the drawing above, the nodes on top, all as large as
           the tree. */
        [data-part="tree"] > * {
            grid-area: 1 / 1;
            min-inline-size: 0;
            block-size: calc(var(--rows) * var(--row));
        }

        /* ---- The areas of the regions ({T0073S9}) ----------------------- */

        /* The same rows as the grid of the nodes; a band spans its rows and
           the whole width. */
        [data-part="bands"] {
            display: grid;
            grid-template-rows: repeat(var(--rows), var(--row));
            pointer-events: none;
        }

        /* The gradient per region as the bar of the item list carries it
           (Owner, 8 September 2026: »diese Verlaeufe«): from 35 percent
           biome colour on the ground on the left to 12 percent on the right
           - the same formula as --biome-gradient in slice_items.css, defined
           there on the bar and therefore here once more; the colour comes
           from the theme. The derivation stands at the context that sets
           --biome, not in the :root ({K0004}). */
        [data-part="band-area"] {
            --biome: var(--surface-4);
            grid-row: var(--from) / var(--to);
            grid-column: 1 / -1;
            background: linear-gradient(90deg,
                color-mix(in oklab, var(--biome) 35%, var(--surface-0)),
                color-mix(in oklab, var(--biome) 12%, var(--surface-0)));

            &[data-biome="meadows"]     { --biome: var(--biome-meadows); }
            &[data-biome="blackforest"] { --biome: var(--biome-forest); }
            &[data-biome="swamp"]       { --biome: var(--biome-swamp); }
            &[data-biome="mountain"]    { --biome: var(--biome-mountain); }
            &[data-biome="plains"]      { --biome: var(--biome-plains); }
            &[data-biome="mistlands"]   { --biome: var(--biome-mistlands); }
            &[data-biome="ashlands"]    { --biome: var(--biome-ashlands); }
            &[data-biome="deepnorth"]   { --biome: var(--biome-deepnorth); }
            &[data-biome="ocean"]       { --biome: var(--biome-ocean); }
        }

        [data-part="edges"] {
            inline-size: 100%;
            overflow: visible;
            pointer-events: none;

            /* Two colours ({T0073S11}, Owner): grey the step, green the way -
               the green of the action, with coverage so that it carries and
               does not shout. A back edge out of a circle dashed. */
            [data-part="edge"] {
                stroke: oklch(from var(--ink-quiet) l c h / 0.5);
                stroke-width: 1px;
            }

            [data-part="path"] {
                stroke: oklch(from var(--link) l c h / 0.7);
                stroke-width: 1.5px;
            }

            line[data-back] { stroke-dasharray: 4 3; }

            /* The separation of the regions: dashed, quieter than the lines,
               across the whole width ({T0073S7}, Owner). */
            [data-part="band"] {
                stroke: oklch(from var(--ink-quiet) l c h / 0.45);
                stroke-width: 1px;
                stroke-dasharray: 2 4;
            }

            /* The quantity on the line: small type with a dark rim, so that it
               stays readable on the line and on the gradient. */
            text {
                fill: var(--ink-bright);
                font-size: var(--text-micro);
                font-weight: 500;
                text-anchor: middle;
                dominant-baseline: middle;
                paint-order: stroke;
                stroke: var(--surface-0);
                stroke-width: 3px;
                stroke-linejoin: round;
            }

            /* The name of the region: vertical in the gutter on the left, beside
               the gold strip, from the upper edge of its band downwards - a
               time axis at the edge. Level in the band it stood where the
               quantities of the last row of the band above stand. */
            /* Level in the upper left corner of the band ({T0073S11}): with three
               columns there is room left of the resource column, and a band
               of one row is too short for a vertical name - »Sumpfland« ran
               into the mountains. */
            text[data-part="band-name"] {
                text-anchor: start;
                dominant-baseline: hanging;
                translate: 0.4rem 0.3rem;
                fill: var(--gold);
                font-size: var(--text-label);
                font-weight: 400;
                letter-spacing: 0.08em;
                text-transform: uppercase;
            }

            /* Two quantities on one ingredient whose lines still almost converge
               in the gap: the left stands left of its line, the right right
               - a third of an em of distance to the line. */
            text[data-anchor="end"]   { text-anchor: end;   translate: -0.35em 0; }
            text[data-anchor="start"] { text-anchor: start; translate:  0.35em 0; }
        }

        /* The grid of the nodes: three columns - resources, stations,
           products ({T0073S11}) -, one row per row. Row and column of a node
           stand on it as custom properties - a typed attr() Safari would not
           carry (platform-features.md). */
        [data-part="nodes"] {
            display: grid;
            grid-template-columns: repeat(3, minmax(0, 1fr));
            grid-template-rows: repeat(var(--rows), var(--row));
        }

        /* The measure of the badge: 2.75 rem, on a narrow area a share of it.
           **On the children of the area, not on it itself**: `cqi` resolves
           against the next ancestor with `container-type`, and that is the
           area - an element cannot read its own container size. Both
           children need it, the nodes and the legend ({T0073S15}): there
           stood `var(--circle)` without a value, and the symbols stayed tiny. */
        [data-part="nodes"],
        [data-part="legend"] {
            --circle: min(2.75rem, 14cqi);
        }

        [data-part="node"] {
            grid-row: var(--r);
            grid-column: var(--c);
            display: grid;
            justify-items: center;
            align-content: center;
            row-gap: 0.2rem;
            min-inline-size: 0;
            padding-inline: 0.15rem;
            color: inherit;
            text-decoration: none;
            /* A node fills its grid cell, visible are only circle and name: the
               pointer so hits the button in the air beside it too, and the
               sheet opened on moving over the area ({T0073S9}, Owner). The
               node itself accepts no pointer, its children do - a click on
               circle or name stays a click on the button. */
            pointer-events: none;

            > * { pointer-events: auto; }

            /* The badge ({T0073S13}, Owner with a picture): the edge grey like
               the lines - gold is carried only by the corner now -, green on
               the target; the ground the colour of the region the item comes
               from ({T0073S11}) - not of the band it stands in. Without a
               region the raised face. */
            [data-part="mark"] {
                --plaque-edge: var(--ink-quiet);
                position: relative;
                display: grid;
                place-items: center;
                inline-size: var(--circle);
                block-size: var(--circle);
                border-radius: 50%;
                border: 2px solid var(--plaque-edge);
                background: color-mix(in oklab, var(--biome, var(--surface-4)) 60%, var(--surface-2));
                box-shadow: 0 0 0 3px oklch(from var(--surface-0) l c h / 0.6);

                [data-type="game-icon"] {
                    inline-size: 72%;
                    block-size: 72%;
                    image-rendering: pixelated;
                }
            }

            b {
                font-size: var(--text-micro);
                font-weight: 500;
                line-height: 1.15;
                text-align: center;
                color: var(--ink-bright);
                overflow-wrap: anywhere;
                max-inline-size: 100%;
                /* Two lines, then dots: a name that does not fit does not run into
                   the next row. */
                display: -webkit-box;
                -webkit-box-orient: vertical;
                -webkit-line-clamp: 2;
                overflow: hidden;
            }
        }

        /* **The golden corner: the sign for »pops up«** ({T0073S13}, Owner
           with a picture). The same language as on the tile of the items
           page ({K0054}) - here in the measure of the badge instead of fixed
           pixels, because it grows with the area. The top right corner
           becomes square for that, the corner lays itself over the grey
           edge. Only a node with a sheet is a <button>, so only it carries it. */
        button[data-part="node"] [data-part="mark"] {
            border-start-end-radius: 0;
        }

        button[data-part="node"] [data-part="mark"]::after {
            content: "";
            position: absolute;
            inset-block-start: -2px;
            inset-inline-end: -2px;
            inline-size: calc(var(--circle) * 0.3);
            block-size: calc(var(--circle) * 0.3);
            border-block-start: calc(var(--circle) * 0.09) solid var(--gold);
            border-inline-end: calc(var(--circle) * 0.09) solid var(--gold);
            pointer-events: none;
        }

        button[data-part="node"]:where(:hover, :focus-visible) [data-part="mark"]::after {
            border-color: oklch(from var(--gold) calc(l + 0.08) c h);
        }

        /* The target: green, the colour of the action - it is what it is about. */
        [data-part="node"][data-role="target"] [data-part="mark"] {
            --plaque-edge: var(--link);
        }

        /* The colour of the region on the node, the same mapping as on the
           bands and in the item list. */
        [data-part="node"][data-biome="meadows"]     { --biome: var(--biome-meadows); }
        [data-part="node"][data-biome="blackforest"] { --biome: var(--biome-forest); }
        [data-part="node"][data-biome="swamp"]       { --biome: var(--biome-swamp); }
        [data-part="node"][data-biome="mountain"]    { --biome: var(--biome-mountain); }
        [data-part="node"][data-biome="plains"]      { --biome: var(--biome-plains); }
        [data-part="node"][data-biome="mistlands"]   { --biome: var(--biome-mistlands); }
        [data-part="node"][data-biome="ashlands"]    { --biome: var(--biome-ashlands); }
        [data-part="node"][data-biome="deepnorth"]   { --biome: var(--biome-deepnorth); }
        [data-part="node"][data-biome="ocean"]       { --biome: var(--biome-ocean); }

        /* An item is a button onto its sheet, without a sheet a link to its
           page ({T0073S8}). The button appears as a node and not as the
           house's button: no face, no edge. */
        button[data-part="node"] {
            appearance: none;
            border: 0;
            background: none;
            font: inherit;
            cursor: pointer;
        }

        /* On pointing the name lifts, and the focus lies on the circle. */
        :is(a, button)[data-part="node"]:where(:hover, :focus-visible) b {
            text-decoration: underline;
            text-underline-offset: 0.15em;
        }

        :is(a, button)[data-part="node"]:focus-visible {
            outline: none;

            [data-part="mark"] { outline: 2px solid var(--link); outline-offset: 2px; }
        }

        /* In the bar something pops now: the golden corner stays. The rule
           against it from {T0073S4} is thereby void; it no longer stands
           above. */

        /* The station: a box instead of a circle, because the game delivers
           no symbol for it. The name stands in it, the level below as a line
           of its own. */
        [data-part="node"][data-role="station"] [data-part="mark"] {
            background: color-mix(in oklab, var(--ink-quiet) 12%, var(--surface-2));
            inline-size: auto;
            min-inline-size: var(--circle);
            max-inline-size: 100%;
            block-size: auto;
            min-block-size: var(--circle);
            /* Narrow in width: with six boxes in one row on the phone 60 px
               remain, and »Werkbank« broke in the middle of the word with
               0.5 rem of padding. */
            padding: 0.3rem 0.3rem;
            border-radius: 0.375rem;
            font-size: var(--text-micro);
            font-weight: 500;
            line-height: 1.15;
            text-align: center;
            color: var(--ink-bright);
            overflow-wrap: anywhere;

            i {
                display: block;
                font-style: normal;
                font-size: var(--text-label);
                color: var(--gold);
                letter-spacing: 0.04em;
            }
        }

        /* The extension: a small dashed circle - it is no thing one takes in
           hand but something that hangs on the station. */
        [data-part="node"][data-role="extension"] [data-part="mark"] {
            background: color-mix(in oklab, var(--ink-quiet) 12%, var(--surface-2));
            inline-size: calc(var(--circle) / 2);
            block-size: calc(var(--circle) / 2);
            border-style: dashed;
            box-shadow: none;
        }

        /* ---- The result ({T0073S14}) ------------------------------------ */

        /* The target's tile under the tree, centred and as wide as on the
           items page: 279 px, in rem so that it grows with the type. On a
           narrow area it takes what is there. */
        [data-part="result"] {
            display: grid;
            justify-items: center;
            justify-content: center;
            row-gap: 0.35rem;
            padding-block-start: 0.5rem;

            [data-type="item-tile"] { inline-size: min(17.4375rem, 100%); }
        }

        [data-part="result-head"] {
            margin: 0;
            color: var(--gold);
            letter-spacing: 0.06em;
            text-transform: uppercase;
            font-size: var(--text-label);
        }

        /* ---- The bill ({T0073}) ------------------------------------- */

        /* Across under the tree, as the owner sketched it: head with the count
           field, below it the entries in a flow, at the bottom the note. A
           border at the top separates it from the tree - it is a slip of its
           own and not the last row of the plan. */
        [data-part="bill"] {
            display: grid;
            row-gap: 0.5rem;
            margin-block-start: 0.75rem;
            padding-block-start: 0.75rem;
            border-block-start: 1px solid oklch(from var(--ink-quiet) l c h / 0.35);
        }

        [data-part="bill-head"] {
            display: flex;
            align-items: baseline;
            justify-content: space-between;
            gap: 1rem;
            flex-wrap: wrap;

            > p {
                margin: 0;
                color: var(--gold);
                letter-spacing: 0.06em;
                text-transform: uppercase;
                font-size: var(--text-label);
            }
        }

        /* The count field. Narrow enough for three digits and not a character
           more - max="999" stands on the element, the width says the same
           once more with the eyes. */
        [data-part="bill-count"] {
            display: flex;
            align-items: center;
            gap: 0.5rem;
            font-size: var(--text-small);
            color: var(--ink-quiet);

            input {
                inline-size: 4.5rem;
                padding: 0.2rem 0.4rem;
                font: inherit;
                font-variant-numeric: tabular-nums;
                color: var(--ink);
                background: var(--surface-2);
                border: 1px solid oklch(from var(--ink-quiet) l c h / 0.35);
                border-radius: 0.25rem;
            }

            input:focus-visible { outline: 2px solid var(--link); outline-offset: 1px; }
        }

        /* The entries in a grid that fills itself: two columns on the phone,
           on the desk as many as the area carries. No auto-flow column - the
           bill grows with the target, and a bronze sword has three entries,
           a longhouse thirty. */
        [data-part="bill-items"] {
            display: grid;
            /* Capped tracks and flush left: with `1fr` a bill of two entries
               pulled them apart over 1300 px, and the number stood a hand's
               width from the name. 20 rem is the width from which a line of
               picture, name and number begins to fall apart. */
            grid-template-columns: repeat(auto-fill, minmax(min(13rem, 100%), 20rem));
            justify-content: start;
            gap: 0.35rem 1rem;
            margin: 0;
            padding: 0;
            list-style: none;

            a {
                display: flex;
                align-items: center;
                gap: 0.5rem;
                padding: 0.25rem 0.4rem;
                border-radius: 0.25rem;
                color: inherit;
                text-decoration: none;
                font-size: var(--text-small);
            }

            a:hover, a:focus-visible { background: var(--surface-2); }

            [data-type="game-icon"] {
                flex: none;
                inline-size: 1.75rem;
                block-size: 1.75rem;
            }
        }

        /* The name yields, the number never: it stands on the right at the
           edge, with tabular figures, so that the column stays calm at
           every piece count. */
        [data-part="bill-name"] {
            flex: 0 1 auto;
            min-inline-size: 0;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }

        /* The leader dots of a menu (Owner: »wie in einem restaurant«). They
           are the reason why the gap between name and number looks like
           intent and not like a typesetting error - and they stretch so
           that the numbers of a column stand one under the other. */
        [data-part="bill-dots"] {
            flex: 1 1 auto;
            min-inline-size: 0.75rem;
            align-self: center;
            block-size: 0;
            border-block-end: 1px dotted oklch(from var(--ink-quiet) l c h / 0.55);
        }

        [data-part="bill-amount"] {
            flex: none;
            color: var(--gold);
            font-variant-numeric: tabular-nums;
        }

        [data-part="bill-hint"] {
            margin: 0;
            font-size: var(--text-small);
            color: var(--ink-quiet);
        }

        /* ---- The legend ({T0073S15}) ------------------------------------ */

        /* Two lines, read column by column (Owner with a sketch): target and
           needed, station and extension, the way and the ingredient - and on
           the right across both lines the badge at full size. `auto-flow:
           column` fills column by column; on a narrow area it may wrap,
           then the pairs stand one under the other. */
        [data-part="legend"] {
            display: grid;
            row-gap: 0.5rem;
            padding-block-start: 0.25rem;
        }

        [data-part="legend-head"] {
            margin: 0;
            color: var(--gold);
            letter-spacing: 0.06em;
            text-transform: uppercase;
            font-size: var(--text-label);
        }

        /* Wrapping columns on the phone, from 44 rem of area width the
           owner's two lines: with `auto-flow: column` alone the legend stood
           at 664 px and pushed the page open (width run, AW20). A container
           query, no window step - the legend answers to the area it stands
           in (AW6). */
        [data-part="legend-items"] {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(min(12rem, 100%), 1fr));
            gap: 0.5rem 2rem;
            font-size: var(--text-small);
            color: var(--ink-quiet);

            > span {
                display: flex;
                align-items: center;
                gap: 0.5rem;
                min-block-size: 1.6rem;
            }

            /* The symbols in the measure of the badge, halved: large enough that
               form and colour can be recognised (Owner), small enough for a
               text line. */
            i {
                flex: none;
                inline-size: calc(var(--circle) / 2);
                block-size: calc(var(--circle) / 2);
                border-radius: 50%;
                border: 2px solid var(--ink-quiet);
                background: var(--surface-2);
            }

            [data-role="target"] i { border-color: var(--link); }
            [data-role="station"] i { border-radius: 0.25em; }
            [data-role="extension"] i {
                border-style: dashed;
                inline-size: calc(var(--circle) / 3);
                block-size: calc(var(--circle) / 3);
            }

            /* The lines: a stroke in their colour and their weight. */
            [data-role="step"] i,
            [data-role="path"] i {
                inline-size: calc(var(--circle) * 0.9);
                block-size: 0;
                border: 0;
                background: none;
                border-block-start: 2px solid oklch(from var(--ink-quiet) l c h / 0.7);
                border-radius: 0;
            }

            [data-role="path"] i {
                border-block-start-width: 3px;
                border-block-start-color: oklch(from var(--link) l c h / 0.9);
            }

            /* **The badge at full size** (Owner: »das vollgrosse Plakette mit
               sichtbaren Farben«): the same ground as on a node - 60 percent
               region colour on the raised face -, only from two regions at a
               sharp edge, so that one reads the change as colour and not as
               one region. It spans both lines. */
            [data-role="biome"] {
                align-items: center;
            }

            [data-role="biome"] i {
                inline-size: var(--circle);
                block-size: var(--circle);
                border-color: var(--ink-quiet);
                border-start-end-radius: 0;
                background: linear-gradient(100deg,
                    color-mix(in oklab, var(--biome-meadows) 60%, var(--surface-2)) 50%,
                    color-mix(in oklab, var(--biome-mistlands) 60%, var(--surface-2)) 50%);
            }
        }

        @container (min-width: 44rem) {
            [data-part="legend-items"] {
                grid-template-columns: none;
                grid-template-rows: repeat(2, auto);
                grid-auto-flow: column;
                grid-auto-columns: max-content;
                justify-content: start;

                /* The badge across both lines, as the last column. */
                [data-role="biome"] { grid-row: span 2; }
            }
        }

        /* From tablet on: the bar on the left, the area on the right, both in
           one row - only the assignment changes ({K0009}). */
        @media (min-width: 35rem) {
            [data-part="board"] { padding: 1.25rem; }
        }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: What holds only on the landing page. */

@layer layouts.landing {
    @scope ([data-layout="landing"]) {
        /* Almost nothing — and that is the point. The slices order themselves
           in the single-column grid of the content area, and each brings
           its face and its inner room. What stood here would be an
           exception, and an exception needs a reason.

           The intro is the only one: on the landing page it carries more
           air than elsewhere, because above it stands the stage bar and
           below it the tiles begin. */
        [data-slice="intro"] {
            padding-block: var(--space-wide);
        }
    }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: The map page is a screen, no page ({T0026S3},
   engine/spec/karte-programm.md §3): head, window, footer in three rows at
   the height of the window; the window takes what remains, and the page does
   not scroll. dvb, because the bars of the phone browsers should grow along
   — block axis, as the rule allows. */

@layer layouts.map {
    @scope ([data-layout="map"]) {
        :scope {
            display: grid;
            grid-template-rows: auto minmax(0, 1fr) auto;
            block-size: 100dvb;
            overflow: hidden;
        }

        /* main and the band in it may become smaller than their content —
           otherwise the picture grows the row and with it the page.

           **Two rows since {T0095}: the stage, then the window.** The stage
           moved with the change from the head into the content (Owner: »der
           hero block ist teil des contents nicht des headers«), and with
           only one row both shared the same — the stage stood, measured, 65
           points tall instead of 159. `auto` gives it what it needs; the
           window still takes what remains. */
        [data-region="content"] {
            display: grid;
            grid-template-rows: auto minmax(0, 1fr);
            min-block-size: 0;
        }

        /* **The row is assigned, not counted.** In full screen the stage drops
           with `display: none`, and a grid child then no longer counts: the
           map band slipped into the first row and got `auto` instead of
           `1fr` — measured 408 points instead of 898, the map squashed and
           black remainder below. With a fixed assignment it stays in row
           two, whether the stage stands there or not; if it is gone, row one
           is zero tall and row two gets everything. */
        [data-region="content"] > [data-type="header-banner"] { grid-row: 1; }
        [data-region="content"] > [data-slice="map"]          { grid-row: 2; }
    }
}

/* On the phone the room belongs to the map: **the footer** drops on this
   one page, and the window grows by its height (Owner, 3 September 2026:
   »in der mobilen Ansicht würde den orangen Header nach oben und den
   Footer nach unten wegschieben, dann fixieren und die Karte im neuen
   größeren Bereich anzeigen«). The narrow head with logo, language and
   menu stays — without it one would get nowhere.

   **The stage stood here with it until {T0100}** and no longer does: since
   the map always has the full extent, it drops at every width, and this
   query would have hidden it a second time. A rule that says the same
   twice is the one where later one of the two becomes wrong.

   CAPTIVE STEP — the one admissible query downwards ({K0149}, AW7). The
   view belongs to this step exclusively and returns above in no form:
   above, the page stands as everywhere.

   The check order has been run through, and both steps before do not hold:

   1  Grid assignment. It would hold if both were rows of their own of the
      rearranging level — they are not. The stage bar is a child of the
      head and no row of the page grid, and a footer without an area lands
      in an implicit track instead of vanishing.
   2  Mobile first with min. Would mean: hide here, restore from 35rem on —
      and know the display type while doing so. The stage bar is a grid,
      the footer too, and display: revert would not know which. Exactly
      this double leadership stood here once within an hour on both values
      and was wrong once each time ({K0099}). */
@layer layouts.map {
    @scope ([data-layout="map"]) {
        /* CAPTIVE STEP, 34.99rem, exclusively for the phone — above, the rule
           returns in no form. The check order and why the two steps before
           do not hold stands in the comment above this block; repeated
           here, because AW7 searches four lines far. */
        @media (max-width: 34.99rem) {
            [data-slice="page-footer"] {
                display: none;
            }
        }
    }
}

/* **The map takes the page — and always** ({T0100}, Owner on
   12 September 2026: »es gibt nur noch die volle ausdehnung version -
   direkt wenn man den link klickt«). It takes everything under the head,
   across the full window width, and title and bar lay themselves on it
   instead of above it.

   **Until then there were two views and a switch between them**
   ({T0028S1}, 5 September 2026). That is dismantled, and the occasion was
   no taste: since the filter bar carries two rows of switches with
   {T0093}, the panel took 423 of 580 points of the map band in the small
   view. At 1000 points of window height the map kept 82, at 900 **zero** —
   and at 640 the enlarge button stood outside the picture too. Whoever
   came on a small screen saw no map and had no way to one.

   The dismantling makes the rules simpler, not more complicated: they
   stood as a branch behind `:has(#map-full:checked)` and are now the
   normal state. One view, one state, no memory.

   **Rearranged, not rebuilt.** There is no second template and no second
   route: the same nodes get another assignment, as {K0009} demands. */
@layer layouts.map {
    @scope ([data-layout="map"]) {
        /* Without the stage, at every width ({T0070}, Owner on 8 September
           2026 with a screenshot: »den orangen Hero-Titel auch bei Tablet
           und Screen ausblenden im Vollbildmodus, analog mobile«). On this
           one page it is not the picture but the room the map needs. The
           narrow head with logo, language and menu stays.

           The captive step above ({K0149}) said the same for the phone and
           was until {T0100} the second of two switches. Now this rule holds
           at every width, and the query there carries only the footer. */
        [data-type="header-banner"] {
            display: none;
        }
        [data-slice="map"] {
            /* One row instead of three: title, bar and window share it, and who
               lies on top, the order in the tree says. */
            grid-template-rows: minmax(0, 1fr);
            row-gap: 0;
            padding-block: 0;
        }

        /* Edge to edge across the full width - the named track of the slice
           is called full ({K0031}); without it the map would still stand in
           the content band. */
        [data-slice="map"] > * {
            grid-column: full;
            grid-row: 1;
        }

        /* **Two columns: the content on the left, the handle on the right**
           (Owner, 5 September 2026). The second column is exactly as wide
           as the handle, and it fills it entirely — in width and height. So
           no column hangs on the content any more, and nothing can push
           them apart.

           The panel itself carries the width: 42rem were the full line of
           twenty characters of pixel font, plus the star behind it, the edge
           and the handle — measured 560 + 30 + 6 + 32 + 36. A longer seed
           wraps instead of shortening ({T0028S3} QA); at a narrow window the
           room holds.

           **Six rem more since {T0093}** (Owner, 11 September 2026: »bitte
           das panel um 100px breite auf dem desktop«). The sum above stays
           the lower bound and is not undercut; the growth goes entirely to
           the switch columns, which had become tight with the second row —
           »Hoher Norden« wrapped. 48 instead of 48.25 rem, because the
           ladder runs in whole steps: four pixels under the named number and
           for that one that one finds again without a calculator.

           Moved by translate, never by width or inset — only that lets the
           compositor run alone (AW18). The way is `handle width minus 100
           percent minus edge`: percent refers to the own size with
           translate, so exactly the content moves out and the handle stays
           at the edge. */
        [data-slice="map"] > [data-part="panel"] {
            --tab: 2.25rem;
            display: grid;
            /* Two columns, **one** row. The handle no longer spans anything - it
               has its cell and fills it. Exactly that was the break:
               `grid-row: 1 / -1` counts explicit lines, and as soon as the
               rows became implicit, it ended after the first. */
            /* Three columns since {T0106}: content, strip, handle. The strip is
               zero wide open and 6.25 rem collapsed - the owner's hundred
               pixels at sixteen pixels of base type, in rem, because AW16
               knows no px. The column changes on folding, not animated: only
               translate is animated (AW18). */
            --quick: 0rem;
            grid-template-columns: minmax(0, 1fr) var(--quick) var(--tab);
            place-self: start start;
            z-index: 3;
            /* `100%` and not `100vi` ({T0100}, AW16). The viewport unit computes
               without the scrollbar: at 430 points of window width the panel
               came to 421 and the slice ended at 415 — six points out,
               measured. Percent refers to the slice, which has already
               subtracted the bar. Exactly the case AW16 describes with
               »laufen deshalb am Schreibtisch alle gleich ueber«; the line
               stood here since {T0028} and was noticed only once the panel
               floats at every width. */
            /* The drawer of {T2001S6} is the picture's in 24 rem, and as tall
               as the map band allows - the categories scroll inside it, the
               page never. One column of groups at every width, not the
               picture's two stacks: the owner saw them locally on 28
               September 2026 and sent them back - »bitte doch nur einspaltig,
               also alles untereinander« - because long names read better in
               one column and a mark squeezed by a wrapping name shrank. The
               stacks stand one under the other in the order the picture has. */
            inline-size: min(24rem, calc(100% - 2 * var(--space-close)));
            max-block-size: calc(100% - 2 * var(--space-close));
            grid-template-rows: minmax(0, 1fr);
            --columns: 1;
            margin-block-start: var(--space-close);
            margin-inline-start: var(--space-close);
            transition: translate 280ms ease;
            position: relative;
            /* **Eighty-five percent, not fifty** ({T0100}). The panel floats
               above the map, and that is light and patterned: at half
               opacity the golden »ORTE« measures 2.14 to 1 and the note
               line 2.52 — needed are 4.5 (AW3). That was already so in the
               old full-screen mode and was not noticed because nobody had to
               see it; since it is the only one, it holds for everybody.

               The map stays visible behind — it is a pane, no lid —, but it
               no longer carries the text along. */
            background: oklch(8% 0.008 260 / 0.85);
            box-shadow:
                inset  2px  2px 0 oklch(33% 0.014 var(--hue-surface)),
                inset -2px -2px 0 oklch(12% 0.010 var(--hue-surface));
        }

        /* The golden corner, bottom right — the same language as on the item
           tile, only at the other corner ({K0054}: at the third caller an
           element is made of it). It hangs on the container and moves with it. */
        [data-slice="map"] > [data-part="panel"]::after {
            content: "";
            position: absolute;
            inset-block-end: 0;
            inset-inline-end: 0;
            inline-size: 15px;
            block-size: 15px;
            border-block-end: 5px solid var(--gold);
            border-inline-end: 5px solid var(--gold);
            pointer-events: none;
        }

        /* **The content is a box with its own grid**: one column called
           `content`. In the small view its children are grid children of
           the band and stand in its track `content`; here they stand in its
           own, which is called the same. A line name that does not exist in
           the grid would otherwise be answered with an implicit track -
           measured `76px 36px 0px 560px`, and the panel fell apart. Every
           box has its grid, and every rule finds its track. */
        [data-part="panel"] > [data-part="content"] {
            grid-column: 1;
            min-inline-size: 0;
            min-block-size: 0;
            display: grid;
            grid-template-columns: [content-start] minmax(0, 1fr) [content-end];
            /* One row since {T2001S7}: the title row left for the seed bar at
               the top right of the map, and the drawer begins with its own
               heading. */
            grid-template-rows: minmax(0, 1fr);
        }

        /* The map options ({T2001S6}): heading, seed form and the categories
           in three rows, the last taking what remains and scrolling on its
           own. The rows above are content-sized; nothing here has a height
           of its own, the panel's is the band's. */
        [data-part="content"] > [data-type="map-options"] {
            min-block-size: 0;
            display: grid;
            grid-template-rows: auto auto minmax(0, 1fr);
            padding: var(--space-close);
        }
        [data-type="map-options"] > [data-part="options"] {
            min-block-size: 0;
            display: grid;
            /* The orbit's line above the categories since {T2001S9}. */
            grid-template-rows: auto minmax(0, 1fr);
            /* Air between the seed form and the first category (owner, 28
               September 2026: »einen Sicherheitsabstand von 8px, abgeleitet
               von unserer goldener Schnitts-Skala«): the close step, 0.618 of
               the base - without it the first caption's golden line sat on
               the field's edge. */
            margin-block-start: var(--space-close);
        }
/* The handle has its cell and fills it - `stretch` is the resting
           value in the grid, it needs neither height nor span. */
        [data-part="panel"] > [data-type="map-tab"] {
            grid-column: 3;
            align-self: stretch;
            display: grid;
            place-items: center;
            position: relative;
            cursor: pointer;
            background: oklch(20% 0.008 260 / 0.62);
            box-shadow:
                inset  2px  2px 0 oklch(38% 0.014 var(--hue-surface)),
                inset -2px -2px 0 oklch(14% 0.010 var(--hue-surface));
        }

        [data-part="panel"] > [data-type="map-tab"]:hover {
            background: color-mix(in oklch, var(--gold) 10%, oklch(20% 0.008 260 / 0.62));
            box-shadow:
                inset  2px  2px 0 oklch(14% 0.010 var(--hue-surface)),
                inset -2px -2px 0 oklch(38% 0.014 var(--hue-surface));
        }

        [data-type="map-tab"] input {
            grid-area: 1 / 1;
            inline-size: 100%;
            block-size: 100%;
            margin: 0;
            appearance: none;
            opacity: 0;
            cursor: pointer;
        }

        [data-type="map-tab"]:has(:focus-visible) {
            outline: 2px solid var(--link);
            outline-offset: -2px;
        }

        /* **A chevron in place of the word** ({T2001S6}, owner, 28 September
           2026: »die Leiste muss noch diesen Pfeil anstelle des Textes
           bekommen«). The word stays as the switch's name for a screen
           reader and leaves the eye: clipped to nothing, out of the flow. */
        [data-type="map-tab"] [data-part="word"] {
            position: absolute;
            inline-size: 1px;
            block-size: 1px;
            overflow: hidden;
            clip-path: inset(50%);
            white-space: nowrap;
        }
        /* The chevron drawn, as the categories draw theirs: two golden edges
           of a turned square, no glyph. It says where the drawer goes on a
           click - left while open, right while closed (owner, same day, the
           recommendation taken); the nudge along the axis sets the turned
           square's tip on the centre. */
        [data-type="map-tab"]::after {
            content: "";
            grid-area: 1 / 1;
            inline-size: 0.75rem;
            block-size: 0.75rem;
            border-inline-end: 3px solid var(--gold);
            border-block-end: 3px solid var(--gold);
            transform: translateX(25%) rotate(135deg);
            transition: transform 90ms steps(3);
            pointer-events: none;
        }
        [data-slice="map"][data-panel="closed"] [data-type="map-tab"]::after {
            transform: translateX(-25%) rotate(-45deg);
        }
        @media (prefers-reduced-motion: reduce) {
            [data-type="map-tab"]::after { transition: none; }
        }

        /* **The switches are a wrapping flex row** (Owner, 5 September 2026,
           recommended for the second time and this time measured on the
           phone too). `auto-fit` with a ten rem minimum column left one
           column there and eight rows with an empty edge.

           Every switch has the same base of eight rem and may grow: what
           fits into one row shares it equally, and because eight switches
           order themselves in two per row on the phone and four on the
           desk, they stand in columns one above the other in both cases. No
           query, no column count per step. */
        /* Since {T0093} the <fieldset> orders the switches and no longer the
           form: `> *` would otherwise have hit the fieldset itself and the
           switches in it not at all any more. */
        [data-part="panel"] [data-type="checkbox"] {
            color: var(--ink-bright);
            text-shadow: 1px 1px 0 oklch(0% 0 0 / 0.9), -1px 1px 0 oklch(0% 0 0 / 0.9);
        }


        [data-part="panel"] [data-type="seed-form"] {
            background: none;
            box-shadow: none;
            padding-inline: 0;
            padding-block-start: 0;
        }

        /* The strip ({T0106}): not there open, the second column collapsed,
           two buttons one above the other, beginning at the top. The
           background is the panel's that carries it - it needs none of its own. */
        [data-part="panel"] > [data-part="quick"] {
            grid-column: 2;
            display: none;
            align-content: start;
            justify-items: center;
            /* Measured from the owner's design: edge and spacing about ten pixels
               each, the buttons fill the rest of the strip. */
            gap: var(--space-close);
            padding: var(--space-close);
            /* Half as wide as the strip minus edge, square, and only as tall as
               the symbol needs ({T0108}, Owner on 14 September 2026: »halb
               so breit, auf die fuer den text notwendige hoehe, top left des
               panels bleibt gleich«). The rows are as tall as their content
               and begin top left - the first version let two buttons share
               the whole height. */
            /* The button width is set, the strip follows it (below in the folding
               rule), not the other way round: 2.5 rem, half as wide as the
               first version, and the strip is exactly button plus edge
               (Owner, 14 September 2026: »das panel wird so breit wie die
               buttons ihn brauchen«). */
            --quick-size: 2.5rem;
            /* One button since {T2001S6}: the random roll, square, and the
               strip exactly as tall as it plus the edge - that is the closed
               panel's height, and the handle beside it stretches to it. The
               two rows that shared the height of the tab's word went with
               the word and the star. */
            grid-template-rows: auto;
            justify-items: start;
        }
        /* Closed: the container moves out until strip and handle remain
           ({T0106}; until then only the handle). The content becomes
           invisible and unreachable while doing so - `visibility` takes it
           out of the keyboard order without changing the grid -, and only
           once the movement has ended, so that it does not vanish before
           the panel. On opening at once. The star of the title row gives up
           its anchor name, so that the sheet hangs on the visible star of
           the strip and not on one that has moved out. */
        /* No movement at the first picture ({T0106}): until the script sets
           data-ready after the first picture, there is no transition - the
           state from address or storage is set, not moved. After that every
           click animates as before.

           **The state stands on the map section, not in the checkbox**
           ({T0145}). Until then these rules read `:has(#map-panel:checked)`
           from the layout root element, the body. A :has() rule on the body
           makes every insertion and every removal in the document a
           re-evaluation of the anchor, and its invalidation set carries the
           features of the subjects - [data-type], [data-part], [data-slice]
           -, so every element with data-type: 8 900 divs and 8 500 spans
           per pass, at every marker that comes in while panning. The server
           renders data-panel="closed" like the checkbox, the script mirrors
           every click (setPanel), data-ready stands beside it. */
        [data-slice="map"]:not([data-ready]) > [data-part="panel"] {
            transition: none;
        }
        [data-slice="map"][data-panel="closed"] > [data-part="panel"] {
            /* As wide as a button plus edge on both sides ({T0108}); the hundred
               pixels of the first version are history with that. */
            --quick: calc(2.5rem + 2 * var(--space-close));
            translate: calc(var(--tab) + var(--quick) - 100% - var(--space-close)) 0;
        }
        /* Collapsed, the content is not only invisible but out of the flow
           ({T0108}, Owner on 14 September 2026: »das panel und auch die
           schaltflaeche im minimierten zustand nur auf die notwendige hoehe
           fuer den buttontext reduzieren«): with `visibility` it kept the
           whole height, and the strip stood empty under the buttons.
           Without it the strip with its one button sets the height since
           {T2001S6}, the tab with its word before. The change is
           hard, not moved - `display` knows no transition, and the allow
           list no soft one for it; the panel first gets short on folding,
           then moves out. */
        [data-slice="map"][data-panel="closed"] [data-part="panel"] > [data-part="content"] {
            display: none;
        }
        [data-slice="map"][data-panel="closed"] [data-part="panel"] > [data-part="quick"] {
            display: grid;
        }

        @media (prefers-reduced-motion: reduce) {
            [data-slice="map"] > [data-part="panel"] { transition-duration: 1ms; }
        }
        /* The drawer on the phone ({T2001S6}, owner, 28 September 2026): it
           fills the screen but for a guard of one step of the scale, and it
           starts folded - the script says so where the address does not.
           After the panel's own rules, because the guard replaces their
           margins. */
        @media (max-width: 34.99rem) {
            [data-slice="map"] > [data-part="panel"] {
                inline-size: calc(100% - 2 * var(--space-tight));
                max-block-size: calc(100% - 2 * var(--space-tight));
                margin: var(--space-tight);
            }
        }

        /* The footer shrinks to one line ({T0028S2}, Owner on 5 September
           2026): the name on the left, the copyright centred, the game list
           on the right. The trademark note and the technology line do not
           stand here — the owner struck both through in the screenshot.

           The same nodes, another assignment: no second footer and no copy
           of a sentence. Two versions of the same text would be two truths,
           and the workbench names once cost that ({K0102}).

           The rule stands in the layer layouts and so overrides the tablet
           rule of the footer band in slices — the order stands in style.css
           and is the reason why no specificity has to be helped along here.

           **The donate button stands in the strip too** ({T0134}), right
           after the name and so low that the strip does not grow: the map
           is the page on which the server visibly works, and exactly here
           the button should not be missing. The height is set by the
           context, the form the button computes itself from it. Its column
           is the fourth; `align-items` sets the texts to its middle instead
           of the upper edge. */
        [data-slice="page-footer"] {
            --donate-height: 1.625rem;
            --donate-font: 0.75rem;
            padding-block: var(--space-tight);
            row-gap: 0;
            align-items: center;
            border-block-start: none;
            grid-template-areas: none;
            grid-template-columns:
                [full-start] var(--edge)
                [content-start] auto var(--space-close) auto var(--space-close)
                1fr var(--space-close) auto
                [content-end] var(--edge)
                [full-end];
        }

        /* Up to screen the line carries only the button and the copyright.
           The three texts together are 95 characters; at 560 pixels they
           wrap, and the one line becomes four — measured 142 instead of 34
           pixels. Centred stands what stands on every page; name and game
           list are added as soon as there is room. Until then the button
           stands in front, where otherwise the name stands. */
        [data-slice="page-footer"] > [data-type="prose"],
        [data-slice="page-footer"] > [data-type="page-footer-nav"] {
            display: none;
        }

        [data-slice="page-footer"] > [data-type="donate-button"] {
            grid-column: 2;
            grid-row: 1;
        }

        [data-slice="page-footer"] > [data-type="footer-legal"] {
            grid-column: 6;
            grid-row: 1;
            justify-self: center;
            margin-block-start: 0;
            margin-inline: 0;
        }

        /* In the map view the middle carries only the notice ({T0077}, Owner
           on 9 September 2026). Trademark note and build line are sentences
           and no line; they stand on every other page and are not missing
           here because the map fills the whole window and the footer is a
           strip beneath. They lie one level deeper than the rule above
           reaches — hence here once more. */
        [data-slice="page-footer"] [data-type="footer-legal"] [data-type="prose"] {
            display: none;
        }

        @media (min-width: 60rem) {
            [data-slice="page-footer"] > [data-type="prose"],
            [data-slice="page-footer"] > [data-type="page-footer-nav"] {
                display: block;
            }

            [data-slice="page-footer"] > [data-type="prose"] {
                grid-column: 2;
                grid-row: 1;
                justify-self: start;
            }

            [data-slice="page-footer"] > [data-type="donate-button"] {
                grid-column: 4;
            }

            [data-slice="page-footer"] > [data-type="page-footer-nav"] {
                grid-column: 8;
                grid-row: 1;
                justify-self: end;
            }
        }

        /* What does not stand here is gone for the screen reader too: the
           visible and the audible agree (AW3). On every other page and in
           the small view both sentences stand unchanged. */
        [data-slice="page-footer"] [data-type="footer-legal"] [data-type="prose"] {
            display: none;
        }
        }
}

/* Copyright 2026 Vivian Voss. Code by Vivian Voss. Licensed under the BSD-3-Clause.
   SPDX-License-Identifier: BSD-3-Clause
   Scope: the base frame — three regions above one another, the content in one column. */

@layer templates.vga-page {
    @scope ([data-template="vga-page"]) {
        :scope {
            display: grid;
            grid-template-areas: "header" "content" "footer";
            grid-template-rows: auto 1fr auto;
            min-block-size: 100svb;
        }

        /* Assigned through the element, not through a class: the region is a
           property of the frame, and the slice that fills it should not have to
           know of it. A second template arranges the same slices differently by
           changing nothing but the areas. */
        > header { grid-area: header; }
        > main   { grid-area: content; }
        > footer { grid-area: footer; }

        /* The content area is a grid with one column; every row in it is a slice
           and as wide as the page \({K0031}\). No gap: slices butt against each
           other, because their surfaces are meant to alternate — the distance
           arises inside the slice as its own inner space, not between two as a
           gap. A gap would let the ground of the page show through and make
           three bands out of two. */
        > main {
            display: grid;
            grid-template-columns: 1fr;

            /* **The slices stand at the top, they do not fill** ({T0095}). A
               grid without a row definition lays out implicit `auto` rows, and
               `align-content` stands at `normal` there — which here means
               `stretch`: is the content shorter than the page, the rest is
               spread over all rows. `min-block-size: 100svb` on the frame sees
               to it that this rest almost always exists.

               It showed when the stage moved from the head into the content:
               on the building plan page, which carries little, it stood 188
               points high, on the item page 159 — the same widget, the same
               values, only more rest to spread. A building block whose height
               depends on how much stands *beside* it is none. */
            align-content: start;
        }

    }
}

