/* Tierheimfinder — Klassisch, part two: what lies in the lists.
 *
 * Part one is `app.css`: the two palettes, the reset, the header, the four
 * views, the filter row, the buttons and the front door. This file is the
 * animal card, the skeletons, the shelter list and its map, konto and
 * Merkliste, the footer and the widths.
 *
 * Every page that links `app.css` links this one straight after it — the public
 * page through `gestaltung.py`, and the four pages that link Klassisch by name,
 * `/admin`, `/qa`, `/analytics` and the 404 page. The order is not a
 * convenience: the media blocks near the end of this file override rules that
 * stand higher up in it, and a swapped link would turn that around.
 *
 * The split was made on 2026-08-04 (OP191), the day the file reached the
 * two-thousand-line ceiling of house rule 25. The three other looks were split
 * the same day and the same way.
 */

/* --------------------------------------------------------- the animal card */
/* There is no card. There is a photograph with three or four lines under it,
   on the cream. Two columns of 164px with an 8px gutter at 360: 164 + 8 + 164
   is 336, which is exactly the content width. */
.tier-gitter {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: var(--s3);
    row-gap: var(--s4);
}

/* A frame, at Georg's request, and a frame you can actually see.
   The first version used the decorative hairline, which measures 1.31:1 against
   the cream — that is what "decorative" means, and Georg's answer was that it
   was too gentle to find. The edge is the control tone now: 3.63:1 in Papier,
   3.81:1 in Abend, against the 3:1 a non-text boundary needs. The fill goes one
   step deeper too, to the sunk tone, and every text pair on it was re-measured
   rather than assumed — 13.89, 6.75 and 4.74 in Papier, 13.93, 7.18 and 5.04 in
   Abend, all clear of 4.5. Still no shadow and still no white: this is a frame,
   not the old card. */
.tier {
    /* 1420 of these; two lines and the largest rendering gain available. */
    content-visibility: auto;
    contain-intrinsic-size: auto 340px;
    display: flex;
    flex-direction: column;
    gap: var(--s2);
    padding: var(--s2);
    background: var(--bg-sunk);
    border: 1px solid var(--line-strong);
    border-radius: var(--r-control);
    /* The heart is pinned to the photograph's corner relative to this box. */
    position: relative;
}

.tier-haupt {
    display: flex;
    flex-direction: column;
    gap: var(--s2);
    text-decoration: none;
    color: inherit;
    /* Only so the found-animal strip below can sit on the photograph. */
    position: relative;
}

/* ------------------------------------------- Fundtiere, Vermittlungsfälle */
/* Two marks on one tile, on purpose. This one is for the eye going past at
   speed: a solid amber label on the corner of the photograph, which no
   photograph can wash out because the fill is opaque. It is `aria-hidden`, so a
   screen reader gets the sentence under the name once and not twice.

   Top left and not top right: the heart the account draws sits on the
   photograph's top-right corner, and the two would meet on a 164-pixel tile.

   Since 2026-08-05 a tile can wear a second mark instead — Vermittlungsfall,
   an animal a private person is rehoming through the shelter. Same place, same
   size, same weight, because it answers the same question ("what am I looking
   at?") and only the colour separates them: the amber is a warning and this is
   not one. Never both at once — `tierMarke` in sprache.js picks one. */
.tier-fund-band,
.tier-vermittlung-band {
    position: absolute;
    top: var(--s2);
    left: var(--s2);
    padding: 2px 8px;
    font-size: var(--t-xs);
    font-weight: 600;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    background: var(--fund);
    color: var(--on-fund);
    border-radius: var(--r-control);
    pointer-events: none;
}

/* And this one is for the eye that has stopped: the strip says what the animal
   is, the line says what that means for the person reading it. Above the breed,
   because it changes what the whole tile is about. A filled amber box, not a
   coloured line — it must not be read past. */
.tier-fund-hinweis,
.tier-vermittlung-hinweis {
    font-size: var(--t-sm);
    line-height: 1.4;
    font-weight: 600;
    background: var(--fund);
    color: var(--on-fund);
    padding: var(--s1) var(--s2);
    border-radius: var(--r-photo);
    overflow-wrap: break-word;
}

/* The only thing that separates the two marks, and it is deliberately the only
   thing: the amber says "do not fall for this one", and a Vermittlungsfall is
   an animal somebody really can take home. The house green says "read me"
   without saying "beware". Last, so it wins over the fill above. */
.tier-vermittlung-band,
.tier-vermittlung-hinweis {
    background: var(--brand);
    color: var(--on-brand);
}

.tier-foto {
    display: block;
    width: 100%;
    aspect-ratio: 1 / 1;
    object-fit: cover;
    /* A shelter photograph is a full-body shot with the head in the upper half,
       and a centred square crop takes the head off a standing dog. */
    object-position: 50% 35%;
    background: var(--bg-sunk);
    border-radius: var(--r-photo);
    /* Not a shadow: a containment line drawn inside the image, so it costs no
       layout and stops a pale photo bleeding into the cream. */
    box-shadow: inset 0 0 0 1px var(--photo-edge);
}
.tier-haupt:hover .tier-foto { filter: brightness(1.05); }
.tier-haupt:focus-visible .tier-foto { filter: brightness(1.05); }

/* 1.6% of animals. Type doing an icon's job: the initial letter, not a grey
   paw on a grey gradient at 1.30:1. */
.tier-leer {
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: var(--t-xl);
    font-weight: 600;
    color: var(--ink-3);
}

/* A tile whose photograph has not been fetched yet (OP181): the mark the sweep
   in app.js looks for, and the one the heart rules below step around. The
   letter under it is a stand-in and not text — unlike the permanent one above
   it is gone a second later, and a selection dragged across the grid must not
   copy an initial out of every tile that had not loaded yet. */
