/**
 * Frontend-Styling (Katalog: Header, Sidebar, Cards-Grid, Tool-Detailseite, Footer, …) — bis
 * 2026-08-12 inline in the <style> block of templates/layout.php, extracted now so that the "tools"
 * module in the admin backend (public/admin-tools.php) can reuse exactly the same look through a <link>
 * rather than duplicating CSS (the user's wish: "use the templates and logic that are there for it").
 * UNCHANGED in substance from the earlier inline block -- a pure extraction, no reformatting. See
 * docs/wiki/architecture/admin-tools-module.md.
 */
:root {
            --bg-dark: #090d16;
            --bg-card: rgba(22, 30, 46, 0.7);
            --bg-card-hover: rgba(30, 41, 59, 0.85);
            --bg-input: rgba(15, 23, 42, 0.8);
            --border-color: rgba(255, 255, 255, 0.08);
            --border-highlight: rgba(99, 102, 241, 0.4);
            --text-main: #f1f5f9;
            --text-muted: #94a3b8;
            --text-dim: #64748b;
            --accent-indigo: #6366f1;
            --accent-cyan: #06b6d4;
            --accent-emerald: #10b981;
            --accent-pink: #ec4899;
            --accent-amber: #f59e0b;
            --accent-purple: #a855f7;
            --shadow-glow: 0 0 25px rgba(99, 102, 241, 0.15);
            --glass-blur: blur(16px);
            --radius-lg: 16px;
            --radius-md: 10px;
            --radius-sm: 6px;
        }

    /* Scoped on .catalog-page (rather than a global "*" or "body"), so that this stylesheet can be loaded
       beside Tabler in the admin backend too (the tools module, see admin-tools.php) without overwriting
       its own body styling (admin-custom.css). */
        .catalog-page, .catalog-page * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
        }

        body.catalog-page {
            font-family: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, sans-serif;
            background-color: var(--bg-dark);
            background-image:
                radial-gradient(at 15% 15%, rgba(99, 102, 241, 0.12) 0px, transparent 50%),
                radial-gradient(at 85% 85%, rgba(6, 182, 212, 0.1) 0px, transparent 50%),
                radial-gradient(at 50% 50%, rgba(168, 85, 247, 0.05) 0px, transparent 70%);
            background-attachment: fixed;
            color: var(--text-main);
            min-height: 100vh;
            line-height: 1.6;
        }

        /* Layout Container */
        .app-header {
            position: sticky;
            top: 0;
            z-index: 100;
            background: rgba(9, 13, 22, 0.85);
            backdrop-filter: var(--glass-blur);
            border-bottom: 1px solid var(--border-color);
    /* Horizontal padding belongs INSIDE the child, not here (2026-08-16).
       Before: `padding: 1rem 2rem` out here, and `.header-content` in it limited to 1440px -- so the
       header was allowed to become 1440px wide. `.app-container` below has the same 1440px but
       `padding: 0 2rem` INSIDE it and is thereby only 1376px wide. The result: the content stood 32px
       further in on every page than the
               elements in the header above it. Now both use the same model. */
            padding: 1rem 0;
        }

        .header-content {
            max-width: 1440px;
            margin: 0 auto;
            padding: 0 2rem;
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: 1.5rem;
            flex-wrap: wrap;
        }

        .brand-logo {
            display: flex;
            align-items: center;
            gap: 0.75rem;
            text-decoration: none;
            color: var(--text-main);
        }

        .brand-icon {
            width: 42px;
            height: 42px;
            border-radius: var(--radius-md);
            background: linear-gradient(135deg, var(--accent-indigo), var(--accent-cyan));
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 1.4rem;
            box-shadow: 0 4px 14px rgba(99, 102, 241, 0.4);
        }

        .brand-text h1 {
            font-size: 1.25rem;
            font-weight: 800;
            letter-spacing: -0.02em;
            background: linear-gradient(to right, #ffffff, #cbd5e1);
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
        }

        .brand-text p {
            font-size: 0.78rem;
            color: var(--text-muted);
        }

        /* Header Stats */
        .header-stats {
            display: flex;
            align-items: center;
            gap: 1rem;
        }

        .stat-badge {
            background: var(--bg-card);
            border: 1px solid var(--border-color);
            padding: 0.4rem 0.85rem;
            border-radius: 30px;
            font-size: 0.82rem;
            display: flex;
            align-items: center;
            gap: 0.5rem;
            color: var(--text-muted);
        }

        .stat-badge strong {
            color: var(--text-main);
            font-weight: 700;
        }

        .stat-dot {
            width: 8px;
            height: 8px;
            border-radius: 50%;
            background: var(--accent-emerald);
            box-shadow: 0 0 10px var(--accent-emerald);
        }

        /* Main Workspace Layout */
        .app-container {
            max-width: 1440px;
            margin: 2rem auto;
            padding: 0 2rem;
            display: grid;
            grid-template-columns: 280px 1fr;
            gap: 2rem;
        }

        @media (max-width: 1024px) {
            .app-container {
                grid-template-columns: 1fr;
            }
        }

        /* Sidebar Navigation */
        .sidebar {
            position: sticky;
            top: 90px;
            height: calc(100vh - 120px);
            overflow-y: auto;
            display: flex;
            flex-direction: column;
            gap: 1.5rem;
            padding-right: 0.5rem;
        }

        .sidebar-section-title {
            font-size: 0.75rem;
            text-transform: uppercase;
            letter-spacing: 0.08em;
            color: var(--text-dim);
            font-weight: 700;
            margin-bottom: 0.5rem;
            padding-left: 0.5rem;
        }

        .nav-list {
            list-style: none;
            display: flex;
            flex-direction: column;
            gap: 0.35rem;
        }

        .nav-item a {
            display: flex;
            align-items: center;
            gap: 0.75rem;
            padding: 0.65rem 0.85rem;
            border-radius: var(--radius-md);
            color: var(--text-muted);
            text-decoration: none;
            font-size: 0.88rem;
            font-weight: 500;
            transition: all 0.2s ease;
            border: 1px solid transparent;
        }

        .nav-item a:hover {
            color: var(--text-main);
            background: rgba(255, 255, 255, 0.04);
            border-color: rgba(255, 255, 255, 0.05);
        }

        .nav-item.active a {
            color: #ffffff;
            background: linear-gradient(90deg, rgba(99, 102, 241, 0.2), rgba(6, 182, 212, 0.1));
            border-color: var(--border-highlight);
            font-weight: 600;
        }

        .nav-emoji {
            font-size: 1.1rem;
        }

        .nav-count {
            margin-left: auto;
            font-size: 0.75rem;
            background: rgba(255, 255, 255, 0.06);
            padding: 0.15rem 0.5rem;
            border-radius: 12px;
            color: var(--text-dim);
        }

        /* Filter Controls */
        /* No shadow any more, the border stays, a grey ground (2026-09-01, the user's
           instruction). The shadow made the bar float above the list; it is not a window though,
           but a field on the same plane. The ground is deliberately GREY and not the card colour:
           that way one sees at a glance that this is where one SETS things rather than reads them.
           The light value stands further down with the other light values. */
        .toolbar {
            background: rgba(148, 163, 184, 0.10);
            backdrop-filter: var(--glass-blur);
            border: 1px solid var(--border-color);
            border-radius: var(--radius-lg);
            padding: 1.25rem;
            margin-bottom: 2rem;
            display: flex;
            flex-wrap: wrap;
            gap: 1rem;
            align-items: center;
            justify-content: space-between;
        }

        .search-box {
            position: relative;
            flex: 1;
            min-width: 280px;
        }

        .search-icon {
            position: absolute;
            left: 1rem;
            top: 50%;
            transform: translateY(-50%);
            color: var(--text-dim);
            pointer-events: none;
        }

        .search-input {
            width: 100%;
            background: var(--bg-input);
            border: 1px solid var(--border-color);
            border-radius: var(--radius-md);
            padding: 0.75rem 1rem 0.75rem 2.75rem;
            color: var(--text-main);
            font-family: inherit;
            font-size: 0.9rem;
            outline: none;
            transition: all 0.2s ease;
        }

        .search-input:focus {
            border-color: var(--accent-indigo);
            box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.2);
        }

/* ── One label column for every filter row (2026-09-05, the user's wish) ──────────────────────
   The rows read like a list with an index: every group title stands in a column of one width on the
   left, and the buttons of every row begin at the same x beside it and wrap within their own
   container -- not under the title. The price row is built the same way since the same day. */
        .filter-group {
            display: flex;
            align-items: flex-start;
            gap: 0.5rem;
            flex-wrap: nowrap;
            width: 100%;
        }

        .filter-group-items {
            display: flex;
            align-items: center;
            gap: 0.5rem;
            flex-wrap: wrap;
            min-width: 0;
            flex: 1 1 auto;
        }

        .filter-btn {
            background: rgba(255, 255, 255, 0.04);
            border: 1px solid var(--border-color);
            color: var(--text-muted);
            padding: 0.55rem 0.9rem;
            border-radius: var(--radius-md);
            font-size: 0.82rem;
            font-weight: 500;
            cursor: pointer;
            transition: all 0.2s ease;
            display: flex;
            align-items: center;
            gap: 0.4rem;
        }

        .filter-btn:hover {
            background: rgba(255, 255, 255, 0.08);
            color: var(--text-main);
        }

        /* ── Umschalt-Zustand (2026-08-16) ──────────────────────────────────────────────────────
   Since the "all" button fell away, the filters are real switches: several may be on at once.
   `aria-pressed` carries the same state for screen readers -- without it three buttons sound identical
   without giving away which ones take hold.

   OFF means here: only the type carries colour, the surface stays empty (the user's wish).
   ON means: the whole button is coloured. */
        .filter-btn.active {
            background: var(--accent-indigo);
            color: #ffffff;
            border-color: var(--accent-indigo);
            box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);
        }

/* The price buttons carried a mid-grey filled state of their own from 2026-09-01 (the coloured
   dots had come out of the texts with db/111). Since 2026-09-05 they are `.filter-btn-tag` like the
   facets, and that grey is the ticked state of EVERY filter button -- see `.filter-btn-tag.active`. */

/* ── The facet buttons look like the tags in the cards (2026-09-01) ────────────────────────────
   The instruction, VERBATIM and therefore left in German -- a quotation is evidence:

   *"stelle die tags genau gleich dar wie in den tool cards, also viel weniger padding, kleinere
   text, dunklerer hintergrund (einfach derselbe style)"*

   The values are word for word the ones from `.format-tag`, so that the same facet looks the same
   in both places. Whoever changes the one style has to bring the other along; that is why the two
   are named next to each other here. */
        .filter-btn-tag {
            background: rgba(0, 0, 0, 0.25);
            border: 1px solid rgba(255, 255, 255, 0.05);
            border-radius: 4px;
            color: var(--text-dim);
            font-family: 'JetBrains Mono', monospace;
            font-size: 0.72rem;
            font-weight: 400;
            padding: 0.1rem 0.3rem;
        }

        .filter-btn-tag:hover {
            background: rgba(0, 0, 0, 0.35);
            color: var(--text-muted);
        }

        /* Switched on: the filled mid grey the price buttons had (2026-09-05, the user's wish --
           "so wie bei den drei selektierten tags"). Filled means on, empty means off; the colour
           does not carry the information on its own. */
        .filter-btn-tag.active {
            background: #475569;   /* slate-600: dark enough that white text has contrast (2026-09-05) */
            border-color: #475569;
            box-shadow: none;
            color: #ffffff;
        }

/* ── The tag filter per category (2026-08-24, db/083) ──────────────────────────────
   Which groups stand here is decided by the category editor. The group title stands BEFORE the buttons
   and not above them: a row of its own per group would make the bar twice as tall as the list of hits
   with four groups. */
        .filter-group-title {
            color: var(--text-muted);
            font-size: 0.78rem;
            font-weight: 600;
            letter-spacing: 0.02em;
            flex: 0 0 7.5rem;   /* the index column: one width for every row, so the buttons line up */
            padding-top: 0.15rem;
            margin-right: 0.15rem;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
        }

        /* Narrow: the column would eat the row, so the title takes a row of its own above the buttons. */
        @media (max-width: 560px) {
            .filter-group {
                flex-wrap: wrap;
            }
            .filter-group-title {
                flex-basis: 100%;
                padding-top: 0;
            }
        }

/* The number of hits answers the question one otherwise answers by clicking. Muted, so that it does not
   overrule the label. */
        .filter-results {
            opacity: 0.55;
            font-size: 0.72em;
            font-variant-numeric: tabular-nums;
            margin-left: 0.3rem;
        }

        .filter-btn.active .filter-results {
            opacity: 0.8;
        }

        .view-toggle {
            display: flex;
            background: rgba(0, 0, 0, 0.3);
            padding: 3px;
            border-radius: var(--radius-md);
            border: 1px solid var(--border-color);
        }

        .view-btn {
            background: transparent;
            border: none;
            color: var(--text-dim);
            padding: 0.45rem 0.75rem;
            border-radius: var(--radius-sm);
            cursor: pointer;
            transition: all 0.2s ease;
        }

        .view-btn.active {
            background: var(--bg-card-hover);
            color: var(--text-main);
        }

        /* Category Section */
        .category-section {
            margin-bottom: 3rem;
            scroll-margin-top: 100px;
        }

        .category-header {
            display: flex;
            align-items: center;
            gap: 0.75rem;
            margin-bottom: 1.5rem;
            padding-bottom: 0.75rem;
            border-bottom: 1px solid var(--border-color);
        }

        .category-header h2 {
            font-size: 1.4rem;
            font-weight: 700;
            letter-spacing: -0.01em;
        }

        .category-header .cat-emoji {
            font-size: 1.6rem;
        }

        /* Subsections & Cards Grid */
        .subsection-title {
            font-size: 1rem;
            font-weight: 600;
            color: var(--accent-cyan);
            margin: 1.5rem 0 1rem 0;
            display: flex;
            align-items: center;
            gap: 0.5rem;
        }

        .cards-grid {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
            gap: 1.25rem;
        }

        .tool-card {
            background: var(--bg-card);
            backdrop-filter: var(--glass-blur);
            border: 1px solid var(--border-color);
            border-radius: var(--radius-lg);
            padding: 1.25rem;
            display: flex;
            flex-direction: column;
            justify-content: space-between;
            gap: 0.75rem;
            transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
            position: relative;
            overflow: hidden;
        }

        .tool-card::before {
            content: '';
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            height: 2px;
            background: linear-gradient(90deg, transparent, var(--border-highlight), transparent);
            opacity: 0;
            transition: opacity 0.3s ease;
        }

        .tool-card:hover {
            transform: translateY(-4px);
            background: var(--bg-card-hover);
            border-color: rgba(99, 102, 241, 0.3);
            box-shadow: 0 12px 30px rgba(0, 0, 0, 0.3), var(--shadow-glow);
        }

        .tool-card:hover::before {
            opacity: 1;
        }

        /* ── Kartenkopf (2026-08-16) ────────────────────────────────────────────────────────────
   Up to here the favicon, the title AND the price badges lay in one single flex row (`.card-top`). The
   title thereby got only what was left beside the badges and wrapped already at
           mittleren Namen um, waehrend rechts Platz frei blieb.

   Now two columns: the favicon fixed on the left, and beside it a column that takes the title and the
   badges ONE UNDER THE OTHER. The title thereby has the full remaining width, and
   `align-items: flex-start` sets it flush at the top with the icon rather than centred beside it. */
        .tool-card-body {
            display: flex;
            flex-direction: column;
            gap: 0.6rem;
        }

        .tool-card-head {
            display: flex;
            align-items: flex-start;
            gap: 0.75rem;
        }

/* min-width: 0 is compulsory: flex children otherwise do not shrink below their content width, and a long
   title would push the card open sideways. */
        .tool-head-text {
            display: flex;
            flex-direction: column;
            gap: 0.4rem;
            min-width: 0;
            flex: 1;
        }

        .tool-title {
            font-size: 1.05rem;
            font-weight: 700;
            color: #ffffff;
/* Tighter than the browser default (about 1.5): a two-line title otherwise looked like two separate lines
   rather than like one name. */
            line-height: 1.25;
        }

/* The visitor badges flush right under the title -- as in the user's sketch. Wrapping is allowed, so that
   nothing is cut off with three price models. */
        .tool-badges {
            display: flex;
            flex-wrap: wrap;
            justify-content: flex-end;
            gap: 0.3rem;
        }

/* The favicon before the tool name -- in the card AND the table view, in the frontend as in the admin
           (gemeinsame Partial templates/partials/tool-grid.php).

   flex-shrink: 0 is not optional: without it flexbox squeezes the icon into a line with long tool names
   rather than letting the text wrap.

   The size sits in ONE variable, because it has to be consistent at three places: here, at the
   placeholder and in the width/height attributes of the partial. If the values stood there one by one,
   one of them would drift sooner or later.

   64px rather than the 20px at the start (2026-08-14, the user's wish). The store delivers that without a
   loss of quality: ToolImageStore::FAVICON_MAX_DIMENSION is 128, so a 64 box is still served sharply even
   on retina displays. */
        :root {
            --tool-favicon-size: 64px;
        }
        .tool-favicon {
            width: var(--tool-favicon-size);
            height: var(--tool-favicon-size);
            flex-shrink: 0;
            border-radius: 8px;
            object-fit: contain;
/* Many favicons are made for light browser tab bars and are barely recognisable on a dark ground. The
   light background under them makes them readable independently of that -- the competitor's directory
   uses the same means. */
            background: rgba(255, 255, 255, 0.92);
            padding: 4px;
        }

/* A placeholder for the roughly 15 tools without a favicon: the same dimensions, so that the titles do
   not stand differently far to the left and the list looks restless. */
        .tool-favicon-empty {
            display: inline-block;
            width: var(--tool-favicon-size);
            height: var(--tool-favicon-size);
            flex-shrink: 0;
            border-radius: 8px;
            background: rgba(255, 255, 255, 0.06);
            border: 1px solid rgba(255, 255, 255, 0.1);
        }

/* The table view: the same alignment, but inline rather than as a block. */
        .tool-title-cell {
            display: inline-flex;
            align-items: center;
            gap: 0.5rem;
        }

/* The larger favicon (64px rather than 20px) leaves the title 44px less room. With long names without a
   natural place to break, the card thereby ran over sideways -- measured on 5 additional cards. Flex
   children have `min-width: auto` and so do not shrink below their content width; only `min-width: 0`
   allows the wrap, and `overflow-wrap: anywhere` breaks even where there is no space. */
        .tool-title-link,
        .tool-title > span {
            min-width: 0;
            overflow-wrap: anywhere;
        }

        .tool-title-link {
            color: inherit;
            text-decoration: none;
        }
        .tool-title-link:hover {
            color: var(--accent-cyan);
            text-decoration: underline;
        }

        .tool-subcat {
            font-size: 0.78rem;
            color: var(--accent-indigo);
            font-weight: 600;
        }

        /* ── Badges (2026-08-16) ────────────────────────────────────────────────────────────────
   Three changes at the user's wish:
   - smaller (0.65rem rather than 0.7rem),
   - the same corner radius as the tag badges (4px rather than 20px), so that the card does not
     mix two visual languages,
   - `white-space: normal` rather than `nowrap`. That is the most important one: with `nowrap` and the
     card's `overflow: hidden`, a long badge was simply CUT OFF. Now it grows in width and slips into the
     next row if need be -- the text is
               nie verstuemmelt. */
        .badge {
            font-size: 0.65rem;
            font-weight: 700;
            padding: 0.2rem 0.45rem;
            border-radius: 4px;
            text-transform: uppercase;
            letter-spacing: 0.03em;
            white-space: normal;
            line-height: 1.3;
            display: inline-block;
        }

        .badge-free {
            background: rgba(16, 185, 129, 0.15);
            color: #34d399;
            border: 1px solid rgba(16, 185, 129, 0.3);
        }

        .badge-paid {
            background: rgba(99, 102, 241, 0.15);
            color: #818cf8;
            border: 1px solid rgba(99, 102, 241, 0.3);
        }

        .badge-sub {
            background: rgba(168, 85, 247, 0.15);
            color: #c084fc;
            border: 1px solid rgba(168, 85, 247, 0.3);
        }

        .badge-rto {
            background: rgba(245, 158, 11, 0.15);
            color: #fbbf24;
            border: 1px solid rgba(245, 158, 11, 0.3);
        }

/* The price is not established (2026-08-18). Deliberately pale and without a colour of its own: it is no
   price model but the admission that none was collected. Before, the purchase badge stood at this place -
   an assertion without a basis. */
        .badge-unknown {
            background: rgba(148, 163, 184, 0.12);
            color: #94a3b8;
            border: 1px dashed rgba(148, 163, 184, 0.35);
        }

        .badge-don {
            background: rgba(6, 182, 212, 0.15);
            color: #22d3ee;
            border: 1px solid rgba(6, 182, 212, 0.3);
        }

/* Not approved editorially yet (status='pending_review') -- a continuation of the old
   "🆕 Automatisch ergänzte Tools" marker, now as a badge rather than a Markdown section of its own. */
        .badge-pending {
            background: rgba(234, 179, 8, 0.15);
            color: #eab308;
            border: 1px solid rgba(234, 179, 8, 0.35);
        }

        .tool-desc {
            font-size: 0.85rem;
    line-height: 1.4; /* the browser default was about 1.5 before -- tighter at the user's wish */
            color: var(--text-muted);
            display: -webkit-box;
            -webkit-line-clamp: 3;
            -webkit-box-orient: vertical;
            overflow: hidden;
            flex-grow: 1;
            margin: 0;
        }

/* Used by no template any more since the card conversion of 2026-08-16 (checked over public/ with
   --include=*.php --include=*.js). Deliberately LEFT STANDING rather than removed: the rule costs nothing,
   and deleting it would be a second change to the same file without a use of its own. Whoever tidies up
   here can take it along. */
        .card-meta {
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: 0.75rem;
            padding-top: 0.75rem;
            border-top: 1px solid rgba(255, 255, 255, 0.05);
            font-size: 0.78rem;
        }

/* ── The link row (2026-08-16, simplified 2026-08-17) ───────────────────────────────────
   Since the card became clickable it contains only the website link to the vendor; "more »" fell away,
   because it pointed at the same target as the card itself. The `justify-content: space-between` needed
   here before is thereby pointless. */
        .tool-links {
            display: flex;
            align-items: baseline;
            gap: 0.75rem;
            font-size: 0.8rem;
        }

/* The same colour as the short text above it (the user's wish) -- the vendor link is a
   footnote, no offering. */
        .tool-visit {
            color: var(--text-muted);
            text-decoration: none;
            display: inline-flex;
            align-items: center;
            gap: 0.3rem;
            transition: color 0.2s ease;
        }
        .tool-visit:hover {
            color: var(--accent-cyan);
            text-decoration: underline;
        }

        .tool-tags {
            display: flex;
            flex-wrap: wrap;
            gap: 0.3rem;
        }

/* ── The second zone: for the editors only (2026-08-16) ─────────────────────────────────
   It is not rendered in the frontend at all (see $adminMode in tool-grid.php) -- the dividing line above
   it makes visible that from here on nothing concerns the visitor. */
/* ── The button group stays right, the badges wrap within themselves (2026-08-16, a second attempt) ──
   The first attempt let the container itself wrap (`flex-wrap: wrap`) and pushed the button group to the
   right with `margin-left: auto`. That did not solve the problem: as soon as the badges got too wide, the
   group slipped onto a row of ITS OWN and stood on the left there.

   The right way is the reverse: the container NEVER wraps (`nowrap`), and the badge box wraps within
   itself instead. `min-width: 0` is the decisive line in that -- without it a flex child does not shrink
   below its content width, so the box would stay wide and the buttons would be pushed out. */
        .tool-admin {
            display: flex;
            align-items: flex-end;
            flex-wrap: nowrap;
            gap: 0.5rem;
            padding-top: 0.6rem;
            border-top: 1px solid rgba(255, 255, 255, 0.08);
        }

        .tool-admin-badges {
            display: flex;
            flex-wrap: wrap;
            gap: 0.3rem;
    /* it grows and shrinks; the badges wrap within this box */
            flex: 1 1 auto;
            min-width: 0;
        }

/* ── Not visible in the frontend (2026-08-16, the user's wish) ──────────────────────────
   A dark, deliberately weakly saturated red: with a stronger red the contrast to the type falls below
   what is still comfortably readable on a dark ground -- and these cards are exactly the ones one has to
   READ in order to edit them.

   In the admin area only: in the catalogue such entries do not occur at all because of the publication
   boundary. */
        .tool-card-flagged {
            background: rgba(84, 26, 32, 0.55);
            border-color: rgba(190, 72, 82, 0.4);
        }
        .tool-card-flagged:hover {
            background: rgba(96, 30, 37, 0.65);
            border-color: rgba(190, 72, 82, 0.6);
        }

/* Entries that are hidden look hidden (2026-08-26, the user's wish).

   Until now the visibility switch showed the state only on an icon in the bottom corner -- with twenty
   cards beside one another one does not find that.

   Opacity and saturation rather than a colour of its own: a pale card reads as "not active", and a red or
   grey card as "broken". The difference matters, because hidden is a state that is wanted and no
   shortcoming.

   On hover it becomes clear again -- otherwise one would have to switch it on first in order to read it.
   The button bar stays fully visible at all times: it is the way back. */
/* 0.45 was the first attempt and too little (2026-08-26, reported by the user): on the dark ground the
   text under it fell below what can still be read comfortably -- and those are exactly the cards one has
   to READ in order to decide whether they become visible again.

   The same consideration stands at `.tool-card-flagged` further up already. Hence 0.72 and a milder
   desaturation: pale enough to stand out at once, clear enough to read. */
        .tool-card-hidden {
            opacity: 0.72;
            filter: saturate(0.5);
            transition: opacity 0.15s ease, filter 0.15s ease;
        }
        .tool-card-hidden:hover {
            opacity: 1;
            filter: none;
        }
        .tool-card-hidden .admin-tool-controls {
            opacity: 1;
            filter: none;
        }

        .format-tag {
            font-family: 'JetBrains Mono', monospace;
            font-size: 0.72rem;
            color: var(--text-dim);
            background: rgba(0, 0, 0, 0.25);
    padding: 0.1rem 0.3rem; /* tighter, as in the sketch */
            border-radius: 4px;
            border: 1px solid rgba(255, 255, 255, 0.05);
        }

        .tool-link {
            color: var(--accent-cyan);
            text-decoration: none;
            font-weight: 600;
            display: inline-flex;
            align-items: center;
            gap: 0.35rem;
            transition: color 0.2s ease;
        }

        .tool-link:hover {
            color: #ffffff;
            text-decoration: underline;
        }

/* Not in the cards any more since 2026-08-16 (there `.tool-visit` stands as a plain text link), but still
   in use on the detail page and the 404 page --
   templates/pages/tool.php and not-found.php. Do not remove. */
        .btn-visit {
            background: rgba(99, 102, 241, 0.1);
            border: 1px solid rgba(99, 102, 241, 0.25);
            color: #ffffff;
            padding: 0.4rem 0.85rem;
            border-radius: var(--radius-md);
            text-decoration: none;
            font-size: 0.8rem;
            font-weight: 600;
            display: inline-flex;
            align-items: center;
            gap: 0.4rem;
            transition: all 0.2s ease;
        }

        .btn-visit:hover {
            background: var(--accent-indigo);
            border-color: var(--accent-indigo);
            box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);
        }

        /* Compact Data Table View */
        .data-table-wrapper {
            width: 100%;
            overflow-x: auto;
            background: var(--bg-card);
            border: 1px solid var(--border-color);
            border-radius: var(--radius-lg);
            margin-top: 1rem;
        }

        .data-table {
            width: 100%;
            border-collapse: collapse;
            text-align: left;
            font-size: 0.85rem;
        }

        .data-table th {
            background: rgba(15, 23, 42, 0.9);
            color: var(--text-muted);
            font-weight: 600;
            padding: 0.85rem 1rem;
            border-bottom: 1px solid var(--border-color);
            text-transform: uppercase;
            font-size: 0.72rem;
            letter-spacing: 0.05em;
        }

        .data-table td {
            padding: 0.85rem 1rem;
            border-bottom: 1px solid rgba(255, 255, 255, 0.04);
            color: var(--text-muted);
            vertical-align: middle;
        }

        .data-table tr:hover td {
            background: rgba(255, 255, 255, 0.02);
            color: var(--text-main);
        }

        .data-table td strong {
            color: #ffffff;
        }

        /* Hidden Class for Search Filtering */
        .is-hidden {
            display: none !important;
        }

        /* Footer */
        .app-footer {
            margin-top: 4rem;
            border-top: 1px solid var(--border-color);
            padding: 2rem 0;
            text-align: center;
            color: var(--text-dim);
            font-size: 0.82rem;
        }

        /* Width and side spacing do NOT stand here but further down, in the rule
           `.header-content, .app-container, .footer-content` -- see the reasoning there. All that
           stands here is how the two groups in the footer sit relative to each other. */
        .footer-content {
            margin: 0 auto;
            display: flex;
            align-items: center;
            justify-content: space-between;
            flex-wrap: wrap;
            gap: 1rem;
        }