.tier-foto-wartet { user-select: none; }

/* The name's row: the name, the age where one is known, and the share control
   far right. The waiting time sits one line down, at the breed's right — at
   360 a tile is 164 pixels and all four on one line left the name as "T…".
   The name gives way, on one line with dots, never the control. */
.tier-zeile {
    display: flex;
    align-items: center;
    gap: var(--s2);
    min-width: 0;
    /* Anchors the two things a press of the share control can leave on the
       tile — the "Link kopiert" toast and the address field (OP187). */
    position: relative;
}

/* Far right of its row, and of its own line when it wraps to one. The breed's
   `margin-right: auto` only puts it right while the two share a line; on a
   narrow card the mark drops below and started at the left margin, so a tile
   reading "mind. 130 Tage" hung under the breed while every dated tile beside
   it kept its number in the right-hand column (Georg, 2026-08-06). */
.tier-marke {
    flex: none;
    margin-left: auto;
    font-size: var(--t-xs);
    color: var(--ink-3);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* The age directly after the name. It gives way completely before the name
   loses a pixel: flex-shrink 999 against the name's 1 means the age is
   squeezed to nothing first, and only then does the name start to ellipsize. */
.tier-alter {
    flex: 0 999 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    margin-right: auto;
    font-size: var(--t-xs);
    color: var(--ink-3);
    white-space: nowrap;
}

/* One line, dots when longer. Four tiles stand side by side, and every row of
   text must sit at the same height in all four — a wrapping name pushed its
   tile's breed and foot a line lower than its neighbours'. The dots keep
   OP80's promise: never a bare mid-word chop. It is an anchor here — a
   duplicate of the photograph's link, out of the tab order — and must not
   turn blue or underline for it. */
.tier-name {
    min-width: 0;
    overflow: hidden;
    font-size: var(--t-md);
    font-weight: 600;
    line-height: 1.25;
    letter-spacing: -0.011em;
    color: var(--ink);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
    text-decoration: none;
}
a.tier-name:hover { text-decoration: underline; }

/* On every tile — the breed, or the species where no breed is known — and one
   line with dots, for the same alignment reason as the name (OP80: never a
   bare chop). */
/* Breed left, and far right how long the animal has been waiting. The breed
   is the one that gives way, on one line with dots. */
/* The breed and the waiting mark share this row, and since 2026-08-04 the mark
   can be as long as "mindestens 87 Tage" — the marker no longer carries its
   date over it, so the hedge is written out. `flex-wrap` is what keeps that
   from eating the breed: on a 164-pixel card at 360 the two no longer fit on
   one line and the mark drops to its own, where before the breed was cut to a
   single letter and a dot. On a wide card they still stand side by side. */
.tier-rasse {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0 var(--s2);
    font-size: var(--t-sm);
    line-height: 1.4;
    color: var(--ink-2);
    white-space: nowrap;
    min-width: 0;
}

/* The floor is what makes the row above wrap at all: a flex item shrinks before
   its line wraps, so with `min-width: 0` the breed was squeezed to one letter
   and a dot beside a long mark instead of sending the mark to the next line.
   Eight characters is about "Mischling", the commonest breed on the site. */
.tier-rasse-wort {
    min-width: 8ch;
    overflow: hidden;
    text-overflow: ellipsis;
    margin-right: auto;
}

/* The shelter and its province, one line, dots when longer. This is also the
   only lawful cue that the photo link leaves the site: the house rule against
   "opens elsewhere" arrows is absolute, so the destination is named in words.
   min-height makes it a real touch target beside the large photo link. */
.tier-fuss {
    font-size: var(--t-sm);
    line-height: 1.4;
    color: var(--ink-2);
    min-height: 44px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.tier-heim { color: var(--brand); text-decoration: none; }
.tier-heim:hover { text-decoration: underline; }
.tier-ort { color: var(--ink-2); }

/* The shelter's website is down, or its animal list would not load. A line of
   its own directly under the shelter's name, and never a band across the
   photograph: there is exactly one band and a found animal has to keep it —
   "not for adoption at all" is the larger fact. Both links on the tile are
   untouched, which is the whole point: somebody can press one and look.

   Amber and heavier, exactly as `.heim-alt, .heim-fehler` further down this
   file: this look already spends `--warn` on a stale scrape, and it is the same
   class of fact. In `--ink-3` at normal weight — where this rule stood until
   2026-08-09 — the line was the caption grey of `.tier-alter`, `.tier-marke`
   and `.tier-zeit`, sitting under a foot in the darker `--ink-2`, so the one
   line saying the tile may be out of date was the quietest thing on it. The
   other three looks all chose amber at 600. */
.tier-stoerung {
    font-size: var(--t-xs);
    line-height: 1.35;
    color: var(--warn);
    font-weight: 600;
    overflow-wrap: break-word;
}

/* Langzeitgäste only: the listing age under the tile, the view's sort key and
   therefore the reason the visitor is there. Everywhere else the small marker
   beside the name carries the date. */
.tier-zeit {
    font-size: var(--t-xs);
    font-weight: 500;
    letter-spacing: 0.015em;
    color: var(--ink-3);
    font-variant-numeric: tabular-nums;
}

/* The heart, on the photograph's top-right corner and outside .tier-haupt — a
   button inside the photograph's <a> is invalid markup and does not click. It
   is positioned, so it also wins the hit-test over the positioned anchor
   underneath. No plate and no fill behind it — the operator's word — so the
   glyph stands on the photograph itself: white ink with a drop shadow on the
   strokes, which reads on a pale photo and a dark one alike. The found-animal
   band owns the top LEFT, so the two never meet.

   Off is the white outline; on is the filled heart in the brand green, the
   same fill an active pill already wears. Never opacity: a half-transparent
   icon reads as disabled rather than as off.

   44 pixels square. At 360 the tile column is 164 wide, so the heart is the
   icon alone and its word lives in the aria-label. */
.tier-merken {
    position: absolute;
    top: var(--s2);
    right: var(--s2);
    width: 44px;
    height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 0;
    background: transparent;
    border-radius: 12px;
    color: var(--foto-ink);
    filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.6));
    cursor: pointer;
}
.tier-merken svg { width: 22px; height: 22px; fill: none; stroke: currentColor;
                   stroke-width: 2; transition: transform var(--motion); }