/* The footer menu (public/admin-menu.php, menu_key='footer') */
        /* Two groups in the footer (2026-09-01): `.footer-content` stands on `space-between`, so
           it is enough that both groups are there -- the first slides left, the second right.
           Where the left one is empty, an empty `<span>` in the markup holds the place. */
        .footer-menu {
            display: flex;
            gap: 1.25rem;
            flex-wrap: wrap;
        }
        /* Right-aligned even when the group wraps: otherwise the second line would begin left. */
        .footer-menu-legal {
            justify-content: flex-end;
        }
        .footer-menu a {
            color: var(--text-dim);
            text-decoration: none;
        }
        .footer-menu a:hover {
            color: var(--text-muted);
            text-decoration: underline;
        }

        .agent-indicator {
            display: flex;
            align-items: center;
            gap: 0.5rem;
            color: var(--accent-cyan);
            font-weight: 600;
        }

        /* ── Tool-Detailseite (/tool/{slug}) ────────────────────────── */
        .tool-breadcrumb {
            margin-bottom: 1.25rem;
            font-size: 0.85rem;
            color: var(--text-dim);
        }
        .tool-breadcrumb a {
            color: var(--text-muted);
            text-decoration: none;
        }
        .tool-breadcrumb a:hover {
            color: var(--text-main);
            text-decoration: underline;
        }
        .tool-breadcrumb span {
            margin: 0 0.4rem;
        }
        .tool-detail-header {
            display: flex;
            gap: 1.75rem;
            align-items: flex-start;
            margin-bottom: 2rem;
            flex-wrap: wrap;
        }
        /* Larger and wider since 2026-08-30 (the user's wish): 180x180 was square, and square is
           the one shape a screenshot of a piece of software never has. 300x188 is 16:10 -- close
           enough to what the snapshots really bring that `object-fit: cover` hardly cuts anything
           away any more.

           The height stands as a number and not as an `aspect-ratio`: the empty placeholder beside
           it (.tool-detail-thumb-empty) is a <div> without an image, and an aspect ratio without
           content yields no height there. Two elements, one shape. */
        .tool-detail-thumb {
            width: 300px;
            height: 188px;
            border-radius: var(--radius-lg);
            object-fit: cover;
            border: 1px solid var(--border-color);
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
            flex-shrink: 0;
        }

        /* On narrow screens it must not stick out past the edge. */
        @media (max-width: 560px) {
            .tool-detail-media,
            .tool-detail-thumb {
                width: 100%;
                max-width: 300px;
            }
        }
        .tool-detail-thumb-empty {
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 3rem;
            background: var(--bg-card);
        }
        .tool-detail-headline {
            flex: 1;
            min-width: 260px;
        }
        .tool-detail-headline h1 {
            font-size: 1.8rem;
            font-weight: 800;
            letter-spacing: -0.01em;
            margin-bottom: 0.6rem;
        }
        .tool-detail-badges {
            display: flex;
            align-items: center;
            gap: 0.5rem;
            flex-wrap: wrap;
            margin-bottom: 0.75rem;
        }
        .tool-detail-desc {
            color: var(--text-muted);
            font-size: 0.95rem;
            margin-bottom: 1rem;
            /* The 640px are gone since 2026-08-31: the width is set by `.tool-detail-intro` now,
               and that is the same as the box with the summary below it. Two caps on top of each
               other meant the narrower one won -- and the narrower one was the one nobody would
               have gone looking for. */
        }
        /* ── The short text and the button, under the header area (2026-08-31) ────────────
           The same width as `.tool-detail-body` below it -- that is the whole point: the eye
           should see one edge and not three. Whoever changes the one number changes the other
           with it. */
        .tool-detail-intro {
            max-width: 1280px;   /* 780px until 2026-09-05, moved with the body */
            margin-bottom: 2rem;
        }
        .tool-detail-intro .tool-detail-desc {
            margin-bottom: 0.9rem;
        }
        /* The button closes the paragraph off on the right. `flex` and not `text-align`, because
           the button is itself a flex element (text plus symbol) and `text-align` has no effect
           on it. */
        .tool-detail-intro-actions {
            margin-left: auto;
        }
        .tool-detail-intro {
            display: flex;
            flex-direction: column;
            align-items: flex-start;
        }
        /* ── Sharing left, the button right, one row (2026-09-01) ─────────────────────────
           Until then the button hung on the COLUMN with `align-self: flex-end`. Two things in a
           column never stand beside each other -- hence one row for both, with `space-between`
           rather than a `margin-left: auto` on the button: that way the sharing bar stays left
           even where there is no button at all. */
        .tool-detail-intro-row {
            align-items: center;
            display: flex;
            flex-wrap: wrap;
            gap: 1rem;
            justify-content: space-between;
            width: 100%;
        }

        .tool-detail-body {
            background: var(--bg-card);
            border: 1px solid var(--border-color);
            border-radius: var(--radius-lg);
            padding: 1.5rem 1.75rem;
            margin-bottom: 2rem;
            /* 780px until 2026-09-05; 1280px plus the full column width at the user's request.
               `.tool-detail-intro` above carries the same number -- see its comment. */
            width: 100%;
            max-width: 1280px;
            color: var(--text-muted);
            line-height: 1.75;
        }
        .tool-detail-body h3 {
            color: var(--text-main);
            font-size: 1.05rem;
            margin: 1.25rem 0 0.6rem;
        }
        .tool-detail-body h3:first-child {
            margin-top: 0;
        }
        /* ── Every list flush with the text edge (2026-08-31, reported) ───────────────────────
           The numbered list broke out to the LEFT: `.catalog-page *` sets `padding: 0`, and with
           `list-style-position: outside` the browser then draws the number outside the box.
           Measured: the paragraph at 45 pixels, the numbers at 45 as well -- so the number itself
           stuck out past that, to the left.

           The answer is the hanging indent: the marker stands INSIDE (that is, exactly at the text
           edge), and the following lines are indented by the same width so that they sit under the
           text and not under the number.

           Deliberately for `ul` AND `ol` and without exception -- "every form of HTML list", and
           the next kind of text in the catalogue should inherit it without anybody having to think
           of it. */
        .tool-detail-body ul,
        .tool-detail-body ol {
            list-style-position: inside;
            margin: 0.5rem 0;
            padding-left: 0;
        }
        .tool-detail-body li {
            margin-bottom: 0.3rem;
            /* Together: the first line begins at the marker, every further one under the text.
               1.5em carries "10." -- beyond that the number slides into the text, and on a list
               with twelve points that stands out at once. */
            padding-left: 1.5em;
            text-indent: -1.5em;
        }
        /* Nested: indent the inner list without counting the indent twice. */
        .tool-detail-body li > ul,
        .tool-detail-body li > ol {
            padding-left: 1em;
            text-indent: 0;
        }
        .tool-detail-body p {
            margin-bottom: 0.85rem;
        }
        .tool-detail-empty-note {
            color: var(--text-dim);
            font-style: italic;
            margin-bottom: 2rem;
        }
        /* ── The gallery: ONE row, square, scrollable (2026-08-31) ──────────────────────────
           Before it was a grid of images in their own dimensions -- a portrait one beside a wide
           one, and the page grew long from it. The same size means comparable; `object-fit: cover`
           crops for that, and that is exactly why the large view exists. */
        /* The strip is the frame: it carries the cap and the two arrows, the row inside it
           scrolls. Without this frame the arrows would have nothing to align to -- they have to
           stand at the edges of the VISIBLE area and not at those of the scrolled content. */
        .tool-gallery-strip {
            position: relative;
            /* The same width as the short text and the summary. It was missing, and the row ran
               out past the right edge -- reported, and the user had said "no element", not "no
               text". */
            max-width: 1280px;   /* 780px until 2026-09-05, moved with the body */
            margin-bottom: 2rem;
        }

        .tool-detail-gallery {
            display: flex;
            gap: 0.75rem;
            overflow-x: auto;
            /* No visible bar: one pages with the arrows and drags with a finger or a wheel. A bar
               under a row of tiles looks like a second control. */
            scrollbar-width: none;
            /*
             * `scroll-behavior: smooth` stood here and had to go (2026-08-31, measured).
             * In this browser's engine the rule turns every PROGRAMMATIC scroll into a no-op:
             * `scrollLeft = 150` reads back 0 afterwards, `scrollBy()` moves nothing, and that
             * holds even with `behavior: 'auto'` in the call, although by the specification the
             * option should beat the rule. Without the rule: 132 -- exactly the tile edge.
             *
             * The smooth glide therefore stands in the CALL (`scrollBy({behavior: 'smooth'})`)
             * and no longer here. There it applies to the arrows and leaves dragging with a finger
             * or a wheel untouched, where it comes from the device anyway.
             *
             * Found because the arrows did nothing and measuring in the browser showed it -- there
             * was nothing to see in the markup, the click arrived, `preventDefault` took hold.
             */
            /* The tiles snap while being dragged -- without it half an image is left standing at
               the edge on release, and that looks like a fault rather than like a row. */
            scroll-snap-type: x proximity;
        }
        .tool-detail-gallery::-webkit-scrollbar {
            display: none;
        }

        /* The arrows lie over the tiles, half translucent. They stand only where there really is
           more to come -- the script hides them at the ends; an arrow that does nothing is worse
           than no arrow. */
        .tool-gallery-arrow {
            align-items: center;
            background: rgba(9, 13, 22, 0.72);
            backdrop-filter: blur(2px);
            border: 1px solid var(--border-color);
            border-radius: 999px;
            color: var(--text-main);
            cursor: pointer;
            display: flex;
            font-size: 1.4rem;
            height: 2.2rem;
            justify-content: center;
            line-height: 1;
            padding: 0;
            position: absolute;
            top: calc(50% - 0.25rem);
            transform: translateY(-50%);
            transition: opacity 0.15s ease;
            width: 2.2rem;
            z-index: 2;
        }
        .tool-gallery-arrow:hover {
            background: rgba(9, 13, 22, 0.9);
            border-color: var(--border-highlight);
        }
        .tool-gallery-arrow[hidden] {
            display: none;
        }
        .tool-gallery-prev { left: 0.4rem; }
        .tool-gallery-next { right: 0.4rem; }

        html[data-theme="light"] .tool-gallery-arrow {
            background: rgba(255, 255, 255, 0.85);
            color: #0f172a;
        }
        .tool-gallery-thumb {
            background: none;
            border: 1px solid var(--border-color);
            border-radius: var(--radius-md);
            cursor: zoom-in;
            flex: 0 0 auto;
            height: 120px;
            overflow: hidden;
            padding: 0;
            scroll-snap-align: start;
            width: 120px;
        }
        .tool-gallery-thumb:hover {
            border-color: var(--border-highlight);
        }
        .tool-gallery-thumb:focus-visible {
            outline: 2px solid var(--accent-indigo);
            outline-offset: 2px;
        }
        .tool-detail-gallery img {
            display: block;
            height: 100%;
            object-fit: cover;
            width: 100%;
        }

        /* The main image opens the same view -- so it carries the same cursor. The button itself
           may add nothing: the border and the rounding sit on the image. */
        .tool-detail-thumb-btn {
            background: none;
            border: none;
            cursor: zoom-in;
            display: block;
            line-height: 0;
            padding: 0;
        }
        .tool-detail-thumb-btn:focus-visible {
            outline: 2px solid var(--accent-indigo);
            outline-offset: 3px;
        }

/* ── Ratings (phase 3/5, since 2026-08-10) ────────────────── */
        .tool-detail-rating {
            display: flex;
            align-items: center;
            gap: 0.6rem;
            margin-bottom: 0.5rem;
            flex-wrap: wrap;
        }
        .star-row {
            display: inline-flex;
            gap: 0.1rem;
            font-size: 1.1rem;
            line-height: 1;
        }
        .star-row-small {
            font-size: 0.85rem;
        }
        .star {
            color: rgba(255, 255, 255, 0.15);
        }
        .star-filled {
            color: var(--accent-amber);
        }
        .tool-detail-rating-value {
            font-weight: 700;
            color: var(--text-main);
            font-size: 0.9rem;
        }
        .tool-detail-rating-count {
            color: var(--text-dim);
            font-size: 0.82rem;
        }
        .tool-detail-external-ratings {
            list-style: none;
            display: flex;
            flex-direction: column;
            gap: 0.2rem;
            margin: 0 0 0.85rem;
            padding: 0;
            font-size: 0.78rem;
            color: var(--text-dim);
        }
        .tool-detail-external-ratings a {
            color: var(--accent-cyan);
            text-decoration: none;
        }
        .tool-detail-external-ratings a:hover {
            text-decoration: underline;
        }

/* ── Further links (curated by the admin, since 2026-08-10) ─── */
        .tool-related-links {
            list-style: none;
            display: flex;
            flex-direction: column;
            gap: 0.5rem;
            margin: 0.5rem 0 0;
            padding: 0;
        }
        .tool-related-link {
            display: flex;
            /* `flex-start` and not `baseline` (2026-09-01, reported: the symbols looked
               >>vermurkst<<). An emoji has a different baseline from the text beside it, and with
               `baseline` the browser aligns both to it -- which pushed the symbol visibly down.
               `flex-start` aligns to the top edge, and that one is right. */
            align-items: flex-start;
            /* ── The body's hanging indent does NOT apply here (2026-09-01, reported) ─────────
               `.tool-detail-body li` sets `padding-left: 1.5em; text-indent: -1.5em` -- the
               hanging indent for lists inside running text (31.08.). This list stands in the same
               box and inherited it, with two visible consequences:

               - the indent: a measured 21 pixels by which the whole line began further right than
                 it needed to. Exactly what was reported, verbatim: *"du sollst sie ja nicht weiter
                 einruecken"*.
               - the overlap: `text-indent` inherits into EVERY flex element and pulls the first
                 line there left by the same 21 pixels. So the box of the source note stood
                 correctly while its TEXT reached 13 pixels into the link text. That is also why my
                 earlier measurement found nothing: it measured the BOXES, and those were fine. The
                 user saw it.

               The hanging indent is right for a list inside running text and wrong for this list:
               it has a symbol column of its own and needs no marker compensation.

               The undoing stands further down under `.tool-detail-body .tool-related-link` and not
               here: `.tool-detail-body li` has a class AND an element (0,1,1), `.tool-related-link`
               only a class (0,1,0) -- here it loses. Measured after exactly that had happened. */
            /* A visible gap between symbol and text -- not an indent, but air. */
            gap: 0.6rem;
            font-size: 0.88rem;
        }
        /* An emoji has no fixed width: 📘 is wider than ▶. Without a set width the captions were
           therefore indented differently depending on the kind of link, and that was exactly what
           made it restless. `1.1em` plus the row's gap yields one edge. */
        .tool-related-link-icon {
            flex: 0 0 1.1em;
            /* `min-width: 0` is needed although the width already stands above: a flex element
               has `min-width: auto` by default and thereby grows past its basis when the content
               is wider. 📄 is wider than ▶ -- measured, the captions therefore stood 4 pixels
               apart depending on the kind of link. */
            min-width: 0;
            overflow: hidden;
            color: var(--accent-cyan);
            font-size: 0.95rem;
            /* On the text's line height, so that symbol and first word sit at the same level. */
            line-height: 1.5;
            text-align: center;
        }
        .tool-related-link a {
            color: var(--accent-cyan);
            text-decoration: none;
            font-weight: 600;
        }
        .tool-related-link a:hover {
            text-decoration: underline;
        }
        /* The undoing of the hanging indent -- with the specificity that beats
           `.tool-detail-body li` (0,2,0 against 0,1,1). See the comment above. */
        .tool-detail-body .tool-related-link {
            padding-left: 0;
            text-indent: 0;
        }
        .tool-related-link-source {
            color: var(--text-dim);
            font-size: 0.8rem;
            /* Do not shrink and do not wrap: the source is short, and it should stand at the end
               of the line rather than in two pieces below it. */
            flex-shrink: 0;
            white-space: nowrap;
        }

/* ── Comments (phase 5, since 2026-08-10) ───────────────────── */
        .tool-detail-comments {
            margin-top: 2.5rem;
            max-width: 1280px;   /* 780px until 2026-09-05, moved with the body */
        }
        .tool-detail-comments-heading {
            font-size: 1.15rem;
            font-weight: 700;
            margin-bottom: 1rem;
        }
        .comment-card {
            background: var(--bg-card);
            border: 1px solid var(--border-color);
            border-radius: var(--radius-md);
            padding: 1rem 1.25rem;
            margin-bottom: 0.85rem;
        }
        .comment-meta {
            display: flex;
            align-items: center;
            gap: 0.6rem;
            margin-bottom: 0.4rem;
            flex-wrap: wrap;
        }
        .comment-author {
            color: var(--text-main);
            font-size: 0.88rem;
        }
        .comment-date {
            color: var(--text-dim);
            font-size: 0.75rem;
            margin-left: auto;
        }
        .comment-body {
            color: var(--text-muted);
            font-size: 0.88rem;
            line-height: 1.6;
/* Comments are pure escaped text (never HTML) -- see templates/pages/tool.php and
   CatalogHelpers::validateCommentBody(); white-space only keeps the line breaks out of nl2br(), and no
   additional HTML rendering is needed. */
        }

/* ── The facet tags on the tool detail page (2026-08-14) ─────────────────────────────
   It replaces the single format statement of before: instead of a free-text field, the typed tags stand
   here, grouped by facet. A definition list rather than a salad of divs, because that is exactly what it
   is in substance -- a term and the values belonging to it. */
        .tool-detail-facets {
            display: grid;
            grid-template-columns: max-content 1fr;
            gap: 0.4rem 1rem;
            margin: 0.75rem 0 0;
            align-items: baseline;
        }
        .tool-detail-facets dt {
            font-size: 0.72rem;
            font-weight: 700;
            text-transform: uppercase;
            letter-spacing: 0.04em;
            color: var(--accent-indigo);
            margin: 0;
        }
        .tool-detail-facets dd {
            display: flex;
            flex-wrap: wrap;
            gap: 0.35rem;
            margin: 0;
        }

        @media (max-width: 560px) {
/* On narrow screens the two-column list gets too tight -- the term above the values. */
            .tool-detail-facets {
                grid-template-columns: 1fr;
                gap: 0.15rem;
            }
            .tool-detail-facets dd {
                margin-bottom: 0.5rem;
            }
        }

/* ── The 404 page (2026-08-15) ───────────────────────────────────────────────────────────
   Deliberately plain: an error page should explain quickly and offer a way back, not look designed. It
   did not exist at all before -- unknown addresses redirected to the start page (a soft 404). */
        .not-found {
            max-width: 40rem;
            margin: 4rem auto;
            padding: 0 1rem;
            text-align: center;
        }
        .not-found h1 {
            font-size: 1.75rem;
            margin-bottom: 0.75rem;
        }
        .not-found-lead {
            color: var(--text-muted, rgba(255, 255, 255, 0.7));
            margin-bottom: 1.5rem;
            line-height: 1.6;
        }


        /* ══ Filterleiste: Umschalter, Sortierung (2026-08-16) ════════════════════════════════ */

/* The last active filter cannot be switched off -- otherwise the list would show nothing and look broken
   rather than filtered. Rather than swallowing the click silently, the button flashes up once: an
   explanation without text. */
        .filter-btn-blocked {
            animation: filter-blocked 0.35s ease;
        }
        @keyframes filter-blocked {
            0%, 100% { transform: translateX(0); }
            25% { transform: translateX(-3px); }
            75% { transform: translateX(3px); }
        }

        .sort-box {
            display: flex;
            align-items: center;
            gap: 0.5rem;
    margin-left: auto; /* flush right, as the user wished */
        }

        .sort-label {
            font-size: 0.8rem;
            color: var(--text-muted);
            white-space: nowrap;
        }

        .sort-select {
            background: rgba(255, 255, 255, 0.04);
            border: 1px solid var(--border-color);
            color: var(--text-main);
            padding: 0.5rem 0.7rem;
            border-radius: var(--radius-md);
            font-size: 0.82rem;
            font-family: inherit;
            cursor: pointer;
        }
        .sort-select:focus {
            outline: 2px solid var(--accent-indigo);
            outline-offset: 1px;
        }
/* The selection menu does NOT inherit the dark ground from the field -- without this line the browser
   draws white entries on a white ground (Windows/Chrome). */
        .sort-select option {
            background: #12131a;
            color: #e6e8ef;
        }

/* The card and table switch has sat in the heading row since 2026-08-16. `margin-left: auto` pushes it
   right over there without the heading needing a
   width of its own. */
        .category-header-row,
        .category-header {
            display: flex;
            align-items: center;
            gap: 0.6rem;
        }
        .category-header .view-toggle,
        .category-header-row .view-toggle {
            margin-left: auto;
        }

/* ══════════════════════════════════════════════════════════════════════════════════════════════
   Hell-Modus (2026-08-16)
   ══════════════════════════════════════════════════════════════════════════════════════════════
   The whole interface reads its colours out of the variables above -- a light mode is thereby a second set
   of variables and no second style sheet. That is why ONLY what really depends on colour is redefined
   here; spacing, radii and font sizes stay untouched.

   `data-theme="light"` on the <html> element rather than a class on the <body>: the attribute stands in
   the markup before the body exists, and setting it in the <head> (see partials/theme-init.php) prevents
   the brief dark flash on loading.

   The accent colours stay the same hues but become darker -- indigo #6366f1 has only 3.4:1 contrast on
   white and would be below the limit as a text colour. #4338ca comes to 7.4:1.
   ══════════════════════════════════════════════════════════════════════════════════════════════ */
html[data-theme="light"] {
    --bg-dark: #f4f6fb;
    --bg-card: rgba(255, 255, 255, 0.85);
    --bg-card-hover: #ffffff;
    --bg-input: rgba(255, 255, 255, 0.9);
    --border-color: rgba(15, 23, 42, 0.12);
    --border-highlight: rgba(67, 56, 202, 0.35);
    --text-main: #0f172a;
    --text-muted: #475569;
/* Darker than in night mode (#64748b). Measured: with the light value the tag badges came to 4.25:1 and
   thereby lay just below the 4.5:1 that WCAG AA demands. #556274 brings 5.1:1 and stays recognisably more
   restrained than --text-muted all the same. */
    --text-dim: #556274;
    --accent-indigo: #4338ca;
    --accent-cyan: #0e7490;
    --accent-emerald: #047857;
    --accent-pink: #be185d;
    --accent-amber: #b45309;
    --accent-purple: #7e22ce;
    --shadow-glow: 0 0 25px rgba(67, 56, 202, 0.1);
}

/* The page background is no variable value but three overlaid gradients -- those have to be damped
   separately, as otherwise they shine through glaringly on a light ground. */
html[data-theme="light"] body.catalog-page {
    background-image:
        radial-gradient(at 15% 15%, rgba(99, 102, 241, 0.07) 0px, transparent 50%),
        radial-gradient(at 85% 85%, rgba(6, 182, 212, 0.06) 0px, transparent 50%),
        radial-gradient(at 50% 50%, rgba(168, 85, 247, 0.04) 0px, transparent 70%);
}

/* Individual places set colours fixed rather than through variables -- those have to be caught up with
   here, as otherwise white text stands on a white ground. Found by searching for `#ffffff` and
   `rgba(255, 255, 255` in the rules that set text colours. */
html[data-theme="light"] .tool-title,
html[data-theme="light"] .btn-visit {
    color: var(--text-main);
}
html[data-theme="light"] .filter-btn.active,
html[data-theme="light"] .view-btn.active {
    color: #ffffff; /* on a filled accent ground white stays right */
}
html[data-theme="light"] .format-tag {
    background: rgba(15, 23, 42, 0.05);
    border-color: rgba(15, 23, 42, 0.08);
}
/* The filter buttons stand on white in light mode (2026-09-05, the user's wish); the marks in the cards
   keep their grey. Ticked stays slate, see below. */
html[data-theme="light"] .filter-btn-tag {
    background: #ffffff;
    border-color: rgba(15, 23, 42, 0.12);
}

html[data-theme="light"] .filter-btn-tag:hover {
    background: rgba(15, 23, 42, 0.06);
}
/* Ticked in light mode: the same slate as in the dark one. Until 2026-09-05 the light rule above won
   over the ticked state on hover and the button read as barely ticked -- the user's screenshot. */
html[data-theme="light"] .filter-btn-tag.active,
html[data-theme="light"] .filter-btn-tag.active:hover {
    background: #475569;
    border-color: #475569;
    color: #ffffff;
}

/* The grey ground of the filter box in light mode (2026-09-01). It stands here and not above,
   because the whole light theme stands together at this place -- a colour defined only above would
   fall back in light mode to a value meant for the dark one. */
html[data-theme="light"] .toolbar {
    background: rgba(15, 23, 42, 0.04);
}
html[data-theme="light"] .tool-favicon {
    background: rgba(255, 255, 255, 0.6);
}
html[data-theme="light"] .sort-select option {
    background: #ffffff;
    color: #0f172a;
}
/* The "not visible yet" marking in the admin area: on a light ground it needs a light red rather than a
   dark one, as otherwise the card goes black. */
html[data-theme="light"] .tool-card-flagged {
    background: rgba(254, 226, 226, 0.75);
    border-color: rgba(190, 72, 82, 0.35);
}
html[data-theme="light"] .tool-card-flagged:hover {
    background: rgba(254, 226, 226, 0.95);
}

/* The header area (the switch, the account, the language) has lain in a FILE OF ITS OWN since 2026-08-16:
   assets/header-controls.css. The reason: the admin layout does not load frontend.css, by which both
   symbols of the day/night switch were visible there at once and the group stayed unstyled
   stayed. A copy in admin-custom.css would have been the second truth. */


/* The price badges in light mode (2026-08-16).
   Their text colours are hard-wired (#34d399, #818cf8, …) and tuned to a dark ground. On a light ground
   the green badge thereby comes to 2.95:1 -- measured, not estimated, and clearly below the 4.5:1 that
   WCAG AA demands for text. The same hues, chosen darker. */