.tier-merken:hover svg { transform: scale(1.12); }
/* On keeps the white outline around the brand fill: the light theme's brand
   is a near-black green, and a filled dark heart alone would sink into a dark
   photograph — the very failure the plate used to prevent. */
.tier-merken.gemerkt { color: var(--brand); }
.tier-merken.gemerkt svg { fill: currentColor; stroke: var(--foto-ink); }
/* While the click is in the air: still white, only paler ink — never the
   opacity property, which would read as disabled. */
.tier-merken.wartet { color: rgba(255, 255, 255, 0.7); }
/* The 1.6% of tiles with no photograph: the heart stands on the sunk cream
   there, where white ink and a shadow are the wrong tools. Keyed on the
   permanent letter only — a tile whose picture is merely pending wears
   `.tier-foto-wartet` beside `.tier-leer`, and its heart must hold the
   photograph colours from the first paint, or every heart on a first screenful
   would flip from dark to white the moment its picture lands. */
.tier:has(.tier-leer:not(.tier-foto-wartet)) .tier-merken { color: var(--ink-2); filter: none; }
.tier:has(.tier-leer:not(.tier-foto-wartet)) .tier-merken.gemerkt { color: var(--brand); }
.tier:has(.tier-leer:not(.tier-foto-wartet)) .tier-merken.gemerkt svg { stroke: currentColor; }
.tier:has(.tier-leer:not(.tier-foto-wartet)) .tier-merken.wartet { color: var(--ink-3); }

/* ----------------------------------------------------- Teilen (OP187) */
/* The share control beside the name. The negative vertical margins keep the
   name row at its text height, so the tiles stay as level as they were before
   the button; the auto right margin does the age's job when there is no age —
   it keeps the time marker far right and itself at the name. */
.tier-teilen {
    flex: none;
    align-self: center;
    width: 40px;
    height: 40px;
    /* The negative vertical margins keep the name row at its text height, so
       the tiles stay as level as they were before the button; the right one
       lets the icon sit at the tile's own edge. */
    margin: -10px -8px -10px 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 0;
    background: transparent;
    border-radius: var(--r-control);
    color: var(--ink-3);
    cursor: pointer;
}
.tier-teilen svg { width: 18px; height: 18px; }
.tier-teilen:hover { color: var(--ink); }

/* "Link kopiert", briefly, above the row that was pressed — over the
   photograph's lower edge, where it covers no text. */
.tier-teilen-hinweis {
    position: absolute;
    right: 0;
    bottom: calc(100% + var(--s1));
    padding: 2px var(--s2);
    font-size: var(--t-xs);
    font-weight: 600;
    background: var(--ink);
    color: var(--bg);
    border-radius: var(--r-control);
    pointer-events: none;
    white-space: nowrap;
}

/* The address itself, selectable, when neither share sheet nor clipboard is
   there. It stays until the next press or the next render sweeps it. */
.tier-teilen-feld {
    position: absolute;
    left: 0;
    right: 0;
    bottom: calc(100% + var(--s1));
    padding: var(--s1) var(--s2);
    font-size: var(--t-xs);
    border: 1px solid var(--line-strong);
    border-radius: var(--r-control);
    background: var(--bg);
    color: var(--ink);
}

/* One animal's own page (OP187): a single tile at a tile's width, never
   stretched to the page, and two lines under it — the shelter, the way back. */
.tier-seite { max-width: 24rem; }
.tier-seite .tier-gitter { grid-template-columns: minmax(0, 1fr); }
.tier-seite-heim,
.tier-seite-weiter {
    margin-top: var(--s4);
    font-size: var(--t-md);
    line-height: 1.5;
}

/* The scroll marker under a long list: one invisible pixel the observer
   watches. It needs real height — a zero-height element never intersects. */
.nachlade-marke { height: 1px; }

/* -------------------------------------------------------- group headings */
.gruppe {
    font-size: var(--t-md);
    font-weight: 600;
    letter-spacing: -0.011em;
    color: var(--ink);
    margin: var(--s4) 0 var(--s2);
    display: flex;
    align-items: baseline;
    gap: var(--s2);
}
/* The strips above end on a pill's own transparent border, so the first heading
   needs none of its own. */
.gruppe:first-child { margin-top: 0; }
.gruppe-zahl {
    font-size: var(--t-xs);
    font-weight: 500;
    color: var(--ink-3);
    font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------------- skeletons */
/* The exact geometry of the real cards, flat --bg-sunk, and no shimmer: a
   shimmer is motion for its own sake and precisely what a reduced-motion
   visitor is asking not to see. The spinner is deleted. */
.skelett { display: flex; flex-direction: column; gap: var(--s2); }
.skelett-foto {
    display: block;
    width: 100%;
    aspect-ratio: 1 / 1;
    background: var(--bg-sunk);
    border-radius: var(--r-photo);
}
.skelett-zeile {
    display: block;
    height: 13px;
    background: var(--bg-sunk);
    border-radius: var(--r-photo);
}
.skelett-kurz { width: 60%; }

/* ---------------------------------------------------------- shelter list */
.such-bar { margin-bottom: var(--s3); }
.such-bar .such-feld { display: block; }

/* All thirty shelters update in the same 03:00 run, so the timestamp belongs
   once, at the top. Thirty copies of it were 33 characters of noise a row and
   the direct cause of the badge splitting in half at 360 (OP87). */
.stand {
    font-size: var(--t-sm);
    color: var(--ink-3);
    font-variant-numeric: tabular-nums;
    margin-bottom: var(--s4);
}

.heim-gruppe { margin-bottom: var(--s5); }

.heim-ueberschrift {
    font-size: var(--t-xs);
    font-weight: 500;
    letter-spacing: 0.015em;
    text-transform: uppercase;
    color: var(--ink-3);
    padding-bottom: var(--s1);
    border-bottom: 1px solid var(--line);
}

.heim-liste { list-style: none; }

.heim-zeile {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--s3);
    padding: var(--s3) 0;
    border-bottom: 1px solid var(--line);
    text-decoration: none;
    color: inherit;
}
.heim-zeile:hover { background: var(--bg-sunk); }