html[data-theme="light"] .badge-free { background: rgba(16, 185, 129, 0.12); color: #047857; border-color: rgba(16, 185, 129, 0.35); }
html[data-theme="light"] .badge-paid { background: rgba(99, 102, 241, 0.12); color: #4338ca; border-color: rgba(99, 102, 241, 0.35); }
html[data-theme="light"] .badge-sub  { background: rgba(168, 85, 247, 0.12); color: #7e22ce; border-color: rgba(168, 85, 247, 0.35); }
html[data-theme="light"] .badge-rto  { background: rgba(245, 158, 11, 0.14); color: #b45309; border-color: rgba(245, 158, 11, 0.4); }
html[data-theme="light"] .badge-unknown { background: rgba(100, 116, 139, 0.10); color: #64748b; border-color: rgba(100, 116, 139, 0.35); }
html[data-theme="light"] .badge-don  { background: rgba(6, 182, 212, 0.12); color: #0e7490; border-color: rgba(6, 182, 212, 0.35); }
html[data-theme="light"] .badge-pending { background: rgba(234, 179, 8, 0.16); color: #854d0e; border-color: rgba(234, 179, 8, 0.4); }


/* ══ Hell-Modus: fest verdrahtete Dunkel-Farben nachziehen (2026-08-16) ═══════════════════════
   Three places do not set their colour through variables and therefore stayed dark. All of them found
   while going through the page in the light mode. */

/* The header had a fixed dark ground and stood as a black bar over the light page. `rgba(...)` rather
   than a solid tone, so that `backdrop-filter` goes on working. */
html[data-theme="light"] .app-header {
    background: rgba(255, 255, 255, 0.85);
    border-bottom-color: rgba(15, 23, 42, 0.1);
}

/* The title is a gradient with `-webkit-text-fill-color: transparent` -- on a light ground it would be
   practically invisible. In the light mode therefore ordinary solid-tone text. */
html[data-theme="light"] .brand-text h1 {
    background: none;
    -webkit-text-fill-color: initial;
    color: var(--text-main);
}

/* The category selected had `color: #ffffff` on a light gradient -- in the light mode white on nearly
   white. Until the visual redesign the text simply stays in the ordinary text colour (decided that way
   with the user on 2026-08-16); the selection is still recognisable by the background and the
   Rahmen erkennbar. */
html[data-theme="light"] .nav-item.active a {
    color: var(--text-main);
}

/* Favicons are often white logos on a transparent ground. On the light surface under them the outline
   disappears -- a thin border gives them a boundary again (the user's wish, noticed on the example of
   "Ableton Live"). It holds in BOTH modes: the surface under the icon is light in night mode too. */

.tool-favicon {
    border: 1px solid rgba(15, 23, 42, 0.12);
}
.tool-favicon-empty {
    border-color: rgba(15, 23, 42, 0.12);
}
html[data-theme="light"] .tool-favicon {
    border-color: rgba(15, 23, 42, 0.15);
}

/* ══ Corner flags: sponsored and new (2026-08-16) ═════════════════════════════════════════════
   They sit over the top right corner, half outside the card -- they do not describe the product (the
   price badges do that) but its place in the catalogue.

   So that the flag may stick out of the card, `.tool-card` had to lose its `overflow: hidden`. That was
   set there for the gradient stripe of `.tool-card::before`; that stripe is now limited to the card width
   itself and no longer needs the block. Without this change the flag would have been cut off exactly at
   the card edge. */
.tool-card {
    overflow: visible;
}
.tool-card::before {
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    overflow: hidden;
}

.tool-flags {
    position: absolute;
    top: -0.6rem;
    right: 0.75rem;
    display: flex;
    gap: 0.3rem;
    z-index: 2;
}

.tool-flag {
    font-size: 0.62rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    padding: 0.2rem 0.5rem;
    border-radius: 4px;
    white-space: nowrap;
/* A shadow, not a border: the flag lies visibly OVER the card, and a border alone does not convey that. */

    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.35);
}

/* Amber rather than green or blue: the colour must not look like a distinction. A paid placement is a
   marking, no seal of quality. */
.tool-flag-sponsored {
    background: #b45309;
    color: #fff7ed;
}
.tool-flag-new {
    background: var(--accent-emerald);
    color: #04231a;
}
html[data-theme="light"] .tool-flag-new {
    background: #047857;
    color: #ecfdf5;
}

/* "Catalogue" (2026-08-26): a collection point, not a single tool -- Freesound.org, Pianobook,
   8notes.com. Indigo, because the flag WARNS rather than distinguishing: whoever clicks gets a list with
   hundreds of items and not one product. Clearly distinguishable from amber (paid) and emerald (new),
   without looking like an error. */
.tool-flag-catalogue {
    background: #4338ca;
    color: #eef2ff;
}
html[data-theme="light"] .tool-flag-catalogue {
    background: #3730a3;
    color: #eef2ff;
}

/* In the table view there is no corner for something to hang on -- there the same flags simply stand
   before the name. */
.tool-flag-inline {
    position: static;
    box-shadow: none;
    margin-right: 0.3rem;
    vertical-align: middle;
}

/* ══ The introductory text in the category head (2026-08-17) ══════════════════════════════════
   It stands between the heading and the filter bar. Deliberately narrower than the card area: flowing
   text over the full width of a three-column grid is hard to read (the line gets too long, and the eye no
   longer finds the start of the next one). */
.category-intro {
    max-width: 68ch;
    margin: -0.5rem 0 1.25rem 0;
    color: var(--text-muted);
    font-size: 0.92rem;
    line-height: 1.6;
}

.category-empty {
    color: var(--text-dim);
    font-size: 0.88rem;
    font-style: italic;
    margin-bottom: 0.5rem;
}

/* ══ The whole card is the link (2026-08-17) ══════════════════════════════════════════════════
   A "stretched link": one single ordinary <a href> in the markup (the tool name), whose ::after surface
   is pulled over the whole card.

   Why this way and not the more obvious ones: wrapping the card in an <a> is invalid as soon as further
   links or buttons stand in it (the website link, the admin buttons) -- the parser tears the nesting
   apart. A JavaScript click handler, on the other hand, is no link at all to a search engine bot. This
   way it stays exactly ONE ordinary anchor with the product name
   as its anchor text -- one cannot offer a crawler more.

   `z-index` regulates the layering: the covering surface lies at 1, and everything that has to be
   clickable itself at 2. Without the second, the website link and the admin buttons would be unreachable.

   A known side effect: text in the card can no longer be selected with the mouse, because the covering
   surface catches the drag. That is the price of this construction, and it is the same with the model. */
.tool-card-link::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 1;
    border-radius: inherit;
}

/* What is clickable lies over the covering surface. */
.tool-card .tool-visit,
.tool-card .tool-admin {
    position: relative;
    z-index: 2;
}

/* `.tool-flags` needs ONLY the layer, NOT `position: relative` (2026-08-17).
   Exactly this error happened on the first attempt: the collecting rule above set `position: relative`
   here too and thereby overwrote the `position: absolute` that lets the flags stick out of the card. The
   result: they slipped back into the card. `z-index` alone is enough, because `absolute` already produces
   a participant in the stacking context. */
.tool-card .tool-flags {
    z-index: 3;
}

/* Feedback over the whole surface: without it the card looks as it did before and nobody tries the click
   outside the title. */
.tool-card:hover .tool-title-link {
    color: var(--accent-cyan);
    text-decoration: underline;
}

/* Keyboard operation: the focus lies on the title anchor, but what should become visible is the card --
   otherwise a border would travel around a word while tabbing through, whereas the click covers the whole
   surface. */
.tool-card:focus-within {
    border-color: var(--accent-cyan);
}
.tool-card-link:focus-visible::after {
    outline: 2px solid var(--accent-cyan);
    outline-offset: 2px;
}

/* The same for the table view. `position: relative` on a <tr> was long unreliable but is implemented in
   every current browser. If it does fail, the title stays an ordinary link -- the row is then only not
   clickable as a whole, and nothing is lost. */
.data-table tbody tr.table-row-item {
    position: relative;
}
.tool-row-link::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 1;
}
.data-table tbody tr.table-row-item:hover {
    background: var(--bg-hover, rgba(148, 163, 184, 0.08));
}
.data-table td a:not(.tool-row-link),
.data-table td .admin-tool-controls {
    position: relative;
    z-index: 2;
}

/* ══ The platform symbols on the card (2026-08-17) ════════════════════════════════════════════
   They stand where the website link stood until 2026-08-17. A muted colour like the short text above
   them: the platform is a marginal statement, no statement about the product -- it should be taken in as
   a pattern while skimming, not as an eye-catcher.

   Do NOT set `position: relative; z-index: 2`: the symbols are no links, and they may lie under the
   card's clickable surface. The `title` appears all the same, because the browser shows the tooltip over
   the topmost element that carries one -- the mouse pointer does not reach through here, which is why the
   title hangs on the enclosing span as well. */
.platform-icons {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
}

.platform-icon {
    display: inline-flex;
    color: var(--text-muted);
    transition: color 0.2s ease;
}
.tool-card:hover .platform-icon {
    color: var(--text-main);
}

/* ══ The badges on the detail page (2026-08-17, set anew on 2026-08-30) ═══════════════════════

   Until 30.08. they stood at the right edge of the HEADER AREA. That was an answer to the card,
   where they stick out past the edge and have to stand apart from the price labels beside them --
   and it was the wrong answer to a different question: in the wide header they stood half a screen
   away from the tool they refer to.

   Now they float over the main image, top right (the user's wish). The image IS the tool, so that
   is where the badge belongs -- and the slight overhang past the image edge is right here, because
   the edge is sharp and the badge would otherwise look like part of the image. */
.tool-detail-media {
    position: relative;
    flex-shrink: 0;
    line-height: 0;   /* otherwise a line gap stands under the image */
}

/* ── The badge sits ON the image edge -- exactly as on the card (2026-08-31, second attempt) ───

   Three versions in two days, and the third is the card's:

     `top: -0.5rem; right: -0.5rem`   stuck out at the top AND the right, and looked fallen off
     `top: 0.5rem;  right: 0.5rem`    lay inside the image and seemed pressed into it
     now                              the same values as `.tool-flags` on the card

   The card had had the answer since 16.08.: over the edge at the top, INSIDE at the right. The
   badge thereby lies on the edge rather than beside it or in it. Whoever changes one of the two
   rules changes the other with it -- they show the same thing in the same place.

   `line-height: normal` is not a nicety but the actual fault of the second attempt:
   `.tool-detail-media` carries `line-height: 0` (so that no line gap stands under the image), and
   the badge inherits it. It therefore stood small and squashed -- reported as "zerquetscht", and
   it looked like a size problem while it was an inherited line height. */
.tool-detail-flags {
    position: absolute;
    top: -0.6rem;
    right: 0.75rem;
    z-index: 2;
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 0.35rem;
    line-height: normal;
    /* So that several badges do not grow across the whole image. */
    max-width: 100%;
}

/* The edit link on the category text in the backend. Small and placed after it: the text is the
   statement, and the link only the way to it. `z-index` is not needed here -- the category head lies
   outside the cards and thereby outside every clickable surface. */
.category-intro-edit {
    margin-left: 0.5rem;
    font-size: 0.8rem;
    white-space: nowrap;
}

/* ══ Navigationsspalte: aufklappbarer Baum, dichter gesetzt (2026-08-17) ══════════════════════
   The occasion: with the 17 subcategories out of db/046 the bar became longer than the screen. Two
   measures together -- the subcategories collapsible, and the rows themselves tighter. Neither would have
   been enough alone: collapsed, 24 parent categories still stand there. */
.nav-list {
    gap: 0.1rem;
}

/* A row = a link plus an expand button. The button has to lie BESIDE the link and not in it: a parent
   category is a real category with a page of its own, and its title still leads there. */
.nav-row {
    display: flex;
    align-items: stretch;
    gap: 0.15rem;
}

.nav-row > a {
    flex: 1 1 auto;
    min-width: 0;
}

.nav-item a {
/* 0.65rem/0.75rem before -- with 41 entries that is over 200px of space alone. */
    padding: 0.35rem 0.6rem;
    gap: 0.55rem;
    line-height: 1.25;
}

/* The title may wrap but does not push the counter out of the row. */
.nav-title {
    min-width: 0;
    overflow-wrap: anywhere;
}

.nav-count {
    flex-shrink: 0;
    padding: 0.1rem 0.4rem;
}

.nav-emoji {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.15rem;
    font-size: 1rem;
    line-height: 1;
}

.nav-toggle {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 1.6rem;
    background: none;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    color: var(--text-dim);
    cursor: pointer;
    padding: 0;
    transition: color 0.2s ease, background 0.2s ease;
}
.nav-toggle:hover {
    color: var(--text-main);
    background: rgba(255, 255, 255, 0.06);
}
/* The arrow shows the STATE, not the action: down = open, right = closed. */
.nav-toggle svg {
    transition: transform 0.2s ease;
    transform: rotate(-90deg);
}
.nav-toggle[aria-expanded="true"] svg {
    transform: rotate(0deg);
}

/* The indentation now sits on the list rather than as inline padding on every link. The line on the left
   makes visible where a branch begins and ends -- with two levels and up to eleven siblings, indentation
   alone is too little. */
.nav-sublist {
    list-style: none;
    margin: 0.1rem 0 0.25rem 0.85rem;
    padding-left: 0.55rem;
    border-left: 1px solid var(--border-color);
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
}

.nav-sublist-collapsed {
    display: none;
}

.nav-item-child .nav-item a,
.nav-sublist .nav-item a {
    font-size: 0.83rem;
}

/* ══ Using the screen more widely (2026-08-17) ════════════════════════════════════════════════
   Up to here: `max-width: 1440px` at `padding: 0 2rem`. On a full-HD screen (1920px, the normal case at a
   desk today) 240px thereby stayed unused on the left and on the right each -- in addition to the 32px of
   inner spacing. Measured against the card grid that meant three columns rather than five.

   The margins stay, they only get smaller: a page whose content sticks to the window edge reads badly,
   and on a very wide screen an upper limit is still needed -- otherwise the lines of the category texts
   run into the unreadable.

   `.header-content`, `.app-container` and `.footer-content` MUST carry the same values. That is
   exactly what had already drifted apart once (see the comment at `.app-header`): the header area stood
   32px further out than the content below it.

   ── And the footer was never in this family (2026-09-01, reported) ─────────────────────────────
   It kept its own 1440px at 2rem while the header and the content had moved here, to 2100px at 1.25rem.
   Measured at a window width of 1600px: the category list began at 20, the left footer group at 105 --
   85 pixels off; on the right the content ended at 1565 and the right group at 1481. The same kind of
   deviation as back then, in the third place.

   That is why the footer now stands IN this rule and carries no numbers of its own any more: two places
   with the same values drift apart, three all the more. */
.header-content,
.app-container,
.footer-content {
    max-width: 2100px;
    padding-left: 1.25rem;
    padding-right: 1.25rem;
}

.app-container {
    gap: 1.5rem;
}

/* More cards beside one another rather than wider cards (the user's wish): the minimum width falls from
   320px to 280px. On 1920px that gives five columns rather than three, and on a laptop with 1366px still
   two -- `auto-fill` regulates that by itself; it stays a number, no condition. */
.cards-grid {
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: 1rem;
}

@media (max-width: 1200px) {
    .header-content,
    .app-container,
    .footer-content {
        padding-left: 1rem;
        padding-right: 1rem;
    }
}

/* ══ The header of a tool list: two halves (2026-08-17) ═══════════════════════════════════════
   The category on the left (the symbol, the title, the introductory text), the controls on the right.
   Before, the two stood one under another -- right at a page width of 1440px, wasteful at 2100px: an
   empty surface stayed beside the introductory text while one had to look down to filter.

   `align-items: start` matters: the two halves are of different heights, and without the line the shorter
   one would be stretched to the height of the longer. */
.list-head {
    display: grid;
    /*
     * No longer 50/50 since 2026-08-30. The user's wish, verbatim: the description text should
     * reach "fast bis zum Block rechts mit den Filtern und Suche". The right half needs only as
     * much as the filter buttons, the search field and the sorting need side by side -- everything
     * beyond that was air. `minmax` rather than fixed fractions: the control bar has a minimum
     * width below which it wraps, and it should keep that.
     */
    /* 1.4fr until 2026-09-05; the user wanted more air between the text and the box, so 1.15fr
       and a wider gap -- the intro's own cap (`max-width` below) shrinks with the column. */
    grid-template-columns: minmax(0, 1fr) minmax(26rem, 1fr);   /* 1.15fr until later on 2026-09-05 */
    align-items: start;
    gap: 3rem;
    margin-bottom: 1.75rem;
}

/* `min-width: 0` is no subtlety here but the condition for the 50/50 split holding at all: a grid child
   has `min-width: auto` and does NOT shrink below the width of its content. With three filter buttons, a
   search field and a selection menu, the control bar is wider than half a page -- it thereby burst its
   column and ran out of the picture to the right. */
.list-head > * {
    min-width: 0;
}

.list-head-info .category-header {
    margin-bottom: 0.75rem;
    padding-bottom: 0;
    border-bottom: none;
}

.list-head-info .category-intro {
    margin: 0;
    /*
     * The column is the limit now, no longer the 60 characters (2026-08-30). The cap stays all the
     * same, only higher up: on a very wide screen there would otherwise be lines of 140 characters,
     * and at that length the eye no longer finds the beginning of the next one. 100 characters is
     * the point at which the column is just exhausted on a 1440 screen. 85 since 2026-09-05,
     * with the narrower column; 75 since the column shrank once more the same day.
     */
    max-width: 75ch;
}

/* Below this width one under the other again: two 50 % columns give rows of under 400px here, and neither
   flowing text nor a row of filters is readable in that. The limit deliberately lies ABOVE the breakpoint
   of the page grid (1024px) -- the navigation column only disappears there, so the content area is
   noticeably narrower before that already. */
@media (max-width: 1100px) {
    .list-head {
        grid-template-columns: 1fr;
        gap: 1rem;
    }
}

/* ══ The bar in two rows ══════════════════════════════════════════════════════════════════════
   Row 1 only filters -- deliberately alone, so that further ones can be added without the search and the
   sorting travelling. Row 2 the search (wide) and the sorting (narrow). */
.toolbar {
    display: flex;
    flex-direction: column;
/* `align-items` stands at `center` further up -- in a ROW that meant vertically centred, in a column it
   means horizontally centred. That is exactly why the filters, the search field and the sorting stood
   centred in the box rather than beginning on the left: the same property, but a different axis, because
   the bar has been a column since 2026-08-17. */
    align-items: stretch;
    justify-content: flex-start;
    gap: 0.75rem;
    padding: 0.9rem 1rem;
    margin-bottom: 0.5rem;
}

.toolbar-row {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: 0.75rem;
    flex-wrap: wrap;
}

/* The search row does NOT wrap: the search field and the sorting should together always fill the full
   width of the box (the user's wish). The search field takes the rest, and the sorting keeps its width --
   a selection menu that adapts to the width jumps at every change of window. */
.toolbar-row-search {
    flex-wrap: nowrap;
    margin-top: 1rem;   /* the filter rows sit closer to each other than to the search (2026-09-05) */
}

/* The search field and the sort menu: one height, one ground (2026-09-05, the user's wish). Both
   are 2.6rem tall, both stand on `--bg-input`, both carry the same border and radius. */
.toolbar-row-search .search-input,
.toolbar-row-search .sort-select {
    height: 2.6rem;
    background: var(--bg-input);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    font-size: 0.9rem;
    box-sizing: border-box;
}
.toolbar-row-search .sort-select {
    padding: 0 2rem 0 0.8rem;
}

.toolbar-row-search .search-box {
    flex: 1 1 auto;
    min-width: 0;
}

.toolbar-row-search .sort-box {
    margin-left: 0;   /* the row orders itself now; no more pushing is needed */
    flex: 0 0 auto;
}

/* One icon on the right: the magnifier while the field is empty, the cross as soon as it holds
   text (2026-09-05). The field is a plain text field, so the browser draws no second cross. */
.search-input {
    padding: 0 2.6rem 0 1rem;
}
.search-box .search-icon {
    left: auto;
    right: 0.9rem;
}
.search-box.has-text .search-icon {
    display: none;
}
.search-box .search-clear {
    display: none;
}
.search-box.has-text .search-clear {
    display: flex;
}

.search-clear {
    position: absolute;
    right: 0.5rem;
    top: 50%;
    transform: translateY(-50%);
    display: flex;
    align-items: center;
    justify-content: center;
    width: 1.5rem;
    height: 1.5rem;
    padding: 0;
    background: none;
    border: none;
    border-radius: 50%;
    color: var(--text-dim);
    cursor: pointer;
    transition: color 0.2s ease, background 0.2s ease;
}
.search-clear:hover {
    color: var(--text-main);
    background: rgba(255, 255, 255, 0.08);
}
.search-clear[hidden] {
    display: none;
}

/* ══ The view switch: small and restrained (2026-08-17) ═══════════════════════════════════════
   Two symbols rather than two labelled buttons. It only changes the presentation and stood there as wide
   as a filter before. */
.view-toggle-row {
    display: flex;
    justify-content: flex-end;
}

.view-toggle {
    padding: 2px;
    gap: 2px;
}

.view-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0.3rem 0.45rem;
    line-height: 0;
}

/* Active = a filled surface with light lines, inactive = empty with muted lines. The state thereby lies
   in the contrast and not in a colour -- it stays recognisable with colour blindness and
   im hellen Thema erkennbar. */
.view-btn.active {
    background: var(--text-main);
    color: var(--bg-dark);
}
html[data-theme="light"] .view-btn.active {
    background: #1f2937;
    color: #ffffff;
}

/* The switch has sat in the header area since 2026-08-17, no longer in the heading row. */
.category-header .view-toggle,
.category-header-row .view-toggle {
    margin-left: 0;
}

/* ══ Aufklapp-Pfeil links (2026-08-17) ════════════════════════════════════════════════════════
   On the right it stood between the title and the counter and pushed the column of numbers inwards at
   every entry with subgroups. On the left it costs the same width everywhere -- entries without children
   get a placeholder, as otherwise their symbols would stand further left than the rest. */
.nav-toggle,
.nav-toggle-spacer {
    flex: 0 0 1.05rem;
    width: 1.05rem;
}

.nav-toggle-spacer {
    display: block;
}

.nav-row {
    align-items: center;
    gap: 0.1rem;
}

.nav-toggle {
    align-self: stretch;
    height: auto;
}

/* ══ The filters are checkboxes now (2026-08-17) ══════════════════════════════════════════════
   Visually unchanged buttons, technically what they are -- only that way does the bar work as a form
   without JavaScript too. The box itself is hidden but stays in the focus flow and present for screen
   readers; `appearance: none` alone would only make it invisible and
   trotzdem Platz belegen. */
.filter-btn {
    position: relative;
    user-select: none;
}
.filter-btn input[type="checkbox"] {
    position: absolute;
    opacity: 0;
    width: 100%;
    height: 100%;
    inset: 0;
    margin: 0;
    cursor: pointer;
}
/* The keyboard focus has to stay visible -- the box is invisible, the button around it is not. */
.filter-btn:focus-within {
    outline: 2px solid var(--accent-cyan);
    outline-offset: 2px;
}

.btn-apply {
    flex: 0 0 auto;
    background: var(--accent-indigo);
    color: #fff;
    border: 1px solid var(--accent-indigo);
    border-radius: var(--radius-md);
    padding: 0.5rem 0.9rem;
    font-family: inherit;
    font-size: 0.82rem;
    font-weight: 600;
    cursor: pointer;
}
.btn-apply[hidden] {
    display: none;
}

/* ══ The number of hits and the paging bar (2026-08-17) ═══════════════════════════════════════
   The number of hits stands above the list, not below it: with a filter set, a short list otherwise looks
   like an empty category, and nobody realises that a search is active. */
/* ── The switch on the left, the number of hits behind it (2026-09-01, the user's instruction) ─

   The spacing at the TOP takes over what used to hold the switch row below the filter box: the
   user said expressly that the distance between the lower edge of the search box and the card list
   should stay. It was measured and reproduced after the rebuild, not estimated. */
.list-summary-row {
    align-items: center;
    display: flex;
    gap: 0.75rem;
    margin: 1.1rem 0 0.9rem;
}

.list-summary {
    color: var(--text-muted);
    font-size: 0.85rem;
    /* The spacing sits on the row now, not on the paragraph -- otherwise the switch inside it
       would stand 0.9rem too high. */
    margin-bottom: 0;
}
.list-summary strong {
    color: var(--text-main);
}

.section-more {
    margin-top: 0.9rem;
    font-size: 0.88rem;
}
.section-more a {
    color: var(--accent-cyan);
    text-decoration: none;
    font-weight: 600;
}
.section-more a:hover {
    text-decoration: underline;
}

.pagination {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 0.3rem;
    margin: 2rem 0 1rem;
}

.pagination-num,
.pagination-step {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 2.1rem;
    padding: 0.35rem 0.6rem;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    color: var(--text-muted);
    text-decoration: none;
    font-size: 0.85rem;
    transition: all 0.2s ease;
}
.pagination-num:hover,
.pagination-step:hover {
    color: var(--text-main);
    background: rgba(255, 255, 255, 0.06);
}

.pagination-num.is-current {
    background: var(--accent-indigo);
    border-color: var(--accent-indigo);
    color: #fff;
    font-weight: 700;
}

/* The ends stay standing rather than disappearing: a bar that is a different width on the first page from
   the second jumps while paging. */
.pagination-step.is-disabled {
    opacity: 0.35;
    cursor: default;
}

.pagination-gap {
    padding: 0 0.2rem;
    color: var(--text-dim);
}

/* The status line of the loading. It stands above the paging bar and is mostly empty -- visible only
   during and shortly after a load. `aria-live` makes it audible for screen readers; without it, loading
   more would be nothing there. */
/* The one button after five automatically loaded pages (2026-09-05), and the sentinel the observer watches. */
.infinite-sentinel {
    height: 1px;
}
.load-more {
    display: block;
    margin: 1.5rem auto 0;
    padding: 0.6rem 1.4rem;
    background: var(--accent-indigo);
    color: #ffffff;
    border: none;
    border-radius: var(--radius-md);
    font: inherit;
    font-size: 0.9rem;
    cursor: pointer;
}
.load-more[hidden] {
    display: none;
}
.infinite-status {
    text-align: center;
    color: var(--text-muted);
    font-size: 0.85rem;
    margin-top: 1.5rem;
}
.infinite-status[hidden] {
    display: none;
}

/* ── The account area (2026-08-29, phase 5) ────────────────────────────────────────────────────
   Built exclusively out of the existing variables -- no colour of its own, no radius of its own.
   The area should look like the catalogue and not like a pack of forms glued on; and a colour
   fixed here stands out at the next change of theme. */

.account-page {
    max-width: 1200px;
    margin: 0 auto;
    padding: 2.5rem 1.5rem 4rem;
}

/* The four narrow forms (sign in, register, reset) stand centred and narrow: a sign-in form
   across the full width makes the fields look like a table. */
.account-card {
    max-width: 26rem;
    margin: 0 auto;
    background: var(--bg-card);
    backdrop-filter: var(--glass-blur);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-lg);
    padding: 2rem;
}

.account-title {
    font-size: 1.4rem;
    font-weight: 700;
    margin-bottom: 1.25rem;
}

.account-subtitle {
    font-size: 1.05rem;
    font-weight: 600;
    margin: 2rem 0 0.75rem;
}

.account-intro,
.account-hint {
    color: var(--text-muted);
    font-size: 0.85rem;
    line-height: 1.5;
    margin-bottom: 1rem;
}

.account-form {
    display: flex;
    flex-direction: column;
}

.account-label {
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--text-muted);
    margin-bottom: 0.35rem;
}

.account-input {
    background: var(--bg-input);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    color: var(--text-main);
    font: inherit;
    padding: 0.6rem 0.75rem;
    margin-bottom: 1rem;
    width: 100%;
}

.account-input:focus {
    outline: none;
    border-color: var(--border-highlight);
    box-shadow: var(--shadow-glow);
}

/* A hint belongs directly under its field, not under the spacing that follows it. */
.account-input + .account-hint {
    margin-top: -0.7rem;
}

.account-textarea {
    resize: vertical;
    min-height: 5rem;
    line-height: 1.5;
}

/* A line that only shows and takes nothing in (the e-mail address in the profile).
   Deliberately different from a field: a disabled input looks like a fault. */
.account-static {
    color: var(--text-main);
    margin-bottom: 1rem;
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.9rem;
}

.account-check {
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
    font-size: 0.85rem;
    line-height: 1.5;
    color: var(--text-muted);
    margin-bottom: 1.25rem;
}

.account-check input {
    margin-top: 0.15rem;
    flex-shrink: 0;
}

.account-check a {
    color: var(--accent-cyan);
}

.account-submit {
    background: var(--accent-indigo);
    border: none;
    border-radius: var(--radius-md);
    color: #fff;
    cursor: pointer;
    font: inherit;
    font-weight: 600;
    padding: 0.65rem 1.2rem;
    text-align: center;
    text-decoration: none;
}

.account-submit:hover {
    filter: brightness(1.1);
}

.account-submit-link {
    display: inline-block;
}

.account-submit-small {
    align-self: flex-start;
    padding: 0.45rem 0.9rem;
    font-size: 0.85rem;
}

.account-submit-danger {
    background: var(--accent-pink);
}

/* The honeypot. `position: absolute` and not `display: none`: a field that is not rendered at all
   is not filled in by a bot either -- and filling it in is precisely what it is meant to do. */
.account-trap {
    position: absolute;
    left: -9999px;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

/* The message after a redirect. Red and green differ in the border as well, not in the colour
   alone -- red-green blindness is the most common there is. */
.account-flash {
    border-radius: var(--radius-md);
    padding: 0.7rem 0.9rem;
    margin-bottom: 1.25rem;
    font-size: 0.9rem;
    line-height: 1.45;
}

.account-flash-error {
    background: rgba(236, 72, 153, 0.12);
    border-left: 3px solid var(--accent-pink);
}

.account-flash-ok {
    background: rgba(16, 185, 129, 0.12);
    border-left: 3px solid var(--accent-emerald);
}

.account-card-foot {
    margin-top: 1.5rem;
    padding-top: 1.25rem;
    border-top: 1px solid var(--border-color);
    font-size: 0.85rem;
    color: var(--text-muted);
}

.account-card-foot p {
    margin-bottom: 0.4rem;
}

.account-card-foot a {
    color: var(--accent-cyan);
}

.account-body {
    background: var(--bg-card);
    backdrop-filter: var(--glass-blur);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-lg);
    padding: 2rem;
    min-width: 0;
}

.account-greeting {
    font-size: 1.05rem;
    margin-bottom: 1.5rem;
}

/* ── The counters on the overview ──────────────────────────────────────────────────────────── */

/* The tiles are as wide as their text, not as wide as a grid (2026-08-31, reported).
   Before: `grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr))`. A grid divides the width
   into equal columns, and a 9rem minimum meant four columns inside the account container -- so the
   fifth tile wrapped although "Vorschlaege" would have fitted beside the others.

   Now flex with wrapping: `flex: 1 1 auto` without a `min-width`, so the minimum width is
   `min-content`. Because the caption does not wrap (`nowrap` further down), that is exactly the
   text width -- a tile never becomes narrower than its content, and wrapping happens only when the
   row really does run out. The `flex-grow: 1` distributes the rest so that the row does not break
   off left-aligned. */
.account-tiles {
    display: flex;
    flex-wrap: wrap;
    gap: 0.9rem;
}

.account-tile {
    background: var(--bg-input);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    color: var(--text-main);
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    gap: 0.2rem;
    /* The upper limit for the growing. Measured without it: as soon as the last tile wraps it is
       alone in its row and `flex-grow` pulls it to the WHOLE width -- 620 instead of 141 pixels, a
       bar across the page under four small tiles. With 14rem everything in a full row stays as
       measured (141 to 172 pixels, the limit never takes hold), and a wrapped tile stays a
       tile. */
    max-width: 14rem;
    padding: 0.8rem 1rem;
    text-decoration: none;
}

.account-tile:hover {
    border-color: var(--border-highlight);
}

.account-tile-count {
    font-size: 1.6rem;
    font-weight: 700;
    line-height: 1;
}

/* `nowrap`: without it the caption wraps INSIDE the tile, and the tile's minimum width would then
   be the longest WORD instead of the whole line -- the tiles would become far too narrow instead
   of moving to a new row. This is what keeps the wrapping where it belongs. */
.account-tile-label {
    color: var(--text-muted);
    font-size: 0.8rem;
    white-space: nowrap;
}

/* ── Listen (Merkliste, verwende ich, eigene Vorschlaege) ──────────────────────────────────── */

.account-list {
    list-style: none;
}

.account-list-item {
    align-items: flex-start;
    border-bottom: 1px solid var(--border-color);
    display: flex;
    gap: 1rem;
    justify-content: space-between;
    padding: 0.85rem 0;
}

.account-list-item:last-child {
    border-bottom: none;
}

.account-list-title {
    color: var(--text-main);
    font-weight: 600;
    text-decoration: none;
}

.account-list-title:hover {
    color: var(--accent-cyan);
}

.account-list-desc {
    color: var(--text-muted);
    font-size: 0.85rem;
    line-height: 1.45;
    margin-top: 0.2rem;
}

.account-list-link {
    color: var(--text-dim);
    display: block;
    font-size: 0.8rem;
    word-break: break-all;
}

.account-list-date {
    color: var(--text-dim);
    font-size: 0.8rem;
    white-space: nowrap;
}

.account-badge {
    background: var(--bg-input);
    border: 1px solid var(--border-color);
    border-radius: 999px;
    color: var(--text-muted);
    font-size: 0.75rem;
    padding: 0.2rem 0.7rem;
    white-space: nowrap;
}

.account-empty {
    color: var(--text-muted);
    font-size: 0.9rem;
    padding: 1.5rem 0;
}

/* ── The support thread ───────────────────────────────────────────────────────────────────────
   A <details> since 2026-08-31: the subject first, the content on a click. After three enquiries
   the open version was a page one scrolls through to find the fourth. */

.account-thread {
    border-top: 1px solid var(--border-color);
    margin-top: 1.25rem;
    padding-top: 0.75rem;
}

.account-thread-head {
    align-items: center;
    cursor: pointer;
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
    list-style: none;
    padding: 0.2rem 0;
}
.account-thread-head::-webkit-details-marker {
    display: none;
}

/* An arrow of our own instead of the default marker: the default looks different from browser to
   browser, and the line should look the same in both. */
.account-thread-head::before {
    color: var(--text-dim);
    content: '›';
    display: inline-block;
    font-size: 1.1rem;
    line-height: 1;
    transition: transform 0.15s ease;
}
.account-thread[open] > .account-thread-head::before {
    transform: rotate(90deg);
}

.account-thread-subject {
    color: var(--text-main);
    font-weight: 600;
    /* Takes the space the two labels beside it leave over -- otherwise the state and the date
       would stand in changing places, depending on the length of the subject. */
    flex: 1 1 12rem;
    min-width: 0;
}

.account-thread-body {
    padding-top: 0.75rem;
}

/* The state: open in amber, settled in green. The colour is not the only statement -- the text
   beside it says the same. */
.account-badge-open {
    border-color: var(--accent-amber);
    color: var(--accent-amber);
}
.account-badge-done {
    border-color: var(--accent-emerald);
    color: var(--accent-emerald);
}

/* One's own message and the reply differ by indent AND border colour -- the indent alone
   disappears on a phone, the colour alone says nothing to a screen reader. That is why the caption
   ("Du" / "Antwort") stands written out above them. */
.account-turn {
    border-left: 3px solid var(--border-color);
    margin-bottom: 1rem;
    padding: 0.2rem 0 0.2rem 0.9rem;
}

.account-turn-reply {
    border-left-color: var(--accent-indigo);
    margin-left: 1.5rem;
}

.account-turn-head {
    align-items: baseline;
    color: var(--text-dim);
    display: flex;
    font-size: 0.78rem;
    gap: 0.6rem;
    margin-bottom: 0.3rem;
}

.account-turn-body {
    font-size: 0.92rem;
    line-height: 1.55;
}

.account-form-inline {
    margin-top: 1rem;
}

/* The deletion area stands at the end of the profile and is visibly set apart. */
.account-danger {
    border-top: 1px solid var(--border-color);
    margin-top: 2.5rem;
    padding-top: 0.5rem;
}

.account-danger p {
    color: var(--text-muted);
    font-size: 0.85rem;
    line-height: 1.55;
    margin-bottom: 1.25rem;
}

/* On a phone the navigation stands above the content and runs horizontally -- a vertical column
   with seven entries would push the content below the fold. */
@media (max-width: 720px) {
    .account-body,
    .account-card {
        padding: 1.25rem;
    }
}
/* ── The three writing actions on the tool page (2026-08-29, phase 5) ─────────────────────────── */

.tool-action-flash {
    background: rgba(16, 185, 129, 0.12);
    border-left: 3px solid var(--accent-emerald);
    border-radius: var(--radius-md);
    font-size: 0.88rem;
    margin: 1rem 0;
    padding: 0.6rem 0.8rem;
}

.tool-action-hint {
    color: var(--text-muted);
    font-size: 0.88rem;
    margin: 1rem 0;
}

.tool-action-hint a {
    color: var(--accent-cyan);
}

.tool-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin: 1rem 0 0.75rem;
}

/* Every action is a form of its own -- without this line they would stand one under the other. */
.tool-action-form {
    display: inline;
}

.tool-action-btn {
    background: var(--bg-input);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    color: var(--text-muted);
    cursor: pointer;
    font: inherit;
    font-size: 0.85rem;
    padding: 0.4rem 0.9rem;
}

.tool-action-btn:hover {
    border-color: var(--border-highlight);
    color: var(--text-main);
}

/* Set: the border carries the statement too, not the text colour alone. */
.tool-action-btn.is-on {
    background: rgba(99, 102, 241, 0.15);
    border-color: var(--accent-indigo);
    color: var(--text-main);
}

.tool-rate-form {
    align-items: center;
    display: flex;
    gap: 0.6rem;
    margin-bottom: 1rem;
}

.tool-rate-label {
    color: var(--text-muted);
    font-size: 0.85rem;
}

.tool-rate-stars {
    display: inline-flex;
    gap: 0.1rem;
}

/* The five stars are buttons and look nothing like it -- the border falls away, the cursor and
   the colour stay. A visible button border around every star would turn the row into a
   toolbar. */
.tool-rate-star {
    background: none;
    border: none;
    color: var(--text-dim);
    cursor: pointer;
    font-size: 1.35rem;
    line-height: 1;
    padding: 0 0.05rem;
}

.tool-rate-star:hover,
.tool-rate-star.is-filled {
    color: var(--accent-amber);
}

/* The keyboard focus MUST stay visible: without a border the star would otherwise be the only
   control on the page that cannot be found with the keyboard. */
.tool-rate-star:focus-visible {
    outline: 2px solid var(--accent-indigo);
    outline-offset: 2px;
}

.comment-form {
    display: flex;
    flex-direction: column;
    margin-bottom: 1.5rem;
}

.comment-form-label {
    color: var(--text-muted);
    font-size: 0.8rem;
    font-weight: 600;
    margin-bottom: 0.35rem;
}

.comment-form-input {
    background: var(--bg-input);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    color: var(--text-main);
    font: inherit;
    line-height: 1.5;
    margin-bottom: 0.75rem;
    padding: 0.6rem 0.75rem;
    resize: vertical;
}

.comment-form-input:focus {
    border-color: var(--border-highlight);
    box-shadow: var(--shadow-glow);
    outline: none;
}