.heim-text { display: block; min-width: 0; }
.heim-name {
    display: block;
    font-size: var(--t-md);
    font-weight: 600;
    letter-spacing: -0.011em;
    color: var(--ink);
}
.heim-ort { display: block; font-size: var(--t-sm); color: var(--ink-2); }
.heim-note { display: block; font-size: var(--t-sm); color: var(--ink-2); }
.heim-zahl {
    flex: none;
    font-size: var(--t-sm);
    color: var(--ink-3);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
/* A shelter whose scraper actually broke becomes visible instead of hiding
   among thirty identical timestamps. */
.heim-alt, .heim-fehler {
    font-size: var(--t-sm);
    color: var(--warn);
    padding-bottom: var(--s2);
}

.abschnitt-note {
    font-size: var(--t-sm);
    color: var(--ink-2);
    margin: var(--s2) 0 var(--s3);
}

/* ---------------------------------------------------------- shelter page */
/* Everything on this page starts at the same left edge: the title, the prose,
   the button, the stand line and — through `main .streifen` above — the pill
   rows under them. */
.heim-seite { max-width: 68ch; margin-bottom: var(--s4); }
.heim-titel {
    display: flex;
    align-items: center;
    gap: var(--s2);
    font-size: var(--t-xl);
    font-weight: 600;
    line-height: 1.15;
    letter-spacing: -0.02em;
    margin-bottom: var(--s3);
}
.heim-zeichen { flex: none; display: inline-flex; color: var(--brand); }
.heim-zeichen svg { width: 26px; height: 26px; fill: currentColor; }
.heim-prosa { margin-bottom: var(--s4); color: var(--ink-2); }
.heim-daten { margin-bottom: var(--s4); }
.heim-daten-zeile { margin-bottom: var(--s2); }
.heim-label { color: var(--ink-3); }
.heim-daten a { color: var(--brand); }
.heim-aktionen { margin-bottom: var(--s3); }
.heim-stand {
    font-size: var(--t-sm);
    color: var(--ink-3);
    font-variant-numeric: tabular-nums;
    margin-bottom: var(--s2);
}
.detail-error { color: var(--warn); margin-top: var(--s3); }

/* ------------------------------------------------- empty states and errors */
/* An empty screen is an invitation, not a dead end. */
.leer { padding: var(--s6) 0; }
.leer p { margin-bottom: var(--s3); }
.leer-aktionen { display: flex; gap: var(--s2); flex-wrap: wrap; margin-bottom: var(--s3); }
.leer-hinweis { font-size: var(--t-sm); color: var(--ink-3); }

.fehler-box { padding: var(--s6) 0; }
.fehler-box p { color: var(--warn); margin-bottom: var(--s4); }
.fehler-aktionen { display: flex; gap: var(--s2); flex-wrap: wrap; }
.fehler-aktionen .btn-more { margin: 0; }

/* ------------------------------------------------------- konto and merkliste */
/* The account control in the header, beside the colour-scheme button. It is not
   a fifth cell of the bottom navigation on purpose: below 1024 that bar gives
   each of its four items a quarter of the width, and a fifth turns it into a
   row that scrolls sideways. */
.kopf-konto {
    flex: none;
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    padding: 0 var(--s2);
    font-size: var(--t-sm);
    font-weight: 600;
    color: var(--on-brand);
    text-decoration: none;
    border: 1px solid transparent;
    border-radius: var(--r-control);
    /* A username can be 32 characters. The header is 52 pixels tall and has a
       wordmark and two controls to fit beside it. */
    max-width: 9rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.kopf-konto:hover { background: rgb(255 255 255 / .12); border-color: rgb(255 255 255 / .28); }

/* Bei 360 fehlte der Wortmarke genau die Breite, die der Gestaltungs-Knopf
   dazugenommen hat. Die andere Hälfte dieser Ausnahme — Abstand und Innenrand
   des Kopfes — steht in `app.css`; diese Regel muss hier stehen, weil
   .kopf-konto hier erklärt wird und die spätere Datei gewinnt. */
@media (max-width: 480px) {
    .kopf-konto { padding-left: 0; padding-right: 0; }
}

/* The one measure for a form. 62 characters is the reading measure the rest of
   the prose uses; a field wider than that is harder to scan, not easier. */
.konto-panel { max-width: 62ch; padding-top: var(--s4); }
.konto-panel h2 { font-size: var(--t-xl); margin-bottom: var(--s3); }
.konto-prosa {
    color: var(--ink-2);
    margin-bottom: var(--s5);
    /* An e-mail address is one unbreakable word, and the block about a
       waiting address change prints two of them. Without this the panel
       grew wider than a 360-pixel screen. */
    overflow-wrap: anywhere;
}

.feld { display: block; margin-bottom: var(--s4); }
.feld-label {
    display: block;
    font-size: var(--t-sm);
    font-weight: 600;
    color: var(--ink-2);
    margin-bottom: var(--s1);
}
.feld-hinweis { display: block; font-size: var(--t-sm); color: var(--ink-3); margin-top: var(--s1); }
/* The same 16px floor as the search field, and for the same reason: below it
   iOS Safari zooms the viewport on focus and the page scrolls sideways. */
.feld-eingabe {
    font-family: inherit;
    font-size: var(--t-base);
    color: var(--ink);
    background: var(--bg-sunk);
    border: 1px solid var(--line-strong);
    border-radius: var(--r-control);
    padding: 0 var(--s3);
    height: 44px;
    width: 100%;
}
.feld-eingabe::placeholder { color: var(--ink-3); opacity: 1; }

/* No rule of its own beyond the reset — the fields carry their own spacing.
   It is a real <form> only so that Enter submits; the CSP refuses a real
   submit, and a delegated listener stops it and sends the fields by fetch. */
.konto-form { display: block; }
.konto-aktionen { display: flex; gap: var(--s2); flex-wrap: wrap; margin-top: var(--s5); }
.konto-wechsel { margin-top: var(--s5); font-size: var(--t-sm); color: var(--ink-2); }
.konto-wechsel a { color: var(--brand); }
/* Errors and confirmations sit above the buttons, where the eye already is
   after the last field, and they are announced as well as shown. */
.konto-fehler { color: var(--warn); margin-top: var(--s4); }
.konto-ok { color: var(--brand); margin-top: var(--s4); }
.konto-gefahr { margin-top: var(--s6); padding-top: var(--s4); border-top: 1px solid var(--line); }
.konto-gefahr .btn-rand { color: var(--warn); }

/* The Admin entry on "Mein Konto", which only an admin is ever shown. It stands
   open: every entry is a link to a real address, and nothing about it is a state
   that exists only while a control is held open. */
.konto-admin { margin-top: var(--s5); padding-top: var(--s4); border-top: 1px solid var(--line); }
.konto-admin-titel,
.konto-abschnitt-titel { font-size: var(--t-sm); font-weight: 600; color: var(--ink-2); }
/* The address block's own heading (OP217), which says which of its three states
   the konto is in. It takes its air here rather than from a wrapper: the Admin
   heading sits inside a <section> that spaces it, this one stands straight in
   the panel. */
.konto-abschnitt-titel { margin-bottom: var(--s2); }
.konto-admin-liste { list-style: none; margin-top: var(--s2); }
.konto-admin-liste li { margin-top: var(--s2); font-size: var(--t-sm); }
/* 44 pixels, like every other link that is a control: a bare run of text is
   15 pixels high and cannot be hit with a thumb (OP102). */
.konto-admin-liste a { min-height: 44px; display: inline-flex; align-items: center; color: var(--brand); }
.konto-admin-was { display: block; color: var(--ink-3); font-size: var(--t-xs); }

/* "Meine Tiere" — and since OP190 the whole of the named lists as well. The
   management page at #listen is gone: every list a konto keeps is a section of
   this page now, with its own title, its own three controls and its own drop
   target in the rail at the top. What follows is that page, read top down.
   Nine rules went with the page they drew; `tests/test_ui_feinheiten.py` names
   them one at a time and goes red if any of them comes back. */

/* This page is tiles, not a form. `.konto-panel` caps a form at the
   62-character reading measure, right for the login and wrong here: it left the
   remembered animals two columns wide on a 1440 monitor while the same tiles
   stand five across on "Alle Tiere". The two grid panels take the full column. */
.konto-panel:has(#merk-seite),
.konto-panel:has(#liste-seite) { max-width: var(--spalte); }

/* ---------------------------------------------- the rail of drop targets */
/* Two elements, and which carries what is the whole of it: konto.js emits a
   bare wrapper `#listen-dock` around the row `.listen-dock`, with the new-list
   form as the row's sibling while it is open.

   The offset is the WRAPPER's. A sticky box may only travel inside its own
   containing block, and the wrapper is exactly as tall as the row — pinned on
   the row the rail has nowhere to go and scrolls away with the page, which is
   what it silently did for a day. The gap below and the opaque fill are the
   wrapper's too, or the gap falls between row and form and the page scrolls
   through the strip between them. */
#listen-dock {
    position: sticky;
    top: 52px;               /* the header, and `html`'s own scroll-padding */
    z-index: 24;
    margin-bottom: var(--s3);
    background: var(--bg);
}
/* Scenery until a drag starts: a quiet line of capsules on the bar tone under
   one hairline, scrolling sideways because twenty lists wrapped would be four
   rows of pinned furniture. */
.listen-dock {
    display: flex;
    align-items: center;
    gap: var(--s2);
    overflow-x: auto;
    scrollbar-width: none;
    scroll-snap-type: x proximity;
    padding: var(--s2) var(--s1);
    background: var(--bg-bar);
    border-bottom: 1px solid var(--line);
}
.listen-dock::-webkit-scrollbar { display: none; }

/* A capsule with the list's name and its count. The name loses its tail rather
   than push the count off the end — sixty characters is a legal list name. */
.listen-dock-ziel {
    font-family: inherit;
    font-size: var(--t-control);
    display: inline-flex;
    align-items: center;
    gap: var(--s2);
    flex: none;
    min-height: 44px;
    max-width: 60vw;
    padding: 0 var(--s3);
    scroll-snap-align: start;
    white-space: nowrap;
    background: var(--bg);
    border: 1px solid var(--line-strong);
    border-radius: 22px;   /* half the 44 target — the capsule the pills wear */
    color: var(--ink-2);
    cursor: pointer;
}
.listen-dock-ziel > span:first-child { overflow: hidden; text-overflow: ellipsis; }
.listen-dock-ziel:hover { background: var(--bg-sunk); color: var(--ink); }

/* The one control that makes a list, and it must not read as another list: a
   dashed outline in the brand tone rather than a filled capsule. */
.listen-dock-neu {
    font-family: inherit;
    font-size: var(--t-control);
    font-weight: 600;
    display: inline-flex;
    align-items: center;
    flex: none;
    min-height: 44px;
    padding: 0 var(--s3);
    white-space: nowrap;
    background: none;
    border: 1px dashed var(--line-strong);
    border-radius: 22px;
    color: var(--brand);
    cursor: pointer;
}
.listen-dock-neu:hover { background: var(--bg-sunk); }

/* ------------------------------------------------ what a drag looks like */
/* Three states, and all four stylesheets answer exactly these: `data-zieht` on
   <body> while a tile is in the air, `.zieht` on the tile that left, and
   `.dock-ueber` on the target under the pointer. A fourth, `.dock-an`, says
   the animal in the air already stands on that list — releasing there takes it
   off again, so the two may never look alike.

   Nothing below changes a size: the rail is pinned, and a target that grew
   mid-drag would move out from under the finger aiming at it. */
body[data-zieht="1"] { -webkit-user-select: none; user-select: none; }
body[data-zieht="1"] .listen-dock {
    background: var(--bg-sunk);
    border-bottom: 2px solid var(--brand);
}
body[data-zieht="1"] .listen-dock-ziel { border-color: var(--brand); color: var(--ink); }
/* Nothing can be dropped on it, so it steps back for the length of the drag. */
body[data-zieht="1"] .listen-dock-neu { opacity: .45; }
body[data-zieht="1"] .tier { cursor: grabbing; }

/* The tile that is being carried. It stays where it was — the copy under the
   finger is konto.js's, positioned from script — so this is what says the one
   on the page is no longer the live one. */
.tier.zieht { opacity: .45; border-style: dashed; }

/* Release here and the animal goes ON the list: the brand fill, which is the
   colour this site says yes with. 10.53:1 in Papier, 9.73:1 in Abend. */
.listen-dock-ziel.dock-ueber {
    background: var(--brand);
    border-color: var(--brand);
    color: var(--on-brand);
}
/* Already on this list. Marked twice over — the tone and a tick — because a
   colour on its own is the one signal a reader who cannot separate these two
   hues does not get; the same argument `.panel-wahl.aktiv` carries. */
.listen-dock-ziel.dock-an {
    border-color: var(--brand);
    color: var(--brand);
    font-weight: 600;
}
.listen-dock-ziel.dock-an::before {
    content: '\2713';   /* a tick, written as its number so the mark cannot
                           depend on how a browser guessed this file's encoding */
    line-height: 1;
}
/* Both at once: the pointer stands over a list the animal is ALREADY on, so
   letting go REMOVES it. The warning fill and never the brand one — those two
   gestures are opposites, and this site has exactly one colour for "this takes
   something away". 7.73:1 in Papier, 8.79:1 in Abend. */
.listen-dock-ziel.dock-an.dock-ueber {
    background: var(--warn);
    border-color: var(--warn);
    color: var(--on-warn);
}
.listen-dock-ziel.dock-an.dock-ueber::before { content: '\2715'; }   /* a cross */

/* ------------------------------------------------------- a list's heading */
/* The rail is pinned above these: without the margin a pressed target scrolls
   its own section underneath the rail that scrolled it. */
.merk-abschnitt { margin-top: var(--s6); scroll-margin-top: 116px; }
/* Since OP190 the heading also carries the list's three controls, so it is a
   row: the name, its count and the shared mark to the left, the tools hard
   right. It wraps rather than squeezing a German list name to two letters. */
.merk-titel {
    font-size: var(--t-lg);
    margin-bottom: var(--s2);
    padding-top: var(--s4);
    border-top: 1px solid var(--line);
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s2);
}
/* Shrinks and breaks, so the count beside it keeps its own line. */
.merk-titel-wort { min-width: 0; overflow-wrap: anywhere; }
.merk-unter { font-size: var(--t-sm); color: var(--ink-3); margin-bottom: var(--s4); }
/* The count beside each heading. Tabular figures so it does not jiggle the
   heading sideways when a tile is taken away and 10 becomes 9. */
.merk-zahl { color: var(--ink-3); font-variant-numeric: tabular-nums; }
.merk-geteilt {
    font-size: var(--t-xs);
    color: var(--ink-3);
    border: 1px solid var(--line-strong);
    border-radius: var(--r-control);
    padding: 2px 8px;
}

/* Rename, share, delete: hard right of the title, and quiet — three icons
   beside a heading must not read as a toolbar. */
.listen-werkzeug {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: 2px;
    flex: none;
}
/* 44 pixels to a thumb, 20 of drawing. Each one says what it is in its own
   aria-label, because a drawn shape is nothing at all to a screen reader. */
.listen-icon {
    width: 44px;
    height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: none;
    border: 0;
    border-radius: var(--r-control);
    color: var(--ink-3);
    cursor: pointer;
}
.listen-icon svg { width: 20px; height: 20px; }
.listen-icon:hover { background: var(--bg-sunk); color: var(--ink); }
/* The share control while the list really is shared. Marked twice — the brand
   tone and a sunk plate with an edge — so it reads as pressed in rather than
   as merely a different colour. 9.45:1 in Papier, 9.75:1 in Abend. */
.listen-icon.an {
    color: var(--brand);
    background: var(--bg-sunk);
    box-shadow: inset 0 0 0 1px var(--line-strong);
}
/* The bin, and the "Löschen" button inside the question it opens — konto.js
   puts this class on both. The konto's own delete tone: 7.73:1 in Papier, 8.79
   in Abend. Written after `.btn-rand` and `.listen-icon:hover`, which both set
   a colour of their own and are one class deep, so this only wins on order. */
.listen-icon-weg,
.listen-icon-weg:hover { color: var(--warn); }

/* --------------------------------------------------- the row that asks */
/* A rename, a deletion, a new list's name. In the flow and never a dialogue
   box: a state that lives in a pop-up has no address and no back button, the
   rule "Konto löschen" already follows. It has to stop the eye, so it is the
   sunk tone inside a real edge and not one more line of text among the tiles. */
.listen-frage {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s2);
    margin: var(--s2) 0 var(--s4);
    padding: var(--s3);
    background: var(--bg-sunk);
    border: 1px solid var(--line-strong);
    border-radius: var(--r-control);
}
.listen-frage > p,
.listen-frage > .feld-label { flex: 1 0 100%; margin: 0; color: var(--ink); }
/* `.feld-eingabe` is 100% wide by itself and would push the buttons onto a
   line of their own. It takes what they leave, down to twelve characters. */
.listen-frage .feld-eingabe { flex: 1 1 12rem; width: auto; min-width: 0; background: var(--bg); }

/* The public address of a shared list, under its title. It stands in a field
   rather than only behind the button: that is the last resort for a browser
   with neither a share sheet nor a clipboard, and seventy unbroken characters
   in running text would be the one thing here that widens the document. */
.listen-adresse {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s2);
    margin: var(--s2) 0 var(--s4);
    padding: var(--s3);
    background: var(--bg-bar);
    border: 1px solid var(--line);
    border-radius: var(--r-control);
    overflow-wrap: anywhere;
}
.listen-adresse > .merk-unter { flex: 1 0 100%; margin: 0; }
.listen-adresse .feld-eingabe { flex: 1 1 14rem; width: auto; min-width: 0; }