.comment-form-submit {
    align-self: flex-start;
    background: var(--accent-indigo);
    border: none;
    border-radius: var(--radius-md);
    color: #fff;
    cursor: pointer;
    font: inherit;
    font-weight: 600;
    padding: 0.45rem 1.1rem;
}

.comment-form-submit:hover {
    filter: brightness(1.1);
}

/* An account marked for deletion appears as a deleted user -- in italics and pale, so that it
   does not look like a display name. */
.comment-author-gone {
    color: var(--text-dim);
    font-style: italic;
    font-weight: 400;
}
/* ══ The rebuild of the evening of 29.08. ═════════════════════════════════════════════════════
   The account area now stands beside the category navigation and no longer beside one of its own.
   `.account-page-wide` and `.account-nav` are gone with that; what remains is `.account-body` as a
   card in the content column. */

/* The four narrow forms (sign in, register, reset, confirm) stand centred in the content column.
   `.account-page` no longer carries an outer spacing of its own -- `.content-area` gives that
   now. */
.account-narrow {
    max-width: 26rem;
    margin: 2rem auto;
}

/* Two fields side by side, half and half (the two password fields). On narrow screens one under
   the other -- two password fields at 40 per cent width each are unusable on a phone. */
.account-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1rem;
}

.account-col {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

@media (max-width: 560px) {
    .account-row {
        grid-template-columns: 1fr;
        gap: 0;
    }
}

/* The five counters, set apart under the form. Each leads to the menu entry that shows what it
   counts -- which is why they are links without exception and there is no dead tile any more. */
.account-stats {
    border-top: 1px solid var(--border-color);
    margin-top: 2rem;
    padding-top: 0.5rem;
}

/* A hint pointing at something outstanding (an address requested but not yet confirmed; one's own
   comment that is hidden). Amber and not red: it is not a fault but a state. */
.account-hint-pending {
    color: var(--accent-amber);
}

/* The second button on the sign-in page ("Register"). Visible as a button, but at a second level
   -- the same colour twice would leave open which one is the one being looked for. */
.account-submit-secondary {
    background: transparent;
    border: 1px solid var(--accent-indigo);
    color: var(--text-main);
    display: inline-block;
}

.account-submit-secondary:hover {
    background: rgba(99, 102, 241, 0.15);
    filter: none;
}

/* ── Taking part on the tool page: the comment left, rating and marks right ─────────────────
   Drawn that way by the user. The comment field gets the room because that is where one types;
   on the right stands narrowly what is a single click. */
.tool-participate {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 15rem;
    gap: 1.5rem;
    /* `stretch` and not `start` (30.08., the user's wish): the right column should reach the same
       height as the comment field on the left. With `start` it ended after its three boxes and
       left a hole underneath. */
    align-items: stretch;
    margin-bottom: 1.5rem;
}

.tool-participate-side {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
}

/* Since 2026-08-31 the caption stands ABOVE the star box and carries the same class as the one on
   the left. The spacing below it comes from its own margin and NOT from the column's gap --
   otherwise it would stand 10 pixels above its box and the three boxes would have unequal spacings
   again. */
.tool-participate-side > .comment-form-label {
    margin-bottom: calc(0.35rem - 0.6rem);
}

/* ── The three boxes on the right (set anew on 2026-08-31) ───────────────────────────────────
   Before, they shared the height in equal parts (`flex: 1 1 0`). That did make the column as tall
   as the left one, but it inflated the boxes themselves -- the star box got a padding nobody
   wanted, and between the two buttons stood a gap that was reported.

   Now they keep their natural height and stand with EQUAL spacings one under the other. The shared
   top edge with the comment field is made by `align-items: stretch` on the grid anyway; whatever
   stays free below that stays free -- which is more honest than three stretched boxes. */
.tool-participate-side > * {
    flex: 0 0 auto;
}

/* The star row stands on the right above the marks and carries a box of its own so that it stands
   apart from the two buttons below it -- otherwise the five stars would read as a third
   button. */
.tool-participate-side .tool-rate-form {
    background: var(--bg-input);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    align-items: center;
    margin-bottom: 0;
    /* Smaller padding (2026-08-31, reported): since the caption stands above it, the box holds
       nothing but the five stars -- and for that it was far too roomy. */
    padding: 0.35rem 0.7rem;
}

/* The two marks fill the narrow column and stand left-aligned with symbol and text -- two buttons
   of differing width one under the other look like a fault in the wrapping. */
.tool-participate-side .tool-action-form {
    display: block;
}

.tool-participate-side .tool-action-btn {
    align-items: center;
    display: flex;
    gap: 0.45rem;
    justify-content: flex-start;
    /* The same padding as the star box above -- otherwise the spacings are equal and the boxes
       differ in height, which comes to the same thing. */
    padding: 0.5rem 0.7rem;
    width: 100%;
}

/* A taller field, so that the block does not look squat -- and because the three boxes on the
   right come to about 170 px of natural height together, the row would otherwise be dictated from
   the RIGHT. */
.tool-participate .comment-form-input {
    min-height: 11rem;
}

/* ── And that was the actual difference (30.08., measured) ────────────────────────────────────
   `.comment-form` brings a `margin-bottom: 1.5rem` with it -- sensible where the form stands on
   its own, but not here: in a grid the outer margin counts towards the height of the grid item.
   The form was 254 px tall, the item 278, and the right column stretched to 278 -- visible as 24 px
   of overhang at the bottom.

   It looked like a fault in the stretching and was one in the margin. Found by measuring in the
   browser, not by reading. */
.tool-participate .comment-form {
    margin-bottom: 0;
}

@media (max-width: 820px) {
    .tool-participate {
        grid-template-columns: 1fr;
    }
    /* One under the other, rating belongs ABOVE: it is the smaller step, and whoever only wants
       to rate should not have to scroll past a text field. */
    .tool-participate-side {
        order: -1;
        flex-direction: row;
        flex-wrap: wrap;
    }
    /* Side by side there is no shared height left to fill. */
    .tool-participate-side > * {
        flex: 1 1 12rem;
    }
    .tool-participate-side .tool-action-form {
        flex: 1 1 12rem;
    }
}
/* ══ The large view of the image gallery (2026-08-31) ═════════════════════════════════════════
   Built on `<dialog>`: the browser brings Escape, the focus trap, `inert` for everything behind it
   and the dimmed surface along as a real element. All that stands here is how it looks. */

.tool-lightbox {
    background: transparent;
    border: none;
    height: 100%;
    max-height: 100%;
    max-width: 100%;
    padding: 0;
    width: 100%;
}

.tool-lightbox::backdrop {
    background: rgba(3, 6, 14, 0.88);
    backdrop-filter: blur(3px);
}

.tool-lightbox-figure {
    align-items: center;
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    height: 100%;
    justify-content: center;
    margin: 0;
    /* The margin keeps the image clear of the buttons -- without it the back arrow lies on the
       image and one clicks beside it while aiming. */
    padding: 3.5rem 4.5rem;
    pointer-events: none;   /* clicks go to the dialog: clicking beside it closes. */
}

.tool-lightbox-figure img {
    border-radius: var(--radius-md);
    box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
    max-height: 100%;
    max-width: 100%;
    object-fit: contain;
    pointer-events: auto;   /* ... only the image itself swallows them. */
}

.tool-lightbox-figure figcaption {
    color: var(--text-muted);
    font-size: 0.85rem;
    font-family: 'JetBrains Mono', monospace;
}

.tool-lightbox-close,
.tool-lightbox-nav {
    background: rgba(255, 255, 255, 0.08);
    border: 1px solid var(--border-color);
    border-radius: 999px;
    color: var(--text-main);
    cursor: pointer;
    line-height: 1;
    position: absolute;
    z-index: 2;
}

.tool-lightbox-close:hover,
.tool-lightbox-nav:hover {
    background: rgba(255, 255, 255, 0.18);
}

.tool-lightbox-close {
    font-size: 1.6rem;
    height: 2.4rem;
    right: 1.25rem;
    top: 1.25rem;
    width: 2.4rem;
}

.tool-lightbox-nav {
    font-size: 2rem;
    height: 3rem;
    top: 50%;
    transform: translateY(-50%);
    width: 3rem;
}

.tool-lightbox-prev { left: 1.25rem; }
.tool-lightbox-next { right: 1.25rem; }

@media (max-width: 640px) {
    .tool-lightbox-figure {
        padding: 3.5rem 1rem;
    }
    /* On a phone the arrows move to the bottom: at the sides they would be as large as half the
       image. */
    .tool-lightbox-nav {
        top: auto;
        bottom: 1.25rem;
        transform: none;
    }
    .tool-lightbox-prev { left: 25%; }
    .tool-lightbox-next { right: 25%; }
}

/* ══ The width of the account forms (2026-08-31, reported) ════════════════════════════════════
   They ran to the right edge of the content column. On a 1440 screen that looked like alignment;
   on a 5K2K it looked like a form field a metre long.

   ONE number for all of them -- expressly wished for that way ("mache alle wieder gleich breit").
   It therefore stands as a variable and not three times as a number: the next page in the account
   area should inherit it rather than invent its own. */
:root {
    --account-max-width: 52rem;
}

.account-body {
    max-width: var(--account-max-width);
    /* On the same axis as the bar above it. Without that the bar would stand centred and the form
       left, and the two would look as though they did not belong together.

       The user had praised the old right alignment ("schoen rechtsbuendig mit den buttons oben") --
       but that came from the box filling the whole column, and that was exactly the complaint. With
       a cap one has to decide: stick right, stick left, or centre. Centred, because the account
       area is a document and not an accessory at the edge. */
    margin-inline: auto;
}

/* The four narrow forms stay narrow -- a sign-in form of 52rem would be the other end of the same
   mistake. They stand in the same frame though, so they are centred within it. */
.account-narrow {
    max-width: 26rem;
}

/* ══ The actions as a bar, centred above the content (2026-08-31, the user's wish) ════════════
   The same entries as in the pulldown under the name. Deliberately SMALL and with no button
   character -- as links with a `|` between them: it is a signpost and not a toolbar, and two rows
   of buttons above each other (the pulldown top right, the bar here) would look like two offerings
   instead of one in two places. */
/* Since 2026-08-31 it stands IN the header, between the logo and the controls -- the header is
   `sticky`, so it stays put. As a row of its own below it, it scrolled away, and that was exactly
   the complaint.

   `flex: 1` takes the room between the two fixed ends, `justify-content: center` sets the entries
   centred within it. Without the `min-width: 0` the bar would be as wide as its content and would
   push the buttons on the right out of the picture on narrow screens. */
.account-bar {
    align-items: center;
    color: var(--text-dim);
    display: flex;
    flex: 1;
    /* Do NOT wrap (2026-08-31, measured): at 1280 pixels the bar went to two lines and the header
       thereby to 47 instead of 24 pixels tall -- it looked as though it had strained itself. Either
       it fits on one line or it is gone; the threshold below decides that. */
    flex-wrap: nowrap;
    font-size: 0.82rem;
    gap: 0.3rem 0.7rem;
    justify-content: center;
    margin: 0 1rem;
    min-width: 0;
    white-space: nowrap;
}

/* Below this width there is no room in the header for three things side by side. The bar is what
   falls away then -- not the buttons: the same entries stand in the pulldown under the name, and
   that stays.

   1400 and not 1100: measured, the six entries need about 640 pixels, the logo and the buttons 260
   each, plus the spacings -- below roughly 1320 it no longer works out. At 1100 the bar would still
   be there and on two lines, and a header that strains itself is worse than one without a bar. */
@media (max-width: 1400px) {
    .account-bar {
        display: none;
    }
}

.account-bar a {
    color: var(--text-muted);
    text-decoration: none;
}

.account-bar a:hover {
    color: var(--text-main);
    text-decoration: underline;
}

.account-bar a.is-current {
    color: var(--text-main);
    font-weight: 600;
}

/* The separator stands in the CSS and not in the markup: a `|` as a text node would be a character
   a screen reader reads out, and it would stand there even when the bar wraps. */
.account-bar a + a::before {
    color: var(--border-color);
    content: '|';
    margin-right: 0.75rem;
}
/* ── The sharing bar (2026-09-01, the user's instruction) ──────────────────────────────────────

   Quiet, single-coloured buttons and no brand colours: six coloured tiles side by side would draw
   the eye more strongly than the button to the vendor's page beside them, and that button is the
   reason somebody is on this page at all. The brand is recognisable by its shape; its red is not
   needed for that.

   On hover the button takes the accent colour -- the same feedback as everywhere else in the
   catalogue. */
.share-bar {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
}

.share-bar-label {
    color: var(--text-dim);
    font-size: 0.78rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    margin-right: 0.25rem;
    text-transform: uppercase;
}

.share-btn {
    align-items: center;
    background: none;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    color: var(--text-muted);
    cursor: pointer;
    display: inline-flex;
    height: 2rem;
    justify-content: center;
    padding: 0;
    transition: color 0.2s ease, border-color 0.2s ease, background 0.2s ease;
    width: 2rem;
}

.share-btn:hover,
.share-btn:focus-visible {
    background: var(--bg-card-hover);
    border-color: var(--border-highlight);
    color: var(--accent-indigo);
}

/* The feedback after copying. It holds for 1.6 seconds (see assets/share-bar.js) -- long enough to
   be seen, short enough not to be mistaken for a state. */
.share-btn-copy.is-copied {
    background: var(--accent-emerald);
    border-color: var(--accent-emerald);
    color: #ffffff;
}

/* ── A content page's headline row sits tighter (2026-09-01, the user's instruction) ───────────

   The instruction, VERBATIM and therefore left in German:

   *"versuche die ueberschrift (hier 'Blog') etwas weiter nach oben zu stellen, und den abstand
   darunter zum naechsten element etwas zu verringern."*

   Two different things, and both are space the tool page needs and a content page does not:

   - UPWARDS: the headline already stands flush with the category list (measured: both at 117), but
     the large type brings leading above its letters. `line-height: 1.15` instead of the inherited
     one pulls the cap height to the top edge of the box -- the LETTERS move up, not the box. A
     negative margin would have shifted the box and thereby destroyed the reference line to the
     category list.
   - BELOW: 2rem is the air that stands between the header and the image gallery on the tool page.
     Here a row of hits follows, and that does not need it.

   Only on the content pages, through a class of its own: `.tool-detail-header` carries the tool
   page as well, and nothing is too tight there. */
/* The blog's and the wiki's search stands centred between the heading and the "back to" link
   (2026-09-05, the user's wish): the form takes the room between the two and centres its field;
   below 720px it takes a row of its own. The count line under the heading gets more air to the list. */
.content-page-search {
    flex: 1 1 auto;
    display: flex;
    justify-content: center;
    padding: 0 1.5rem;
    min-width: 0;
}
.content-page-search .search-box {
    width: 100%;
    max-width: 30rem;
    min-width: 0;
}
.content-page-search .search-input {
    height: 2.6rem;
    padding: 0 2.6rem 0 1rem;
    background: var(--bg-input);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    font-size: 0.9rem;
    box-sizing: border-box;
}
.content-area > .list-summary,
#toolResults > .list-summary {
    margin-bottom: 1.5rem;
}
@media (max-width: 720px) {
    .content-page-head {
        flex-wrap: wrap;
    }
    .content-page-search {
        order: 3;
        flex-basis: 100%;
        padding: 0;
    }
}
.content-page-head {
    margin-bottom: 1rem;
}

.content-page-head .tool-detail-headline h1 {
    line-height: 1.15;
    margin-bottom: 0;
}

/* ── The way back, inside the headline row (2026-09-01) ────────────────────────────────────────

   At first it stood in a row of its own, where the breadcrumb trail had been. Reported: that left a
   hole, and the headline stood lower than the category list on the left.

   Now it sits IN `.tool-detail-header` -- a flex row with `align-items: flex-start`. No
   `margin-bottom` of its own any more: the headline row brings its own, and a second one would be
   exactly the space that was meant to go. `padding-top` holds it on the headline's baseline rather
   than at its top edge. */