/* ------------------------------------------ the tile's own way onto a list */
/* The accessible twin of the drag: a keyboard, a screen reader and a switch
   cannot drag anything. It is one control that opens a menu, and deliberately
   NOT the stack of checkboxes that used to stand open under every tile — that
   stack is what "this really feels a bit clunky" meant. */
.listen-tiermenue { position: relative; margin-top: var(--s1); }
.listen-menue-knopf {
    font-family: inherit;
    font-size: var(--t-sm);
    display: inline-flex;
    align-items: center;
    gap: var(--s1);
    min-height: 44px;
    padding: 0 var(--s2);
    background: none;
    border: 0;
    border-radius: var(--r-control);
    color: var(--ink-2);
    cursor: pointer;
}
.listen-menue-knopf svg { width: 18px; height: 18px; }
.listen-menue-knopf:hover { background: var(--bg); color: var(--ink); }
/* Open, it stands over its own tile rather than pushing the grid about: 1420
   tiles reflowing because one menu unfolded is a page that jumps under the
   reader. The body is the chip panel's, being the same kind of object — the
   strong hairline, no shadow. It rises above the pinned rail. */
.listen-menue {
    position: absolute;
    bottom: 100%;
    left: 0;
    right: 0;
    z-index: 26;
    display: flex;
    flex-direction: column;
    gap: 2px;
    max-height: 240px;
    overflow-y: auto;
    margin-bottom: var(--s1);
    padding: var(--s2);
    background: var(--bg-bar);
    border: 1px solid var(--line-strong);
    border-radius: var(--r-control);
}
/* Without this the flex above beats the hidden attribute and every tile on the
   page carries an open menu — exactly the stack this replaced. */
.listen-menue[hidden] { display: none; }
/* `.tier` carries `content-visibility: auto`, which brings paint containment:
   every descendant is clipped to the tile's own box, this menu included. It
   opens upward and fits a 340-pixel tile today by luck — one more list, or a
   shorter tile, and its top disappears with nothing saying why. "Meine Tiere"
   holds a handful of tiles rather than 1420, so the rendering trick is worth
   almost nothing here and is given up on the tiles that can open a menu. */
.tier:has(.listen-tiermenue) {
    content-visibility: visible;
    contain-intrinsic-size: none;
}
.listen-menue-wahl {
    display: flex;
    align-items: center;
    gap: var(--s2);
    min-height: 44px;
    padding: 0 var(--s1);
    font-size: var(--t-sm);
    color: var(--ink-2);
    cursor: pointer;
}
.listen-menue-wahl:hover { color: var(--ink); }
.listen-menue-wahl input { width: 20px; height: 20px; flex: none; accent-color: var(--brand); }
.listen-menue-wahl span { min-width: 0; overflow-wrap: anywhere; }

/* Leitstand's own signature and only Leitstand's: a strip of thirty cells in
   the footer saying how tonight's run went at each shelter. `heime.js` draws it
   on whichever look is on, so every other look hides it in its own file.
   Klassisch is paper and a photograph, not an instrument panel.

   The id alone would hide it. The classes are named too because a class the
   markup writes with no rule anywhere is how this project has silently lost
   styling three times, and `test_frontend_views.py` is what catches that — an
   id-only rule would leave that check reading a hole. */