.content-page-actions {
    font-size: 0.85rem;
    margin-left: auto;
    padding-top: 0.5rem;
    white-space: nowrap;
}

.content-page-actions a {
    color: var(--text-muted);
    text-decoration: none;
}

.content-page-actions a:hover {
    color: var(--text-main);
    text-decoration: underline;
}

/* ── Glossary, FAQ and wiki (2026-09-01) ──────────────────────────────────────────────────────

   Three pages that share the construction of the other content pages and get rules of their own
   only where their shape is a different one. */

/* The glossary's letter bar. It sticks to the top because, while paging through forty terms, it is
   the only way back -- otherwise one would have to scroll all the way up every time. */
.glossary-bar {
    background: var(--bg-dark);
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
    margin-bottom: 1.5rem;
    padding: 0.5rem 0;
    position: sticky;
    top: 0;
    z-index: 2;
}

.glossary-bar a {
    align-items: center;
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    color: var(--text-muted);
    display: inline-flex;
    font-size: 0.8rem;
    font-weight: 600;
    height: 1.9rem;
    justify-content: center;
    min-width: 1.9rem;
    padding: 0 0.35rem;
    text-decoration: none;
}

.glossary-bar a:hover {
    background: var(--bg-card-hover);
    border-color: var(--border-highlight);
    color: var(--text-main);
}

.glossary-group {
    margin-bottom: 2rem;
}

.glossary-group h2 {
    border-bottom: 1px solid var(--border-color);
    color: var(--accent-indigo);
    font-size: 1.1rem;
    margin: 0 0 0.75rem;
    padding-bottom: 0.35rem;
    /* The jump offset: without it the heading disappears under the sticky bar when a letter is
       clicked. */
    scroll-margin-top: 3.5rem;
}

/* Two columns: the term narrow and fixed, the explanation takes the rest. `dl` and not a table --
   it IS a description list, and a screen reader then reads it as pairs. */
.glossary-list {
    display: grid;
    gap: 0.5rem 1.5rem;
    grid-template-columns: minmax(8rem, 14rem) 1fr;
    margin: 0;
}

.glossary-list dt {
    color: var(--text-main);
    font-weight: 600;
    scroll-margin-top: 3.5rem;
}

.glossary-list dd {
    color: var(--text-muted);
    margin: 0;
}

/* Below this width the term and the explanation would stand in two columns that are too narrow. */
@media (max-width: 700px) {
    .glossary-list {
        grid-template-columns: 1fr;
        gap: 0.15rem;
    }
    .glossary-list dd {
        margin-bottom: 0.75rem;
    }
}

/* ── Die FAQ ───────────────────────────────────────────────────────────────────────────────── */

.faq-group {
    margin-bottom: 2rem;
}

.faq-group h2 {
    color: var(--accent-indigo);
    font-size: 1.05rem;
    margin: 0 0 0.75rem;
}

.faq-entry {
    background: var(--bg-card);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    margin-bottom: 0.5rem;
}

.faq-entry > summary {
    color: var(--text-main);
    cursor: pointer;
    font-weight: 600;
    list-style: none;
    padding: 0.85rem 1rem 0.85rem 2.25rem;
    position: relative;
}

/* Our own arrow instead of the browser's triangle: that looks different in every browser, and
   `list-style: none` above takes it away. `::marker` would be the clean way and is not yet
   supported everywhere. */
.faq-entry > summary::-webkit-details-marker {
    display: none;
}

.faq-entry > summary::before {
    content: '+';
    color: var(--accent-indigo);
    font-size: 1.1rem;
    left: 1rem;
    position: absolute;
}

.faq-entry[open] > summary::before {
    content: '−';
}

.faq-entry > summary:hover {
    color: var(--accent-indigo);
}

.faq-answer {
    border-top: 1px solid var(--border-color);
    padding: 1rem;
}

/* The answer inherits `.tool-detail-body` and with it that box -- which it does not need here, it
   is already inside one. */
.faq-entry .faq-answer.tool-detail-body {
    background: none;
    border-left: none;
    border-radius: 0;
    border-right: none;
    border-bottom: none;
    margin: 0;
    max-width: none;
}

/* ── Das Wiki ──────────────────────────────────────────────────────────────────────────────── */

.wiki-list {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
}

.wiki-card {
    background: var(--bg-card);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-lg);
    padding: 1rem 1.25rem;
    position: relative;
    transition: background 0.2s ease, border-color 0.2s ease;
}

.wiki-card:hover {
    background: var(--bg-card-hover);
    border-color: var(--border-highlight);
}

.wiki-card h2 {
    font-size: 1.05rem;
    margin: 0 0 0.35rem;
}

.wiki-card h2 a {
    color: var(--text-main);
    text-decoration: none;
}

.wiki-card:hover h2 a {
    text-decoration: underline;
}

/* The same construction as the blog cards: ONE link on the title, which covers the whole card
   through `::after`. The reasoning stands there -- the link text should be the title and not
   everything the card contains. */
.wiki-card-link::after {
    content: '';
    inset: 0;
    position: absolute;
    z-index: 1;
}

.wiki-card-teaser {
    color: var(--text-muted);
    font-size: 0.9rem;
    margin: 0;
}

.wiki-updated {
    color: var(--text-dim);
    font-size: 0.8rem;
    margin: 0 0 1.25rem;
}

/* ── The blog (2026-09-01) ────────────────────────────────────────────────────────────────────

   The cards use the same colour tokens as the tool cards, so that the blog does not look like a
   second place beside the catalogue. The cover image gets a fixed aspect ratio and `object-fit:
   cover`: otherwise the first image uploaded would decide how tall every row becomes. */
/* ── Cards as in the tool catalogue, three to a row (2026-09-01, the user's instruction) ───────

   Two attempts, two reports, and the second names the reason. Verbatim:
   *"die card breite soll fix in % vorgegeben sein, und nicht mal 100%, mal 50% usw"* -- and
   *"bild links und der text rechts davon"*.

   Hence `repeat(3, 1fr)` and NOT `auto-fit`. `auto-fit` divides the remaining width among the
   cards that EXIST: with one post the card becomes as wide as the page, with two, half each.
   That is exactly what was reported. Three fixed columns mean: a card is a third wide, whether
   there is one post or nine.

   The two thresholds below are not a relapse into the same thing: they change the number of
   columns at a fixed WINDOW WIDTH, not at the number of posts. At 1100 pixels a third would be
   about 300 pixels wide, and image and text no longer stand side by side in that. */
.blog-list {
    display: grid;
    gap: 1.25rem;
    grid-template-columns: repeat(3, 1fr);
}

@media (max-width: 1400px) {
    .blog-list {
        grid-template-columns: repeat(2, 1fr);
    }
}

@media (max-width: 900px) {
    .blog-list {
        grid-template-columns: 1fr;
    }
}

/* Image left, text right -- the same idea as a tool card, only larger.

   `position: relative` carries the click surface below it, `transition` the feedback on hover: a
   card that is clickable as a whole has to react as a whole, or one cannot tell that it is. */
.blog-card {
    background: var(--bg-card);
    border: 1px solid var(--border-color);
    border-radius: var(--radius-lg);
    display: flex;
    gap: 1rem;
    overflow: hidden;
    padding: 1rem;
    position: relative;
    transition: background 0.2s ease, border-color 0.2s ease;
}

.blog-card:hover {
    background: var(--bg-card-hover);
    border-color: var(--border-highlight);
}

/* ── A link that covers the whole card (2026-09-01, the user's instruction) ────────────────────

   The `::after` lies over the card; a click anywhere on it hits this link. In the document there is
   still only a single link, and it is named after the post -- the reasoning for that stands in
   templates/pages/blog-list.php.

   `z-index: 1`: the surface has to lie above the image and the text, but neither of those needs a
   context of its own for that. Without the value the document order decides, and the heading stands
   BEFORE the teaser -- the last paragraph would then lie on top and swallow the click.

   With `:focus-visible` it stays visible where the keyboard focus is: the outline belongs on the
   TITLE and not on the invisible surface, or it would frame half the page. */
.blog-card-link::after {
    content: '';
    inset: 0;
    position: absolute;
    z-index: 1;
}

.blog-card-link:focus-visible {
    outline: 2px solid var(--accent-indigo);
    outline-offset: 2px;
}

/* The image stands on the left and takes a good third of the card. `flex: 0 0 38%` instead of a
   fixed number of pixels: the card itself is already a third of the column, and a fixed value would
   look different in it on a 5K screen from on a laptop.

   Square it stays (the user's instruction) -- and `object-fit: cover` all the more: without it the
   first image uploaded would decide how tall every card in the row becomes. */
.blog-card-cover {
    display: block;
    flex: 0 0 38%;
    line-height: 0;
}

.blog-card-cover img {
    aspect-ratio: 1 / 1;
    border-radius: var(--radius-md);
    height: auto;
    object-fit: cover;
    width: 100%;
}

.blog-card-body {
    min-width: 0;
}

.blog-card h2 {
    font-size: 1.05rem;
    line-height: 1.3;
    margin: 0 0 0.35rem;
}

.blog-card h2 a {
    color: var(--text-main);
    text-decoration: none;
}

/* Underlined as soon as the CARD is hovered -- not just the title. Otherwise the feedback would
   indicate a smaller area than the click actually hits. */
.blog-card:hover h2 a {
    text-decoration: underline;
}

.blog-card-meta,
.blog-post-meta {
    color: var(--text-dim);
    display: flex;
    font-size: 0.8rem;
    gap: 0.75rem;
    margin: 0 0 0.5rem;
}

/* The dot stands in the CSS and not in the markup -- the same reasoning as for the separator in
   the account bar: a character in the text is read out by a screen reader. */
.blog-card-author::before {
    color: var(--border-color);
    content: '•';
    margin-right: 0.75rem;
}

.blog-card-excerpt {
    color: var(--text-muted);
    font-size: 0.9rem;
    margin: 0;
}

.blog-post-cover {
    margin: 0 0 1.5rem;
}

/* ── The `default` template: image top left, text around it (2026-09-01) ───────────────────────

   40 per cent, as asked for, and with `max-width` rather than a fixed width: in a narrow window
   40 % of little would be even less, and the text beside it would stand in a column of three words.

   `shape-outside` is deliberately absent -- the text should flow around the FRAME, not around the
   outline of the subject. With a photograph the second would be restless, and different for every
   image.

   Below the threshold the image stands at the top again with the text under it: a wrap needs room
   on both sides, and there is none there any more. */
.blog-post-cover-float {
    float: left;
    margin: 0.25rem 1.5rem 1rem 0;
    max-width: 40%;
    width: 40%;
}

.blog-post-cover-float img {
    border-radius: var(--radius-lg);
    display: block;
    height: auto;
    width: 100%;
}

/* The body has to ENCLOSE the floated image -- without that an image taller than the text sticks
   out of the bottom of the card. Measured on a short post with a tall photograph. */
.blog-post-body::after {
    clear: both;
    content: '';
    display: block;
}

@media (max-width: 700px) {
    .blog-post-cover-float {
        float: none;
        margin: 0 0 1.25rem;
        max-width: 100%;
        width: 100%;
    }
}

.blog-post-cover img {
    border-radius: var(--radius-lg);
    max-width: 100%;
}

.blog-post-tags {
    margin-top: 1.5rem;
}

.blog-post-back {
    margin-top: 1.5rem;
}

.blog-post-back a {
    color: var(--text-muted);
    font-size: 0.9rem;
    text-decoration: none;
}

.blog-post-back a:hover {
    color: var(--text-main);
    text-decoration: underline;
}

/* At the narrowest the image stands at the top again: side by side, a column of three words would
   be left for the text. */
@media (max-width: 520px) {
    .blog-card {
        flex-direction: column;
    }
    .blog-card-cover {
        flex: 0 0 auto;
        width: 100%;
    }
}

/* ── The main menu in the header (2026-09-01, the user's instruction) ──────────────────────────

   It stands between the logo and the account bar and is ALWAYS there -- signed out as well. Hence
   no `flex: 1`: the middle belongs to the account bar, this menu takes only what its two or three
   entries need, and pushes nothing away.

   Its threshold lies lower than the account bar's (1400): two entries need about 120 pixels, six
   need 640. A shared threshold would have taken the menu along although it still fits comfortably
   -- and a menu that is meant to be always visible would then be gone on half the devices in
   use. */
.main-menu {
    align-items: center;
    display: flex;
    flex: 0 0 auto;
    flex-wrap: nowrap;
    font-size: 0.82rem;
    gap: 0.3rem 0.7rem;
    /* Away from the logo, towards the middle between it and the account bar (2026-09-05, the
       user's wish): 1.5rem until then. Grows with the window, capped so that it never crowds the
       account bar. */
    margin-left: clamp(2rem, 7vw, 9rem);
    white-space: nowrap;
}

.main-menu a {
    color: var(--text-muted);
    text-decoration: none;
}

.main-menu a:hover {
    color: var(--text-main);
    text-decoration: underline;
}

.main-menu a.is-current {
    color: var(--text-main);
    font-weight: 600;
}

/* The same separator as in the account bar, in the CSS rather than the markup for the same
   reason. */
.main-menu a + a::before {
    color: var(--border-color);
    content: '|';
    margin-right: 0.75rem;
}

@media (max-width: 820px) {
    .main-menu {
        display: none;
    }
}

/* A button that looks like a link -- it stands in the middle of a sentence of guidance (the
   password suggestion, 2026-08-31) and as a button it would tear the sentence apart. A real
   `<button>` and not an `<a href="#">`: it does something and leads nowhere, and a link that leads
   nowhere is a lie to anybody who reads the address bar. */
.account-link-btn {
    background: none;
    border: none;
    color: var(--accent-cyan);
    cursor: pointer;
    font: inherit;
    padding: 0;
    text-decoration: underline;
}

.account-link-btn:hover {
    color: var(--text-main);
}

/* ══ Forward and back to the neighbouring tool (2026-08-31) ═══════════════════════════════════
   At the height of the breadcrumb trail, right-aligned to the column. `margin-left: auto` and not
   `float`: the trail is a flex container, and `float` would have taken it out of the flow. */
/* `.tool-breadcrumb .tool-neighbours` and not just `.tool-neighbours`: further up the trail sets
   `.tool-breadcrumb span { margin: 0 0.4rem }`, and with two class units that is more specific than
   a single class. The `auto` was overridden by it to 6.4 pixels -- the links stood 256 pixels short
   of the right edge instead of at it. Measured, not guessed. */
.tool-breadcrumb .tool-neighbours {
    display: flex;
    gap: 1rem;
    margin-left: auto;
    margin-right: 0;
    white-space: nowrap;
}

.tool-neighbours a {
    color: var(--text-muted);
    text-decoration: none;
}

.tool-neighbours a:hover {
    color: var(--accent-cyan);
    text-decoration: underline;
}

/* For that the trail itself has to be allowed to wrap: on a narrow screen either the trail or the
   two links would otherwise stand in nowhere. */
.tool-breadcrumb {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
}

/* ── The list of sources under a blog post (2026-09-02) ───────────────────────────────────────
   A numbered list, because a source citation has a number a text can refer to -- even where it does
   not do so yet. Set apart by a line and not by a card: it belongs to the post and is not a second
   element beside it. */
.blog-post-sources {
    margin-top: 2rem;
    padding-top: 1.25rem;
    border-top: 1px solid var(--border-color, rgba(128, 128, 128, 0.25));
}

.blog-post-sources h2 {
    font-size: 1rem;
    font-weight: 600;
    margin: 0 0 0.5rem;
    color: var(--text-muted, #6b7280);
}

.blog-post-sources ol {
    margin: 0;
    padding-left: 1.4rem;
}

.blog-post-sources li {
    margin-bottom: 0.3rem;
    /* Long addresses wrap rather than bursting the column. */
    overflow-wrap: anywhere;
}