#heimStreifen,
.heim-streifen-zeile,
.heim-streifen-zelle,
.heim-streifen-wort { display: none; }

/* -------------------------------------------------------------------- footer */
footer {
    max-width: var(--spalte);
    margin: var(--s6) auto 0;
    padding: var(--s4) var(--s3) var(--s5);
    border-top: 1px solid var(--line);
    font-size: var(--t-sm);
    color: var(--ink-3);
}
footer a {
    color: var(--brand);
    display: inline-flex;
    align-items: center;
    min-height: 44px;
}
/* Far right, directly under the footer's rule; the two sentences flow left of
   it. First in the markup, because a float has to precede what wraps it. */
.fuss-impressum { float: right; margin-left: var(--s4); }

/* ==================================================== 640px and above ==== */
@media (min-width: 640px) {
    .wortmarke { font-size: 1.125rem; }
    .wortmarke-land { display: inline; }
    .tier-gitter {
        grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
        column-gap: var(--s3);
    }
}

/* =================================================== 1024px and above ==== */
@media (min-width: 1024px) {
    html { scroll-padding-top: 60px; }  /* the header; nothing else is pinned */

    /* The header is 60 here; the rail and the jump that clears it follow. */
    #listen-dock { top: 60px; }
    .merk-abschnitt { scroll-margin-top: 124px; }

    /* No bottom bar; the body does not have to give room to one. */
    body { padding-bottom: 0; }

    .kopf { border-bottom: 0; }
    .navitem { color: rgb(255 255 255 / .78); }
    .navitem:hover { color: var(--on-brand); }
    .navitem.aktiv { color: var(--on-brand); border-bottom-color: var(--on-brand); }
    .kopf-innen { height: 60px; padding: 0 var(--s5); gap: var(--s5); }
    .wortmarke { font-size: var(--t-lg); letter-spacing: -0.015em; }
    .pfote { width: 24px; height: 24px; }

    .ansichten {
        position: static;
        margin-right: auto;
        background: none;
        border-top: 0;
        padding-bottom: 0;
        gap: var(--s4);
        overflow-x: visible;
    }
    .navitem {
        flex: none;
        min-width: 0;
        height: auto;
        font-size: var(--t-base);
        letter-spacing: 0;
        border-top: 0;
        border-bottom: 2px solid transparent;
        padding-bottom: 2px;
    }
    .navitem.aktiv { border-top-color: transparent; border-bottom-color: var(--brand); }

    .ergebnis-leiste {
        min-height: 60px;
        padding: 0 var(--rand-w);
        gap: var(--s4);
    }
    .ergebnis { font-size: var(--t-lg); font-weight: 600; letter-spacing: -0.015em; }
    .kopf-suche { padding: var(--s2) var(--s5) 0; }
    .kopf-suche .such-feld { max-width: 420px; }
    .chip-leiste { padding-inline: calc(var(--s5) + 3px);
                   scroll-padding-inline: calc(var(--s5) + 3px); }

    .streifen { padding: 0 var(--rand-w); flex-wrap: wrap; overflow-x: visible; }
    /* Nothing scrolls here, so each row takes the width it has and wraps inside
       itself. `max-content` would push the page sideways instead. */
    .streifen-zwei { flex-wrap: nowrap; }
    .streifen-zeile { width: auto; flex-wrap: wrap; }
    .willkommen-wege { grid-template-columns: 1fr 1fr; }
    .willkommen-titel { font-size: 2.75rem; }
    /* Top-aligned, not centred: a short title floated to the middle of six
       bullet lines read as two unrelated blocks — the operator's word. */
    .willkommen-kopf {
        display: flex;
        align-items: flex-start;
        gap: var(--s6);
    }
    .willkommen-links { flex: none; }
    .willkommen-punkte { flex: 1; min-width: 0; }
    .willkommen-zahlen { margin-bottom: 0; }
    .willkommen-titel { margin-bottom: var(--s3); }
    .kopf-zeile { padding: 0 var(--s5); }

    main { padding: var(--s5) var(--s5) var(--s6); }
    footer { padding: var(--s4) var(--s5) var(--s5); }

    .tier-gitter {
        grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
        column-gap: 20px;
        row-gap: var(--s5);
    }
}

/* ------------------------------------------------------- the shelter map */
/* OP172 — the Karte face of "Alle Heime": the nine Bundesland outlines of
   static/austria-karte.svg with one dot per shelter. Every colour is a token,
   which is all dark mode needs. */
/* The Liste/Karte switch above both faces. */
.ansicht-streifen { margin-bottom: var(--s3); }
.karte-platz {
    width: 100%;
    /* The viewBox's own ratio, so the page has its height before the file
       arrives and nothing jumps when it does. */
    aspect-ratio: 987.1 / 511;
    margin-top: var(--s3);
}
.karte-platz svg {
    display: block;
    width: 100%;
    height: auto;
    /* A shelter name near the east edge reaches past the viewBox. */
    overflow: visible;
}
/* Wide enough a stroke to cover the slivers where two simplified borders do
   not quite meet. */
.karte-platz [data-bl] {
    fill: var(--bg-sunk);
    stroke: var(--line-strong);
    stroke-width: 1.5;
    stroke-linejoin: round;
}
.karte-heim { cursor: pointer; }
/* The finger-sized target; the visible dot alone is seven units. */
.karte-heim .karte-treffer {
    fill: none;
    pointer-events: all;
}
.karte-heim .karte-punkt {
    fill: var(--brand);
    stroke: var(--bg);
    stroke-width: 2;
    /* The growth on hover and focus. The site-wide reduced-motion rule clamps
       this transition to 1ms like every other, so the dot still grows — it
       just does not animate. */
    transform-box: fill-box;
    transform-origin: center;
    transition: transform var(--motion);
}
.karte-heim:hover .karte-punkt,
.karte-heim:focus-visible .karte-punkt { transform: scale(1.6); }
/* The name, on hover and focus only: thirty names at once would be the list,
   drawn worse. The stroke under the letters keeps them readable where they
   cross a border. */
.karte-heim .karte-name {
    display: none;
    font-size: 26px;
    font-weight: 600;
    text-anchor: middle;
    fill: var(--ink);
    paint-order: stroke;
    stroke: var(--bg);
    stroke-width: 6px;
    stroke-linejoin: round;
    pointer-events: none;
}
.karte-heim:hover .karte-name,
.karte-heim:focus-visible .karte-name { display: block; }
.karte-quelle {
    margin-top: var(--s2);
    font-size: var(--t-xs);
    color: var(--ink-3);
}
.karte-quelle a { color: inherit; }
/* The strip is display:flex, which outweighs the hidden attribute the map
   face sets on the filter row, so the attribute needs its own heavier rule. */
#shelterFilters[hidden] { display: none; }
