/* ResearchRadar stylesheet, second half (OP190, 2026-08-03): the page-
   specific blocks — filters, the two detail pages, the list tables, the
   admin tabs, the Konferenzen page, the responsive/print/a11y overrides
   and the landing page. app.css carries the palette, the shell and the
   shared components, and the shell links the two in that order with the
   same ?v=, so the cascade reads exactly as the one file did. A pure
   move: concatenating app.css and this file in link order reproduces the
   old sheet apart from this comment. */

/* Filters */
.results-count {
    font-size: 0.9rem;
    color: var(--text-light);
    margin-bottom: 1rem;
}

/* Institution detail */
.inst-detail-header {
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 2rem;
    margin-bottom: 2rem;
}
.inst-detail-header h1 {
    font-size: 1.8rem;
    color: var(--heading);
    margin-bottom: 0.25rem;
}
.inst-detail-header .full-name {
    color: var(--text-light);
    font-size: 1rem;
    margin-bottom: 1rem;
}
.inst-detail-header .description {
    font-size: 1rem;
    line-height: 1.7;
    margin-top: 1rem;
    margin-bottom: 1.25rem;
}
/* the institution's name block and the "to website" button, side by side and
   wrapping under each other when the window is too narrow for both */
.inst-header-row {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    flex-wrap: wrap;
    gap: 1rem;
}
.back-link {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    font-size: 0.9rem;
    margin-bottom: 1.5rem;
}

/* Footer — one line, three zones since version 1.97: the project credit on
   the left, the disclaimer in the middle, the legal pages on the right. On
   a screen too narrow for the line the three stack, centred. */
footer {
    background: var(--bar-bg);
    color: var(--on-bar-footer);
    padding: 1.1rem 0;
    font-size: 0.85rem;
    margin-top: auto;
}
footer a { color: var(--on-bar-soft); }
footer a:hover { color: var(--on-bar); }
/* side padding on the inner box, not the bar — same reason as .nav-inner:
   only then does its 1200px mean what main's 1200px means (OP207) */
.footer-inner {
    max-width: 1200px;
    margin: 0 auto;
    padding: 0 1.5rem;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem 1.5rem;
    flex-wrap: wrap;
}
.footer-disclaimer { flex: 1 1 20rem; text-align: center; font-size: 0.8rem; }
@media (max-width: 680px) {
    .footer-inner { flex-direction: column; text-align: center; }
    .footer-disclaimer { flex: none; }
}
/* .footer-updated is gone with the line it styled: the footer's
   hand-maintained "data last updated" date sat under a sentence promising a
   daily check and contradicted it by a growing number of days (OP81). Each
   entry carries its own "last compared" line, which is the honest one. */

/* Grants list */
.grants-list {
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

/* Overview: career-stage filter pills */
/* The filter block is folded away by default (it is a wall of pills and
   explanations); the summary line is the whole control. */
.stage-filter {
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 0;
    margin-bottom: 1.25rem;
}
.stage-filter > .filter-body { padding: 0 1.25rem 1.1rem; }
.stage-filter-head {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.8rem 1.25rem;
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--heading);
    letter-spacing: 0.3px;
    cursor: pointer;
    list-style: none;
    min-height: 44px;
}
.stage-filter-head::-webkit-details-marker { display: none; }
.stage-filter-head::before {
    content: "\25B8";
    color: var(--primary);
    transition: transform 0.15s;
}
.stage-filter[open] > .stage-filter-head::before { transform: rotate(90deg); }
.stage-filter-head:hover { background: var(--primary-bg); border-radius: var(--radius); }
.stage-filter-head:focus-visible {
    outline: 2px solid var(--primary);
    outline-offset: -2px;
    border-radius: var(--radius);
}
/* how many filters are on — visible while the block is folded away */
.filter-active {
    font-size: 0.72rem;
    font-weight: 600;
    background: var(--primary);
    color: var(--on-primary);
    border-radius: 999px;
    padding: 0.05rem 0.5rem;
}
/* the live result count on the same line (OP172): on a phone the open
   panel is taller than the screen and the count above it is out of sight,
   so this one travels with the summary and shows what a tap just did */
.filter-results {
    margin-left: auto;
    font-size: 0.78rem;
    font-weight: 400;
    color: var(--text-light);
    white-space: nowrap;
}
/* The reset pill and the "?" share the top line; every filter row is one
   line under it: its name, then its pills, and nothing else (Georg,
   2026-07-31). What a pill means is its hover text, what the block means
   is behind the "?". */
.filter-top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.6rem;
    margin-bottom: 0.7rem;
}
.filter-help-btn {
    flex-shrink: 0;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    border: 1px solid var(--border);
    background: var(--bg-card);
    color: var(--primary);
    font-family: inherit;
    font-size: 0.95rem;
    font-weight: 700;
    line-height: 1;
    cursor: pointer;
}
.filter-help-btn:hover { background: var(--primary-bg); border-color: var(--primary-light); }
.filter-help-btn:focus-visible {
    outline: 2px solid var(--primary);
    outline-offset: 2px;
}
.pill-rows {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    min-width: 0;
}
.pill-line {
    display: flex;
    align-items: baseline;
    gap: 0.6rem;
    min-width: 0;
}
/* one column for every row's name, so the pills line up down the block */
.pill-row-label {
    flex: 0 0 8.5rem;
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--text-light);
    text-align: right;
}
.pill-row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    flex: 1;
    min-width: 0;
}
.pill-status .pill-code { font-weight: 600; }
.pill {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: 999px;
    padding: 0.35rem 0.85rem;
    font-size: 0.85rem;
    font-family: inherit;
    color: var(--text);
    cursor: pointer;
    transition: all 0.15s;
}
.pill:hover { border-color: var(--primary-light); background: var(--primary-bg); }
.pill.active {
    background: var(--primary);
    border-color: var(--primary);
    color: var(--on-primary);
}
.pill-code { font-weight: 700; }
.pill-text { color: inherit; opacity: 0.85; }
.pill-count {
    font-size: 0.72rem;
    font-weight: 600;
    background: var(--primary-bg);
    color: var(--primary);
    border-radius: 999px;
    padding: 0.05rem 0.4rem;
    min-width: 1.5em;
    text-align: center;
}
.pill.active .pill-count { background: var(--pill-count-on-bg); color: var(--on-primary); }
/* The one sentence still printed inside the filter: that Germany and
   Switzerland are thin. Everything else that used to stand here moved into
   the panel behind the "?" or onto the pills themselves. */
.filter-note {
    font-size: 0.78rem;
    font-style: italic;
    color: var(--tone-warn-text);
    margin: 0.1rem 0 0 9.1rem;
    line-height: 1.4;
}

/* The help panel. Not a modal overlay: it can be dragged anywhere and the
   page underneath stays usable, so nothing dims and nothing but its own ✕
   closes it. */
.help-panel {
    position: fixed;
    top: 12vh;
    left: 50%;
    transform: translateX(-50%);
    z-index: 120;
    width: min(30rem, 92vw);
    max-height: 76vh;
    display: flex;
    flex-direction: column;
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-hover);
}
.help-panel[hidden] { display: none; }
/* once dragged, the script sets left/top and clears the centring shift */
.help-panel.dragged { transform: none; }
.help-panel-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.6rem;
    padding: 0.7rem 0.9rem;
    border-bottom: 1px solid var(--border);
    cursor: move;
    touch-action: none;
}
.help-panel-head h3 { margin: 0; font-size: 0.95rem; }
.help-panel-close {
    flex-shrink: 0;
    min-width: 32px;
    min-height: 32px;
    border: none;
    background: none;
    color: var(--text-light);
    font-family: inherit;
    font-size: 1rem;
    line-height: 1;
    cursor: pointer;
    border-radius: var(--radius);
}
.help-panel-close:hover { background: var(--primary-bg); color: var(--heading); }
.help-panel-close:focus-visible { outline: 2px solid var(--primary); outline-offset: -2px; }
.help-panel-body {
    padding: 0.9rem;
    overflow-y: auto;
    font-size: 0.82rem;
    line-height: 1.55;
    color: var(--text-light);
}
.help-panel-body p { margin: 0 0 0.7rem; }
.help-panel-body dt {
    font-weight: 700;
    color: var(--heading);
    margin-top: 0.6rem;
}
.help-panel-body dd { margin: 0.1rem 0 0; }
/* the per-button meanings under each row's explainer — the same strings the
   pills carry as hover titles, listed because a touch screen has no hover
   (OP170) */
.help-pill-list {
    margin: 0.2rem 0 0;
    padding-left: 1.1rem;
}
.help-pill-list li { margin-top: 0.15rem; }
.help-pill-list strong { color: var(--heading); font-weight: 600; }
.stage-explainer {
    font-size: 0.8rem;
    color: var(--text-light);
    line-height: 1.55;
    margin-top: 0.85rem;
}
/* The search box rides in the toolbar row with the four buttons, so it
   costs no extra line on a phone. */
input.table-search {
    flex: 1 1 12rem;
    min-width: 0;
    padding: 0.4rem 0.8rem;
    border: 1px solid var(--border);
    border-radius: 8px;
    /* 0.9rem was 14.4px and tripped the iPhone zoom (audit OP110) */
    font-size: 1rem;
    font-family: inherit;
    min-height: 44px;
}
/* The focus ring the retired dropdowns had (OP111): the box that replaced
   them is now the only text control on either list page, and a control
   with no visible focus is unusable from the keyboard. */
input.table-search:focus {
    outline: none;
    border-color: var(--primary);
    box-shadow: 0 0 0 2px var(--focus-ring);
}
/* Admin tabs: plain links, each with its own address. */
.admin-tabs {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    max-width: 900px;
    margin: 0 auto 1rem;
}
.admin-tab {
    padding: 0.5rem 0.9rem;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: var(--bg-card);
    font-size: 0.9rem;
    font-weight: 500;
    min-height: 44px;
    display: inline-flex;
    align-items: center;
}
.admin-tab.on {
    background: var(--primary);
    border-color: var(--primary);
    color: var(--on-primary);
}
.find-group { margin-bottom: 1.25rem; }
.find-group h2 { font-size: 1.05rem; margin-bottom: 0.5rem; }
.find-item {
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 0.7rem 0.9rem;
    margin-bottom: 0.6rem;
}
.find-decided { opacity: 0.7; }
.find-head { display: flex; justify-content: space-between; gap: 0.75rem; flex-wrap: wrap; }
.find-day { color: var(--text-light); font-size: 0.8rem; }
/* wrap: the row grew a third button with OP84 ("Gehört zu diesem Eintrag"),
   and three of them squeeze each other to 75px and three lines of text at
   360px wide. Wrapping costs a line on a phone and nothing on a desktop. */
.find-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-top: 0.5rem;
}
.find-state { font-size: 0.85rem; color: var(--text-light); margin-top: 0.35rem; }
/* The full table row of a read find (OP200): what the directory table
   would show, from the stored page reading. One line that wraps on a
   phone; empty fields say "siehe Website" like the public table does. */
.find-row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.2rem 1.1rem;
    margin-top: 0.45rem;
    font-size: 0.85rem;
}
.find-row-head { font-weight: 600; }
.find-row-label { color: var(--text-light); }
.find-scan {
    margin-top: 0.6rem;
    padding-top: 0.6rem;
    border-top: 1px dashed var(--border);
    font-size: 0.85rem;
}
.find-scan-head { font-weight: 600; margin-bottom: 0.35rem; }
.find-evidence {
    color: var(--text-light);
    font-style: italic;
    margin: 0.15rem 0 0.5rem;
}

input.table-search::placeholder {
    font-style: italic;
    color: var(--text-light);
}
.no-results { text-align: center; color: var(--text-muted); padding: 2rem; }
.no-results .btn { margin-top: 0.75rem; }

/* Overview table */
.overview-toolbar {
    display: flex;
    justify-content: space-between;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.75rem;
    margin-bottom: 1rem;
}
/* the four download buttons must be allowed to wrap — as one unbreakable
   row they pushed the whole start page sideways on a phone */
.overview-downloads { display: flex; flex-wrap: wrap; gap: 0.5rem; }
/* The table is meant to be read, not scrolled: it always fits the window
   width, and the page scrolls as one instead of a box inside a box.
   Columns are fixed shares of the width, so no single long value can push
   the layout sideways; narrow screens drop the least useful columns
   (everything stays in the CSV/Excel download). */
.table-scroll {
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--radius);
}
.overview-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.85rem;
}
#overview-table { table-layout: fixed; }
/* Break at syllables first, mid-word only as the last resort (OP168):
   before `hyphens: auto` the German compounds broke wherever the line
   ended — "KARRIERESTUF E" in the header, "Wissenschaftler:i nnen" in
   the cells. The browser's dictionaries key on the page's lang
   attribute, which templates_shell.py writes on <html> ("de" or "en"),
   so both languages divide at real syllables. `overflow-wrap:
   break-word` stays underneath as the net for a string no dictionary
   knows — without it such a string runs sideways out of its cell, and
   the conference date test (OP165) stands on it. */
#overview-table th, #overview-table td {
    overflow-wrap: break-word;
    -webkit-hyphens: auto;
    hyphens: auto;
}
/* OP130: on a phone the headings themselves genuinely could not fit, so the
   rule above broke them mid-word — "FÖRDE RGEBER". Every heading that is
   still on screen at the narrowest width carries a short wording as well
   (templates_overview.py), and the swap happens at 680px, the same
   breakpoint that starts dropping columns. Never a sideways scroller and
   never a wider table: project rule 26. */
.th-narrow { display: none; }
/* Re-cut for OP168. The 1.97 cut sized every column to its HEADING so
   no heading would ever wrap — and starved the two columns that hold
   whole sentences: Zielgruppe and Einreichfrist had 9% and 10.2% while
   the ellipsized Website address held 9%, and the median row grew 198px
   tall. The sentences now get the room (5 and 8 below); Website pays
   first — its cells truncate with an ellipsis, so 6% only shortens the
   visible address — with Förderer (short names such as FWF), and the
   wrapping text columns pay the rest. A heading squeezed under its own
   width is no longer a fault: the hyphens rule above divides it at a
   syllable ("KARRIERE-STUFE"), not wherever the line happens to end.
   The two date columns keep what their dates measure — "22.10.2026" is
   ~68px and "22.07.26" ~60px at this size. These ten only render from
   1181px of window up to the 1680px cap on `main.wide`. The shares add
   up to 100 — tests/test_stylesheet.py counts them. */
#overview-table th:nth-child(1) { width: 7.7%; }
#overview-table th:nth-child(2) { width: 12.3%; }
#overview-table th:nth-child(3) { width: 8%; }
#overview-table th:nth-child(4) { width: 9%; }
#overview-table th:nth-child(5) { width: 16%; }
#overview-table th:nth-child(6) { width: 9%; }
#overview-table th:nth-child(7) { width: 9.5%; }
#overview-table th:nth-child(8) { width: 15%; }
#overview-table th:nth-child(9) { width: 7.5%; }
#overview-table th:nth-child(10) { width: 6%; }
/* The conference table has nine columns where the funding one has ten, so
   it needs its own shares: without them it inherited the funding table's
   first nine, which add up to 91% and put the funding table's "Neu seit"
   width on the conference table's website column. Both tables carry
   id="overview-table", so one class more is what decides between them.
   The nine add up to 100. */
/* Re-cut 2026-08-02 (Georg): the Datum column now breaks a range onto two
   lines, so it no longer needs room for two dates side by side — at 11%
   it stood half empty and read as a gap between Org and Ort. The freed
   points go to Bewerbung, which holds the longest text in the table since
   version 1.86 put real call-for-papers wording there. Datum keeps enough
   for one date plus its dash: "22.10.2026 –" is ~78px at this size, and
   8% of a 1370px table is 110px gross, 93px inside the padding. */
#overview-table.conf-table th:nth-child(1) { width: 9%; }
#overview-table.conf-table th:nth-child(2) { width: 20%; }
#overview-table.conf-table th:nth-child(3) { width: 7%; }
#overview-table.conf-table th:nth-child(4) { width: 8%; }
#overview-table.conf-table th:nth-child(5) { width: 9%; }
#overview-table.conf-table th:nth-child(6) { width: 24%; }
#overview-table.conf-table th:nth-child(7) { width: 10%; }
#overview-table.conf-table th:nth-child(8) { width: 7%; }
#overview-table.conf-table th:nth-child(9) { width: 6%; }
.overview-table th {
    background: var(--primary-bg);
    color: var(--heading);
    text-align: left;
    font-size: 0.68rem;
    text-transform: uppercase;
    letter-spacing: 0;
    padding: 0.6rem 0.55rem;
    border-bottom: 2px solid var(--border);
    user-select: none;
}
.overview-table th .sort-btn {
    /* deliberately not a flex row: a flex container refuses to shrink below
       its content, which pushed the whole table past the window edge */
    display: block;
    width: 100%;
    min-width: 0;
    background: none;
    border: none;
    padding: 0;
    margin: 0;
    font: inherit;
    color: inherit;
    text-align: left;
    text-transform: inherit;
    letter-spacing: inherit;
    cursor: pointer;
}
.overview-table th:hover { background: var(--th-hover-bg); }
.overview-table th .sort-btn:focus-visible {
    outline: 2px solid var(--primary);
    outline-offset: 2px;
}
.sort-arrow { display: inline-block; width: 0.9em; color: var(--primary); }
th.sorted-asc .sort-arrow::after { content: "\25B2"; font-size: 0.7em; }
th.sorted-desc .sort-arrow::after { content: "\25BC"; font-size: 0.7em; }
.overview-table td {
    padding: 0.6rem 0.55rem;
    border-bottom: 1px solid var(--border);
    vertical-align: top;
    line-height: 1.45;
}
.overview-table tbody tr:hover { background: var(--row-hover-bg); }
.overview-table tbody tr:last-child td { border-bottom: none; }
/* a row is a jump-mark landing since version 1.74 — the calendar page and
   the conference .ics both point at /conferences#<id>. It has to clear two
   sticky layers, not one: the 56px nav and the table's own header row on
   top of it, or the reader lands on a row the header is covering. */
.overview-table tbody tr { scroll-margin-top: 104px; }
/* A deep link that lands on a row or a card marks what it meant (OP173):
   an alarm mail, the calendar and — since 1.96 — the tables' name links
   all end in #<id>, and until this rule the reader was dropped at the
   right scroll position with nothing to say which entry was theirs. One
   rule for both entrances, wearing the same accent border the month grid
   already gives today's date. */
.overview-table tbody tr:target,
.card-item:target { outline: 2px solid var(--accent); outline-offset: -2px; }
/* the header row stays put while the page scrolls; 56px is the sticky nav */
.overview-table thead th {
    position: sticky;
    top: 56px;
    z-index: 3;
    background: var(--primary-bg);
}
.table-hint {
    font-size: 0.78rem;
    color: var(--text-muted);
    margin: 0.35rem 0;
}
.cell-deadline { white-space: nowrap; }
.deadline-value { font-weight: 600; color: var(--heading); }
/* the raw green is the dot colour and was only 4.27:1 as text (audit OP109).
   This is the one place that green is used as text, so it takes the darker
   -text variant like every other label does: 6.52:1. */
.deadline-value.bucket-1 { color: var(--tone-ok-text); font-weight: 500; }
.deadline-value.bucket-2 { color: var(--text-light); font-weight: 400; }
.deadline-value.bucket-3 { color: var(--text-light); font-weight: 400; }
/* The conference table's four states (version 1.73). They are their own
   classes rather than the grant buckets reused, because the numbers mean
   different things on the two tables and one of them — "the call has
   closed but the conference has not" — has no equivalent on the funding
   side at all. An open call is the loud one; everything else recedes. */
.deadline-value.conf-bucket-0 { color: var(--heading); font-weight: 600; }
.deadline-value.conf-bucket-1 { color: var(--text-light); font-weight: 400; }
.deadline-value.conf-bucket-2 { color: var(--tone-warn-text); font-weight: 500; }
.deadline-value.conf-bucket-3 { color: var(--text-light); font-weight: 400; }
.deadline-note {
    display: block;
    font-size: 0.7rem;
    color: var(--text-faint);
    /* the cell above says nowrap so a date never breaks in two, and the
       note inherited it — a whole sentence held on one line, spilling 139
       of its 211 pixels across the name column on a 360-pixel screen (I4,
       version 1.74). A sentence is not a date and wraps. */
    white-space: normal;
}
.row-expired { background: var(--row-expired-bg); }
.row-expired .cell-name a { color: var(--primary); }
.cell-target { color: var(--text-light); }
.cell-info { color: var(--text-light); }
/* up to three badges in a narrow column — they must wrap, not push the
   whole page sideways */
.cell-stages { white-space: normal; }
.cell-stages .stage-badge { margin-bottom: 0.15rem; }
.cell-website a {
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.stage-badge {
    display: inline-block;
    font-size: 0.7rem;
    font-weight: 700;
    padding: 0.1rem 0.35rem;
    border-radius: 4px;
    margin-right: 0.2rem;
    background: var(--primary-bg);
    color: var(--primary);
}
/* #777 on this chip was 3.89:1 for 11.2px bold text (audit OP109). #5c5c5c
   is the grey this sheet already uses wherever small text must clear the
   minimum, and reaches 5.82:1 here. */
.stage-badge.stage-org { background: var(--chip-off-bg); color: var(--text-muted); }
.overview-note {
    font-size: 0.8rem;
    color: var(--text-light);
    margin-top: 0.75rem;
    line-height: 1.5;
}

/* The organization page with nothing to list (OP169): one sentence and a
   way back instead of a header card above an empty page. The global reset
   zeroes paragraph margins, so the gap between the two lines is set here. */
.org-empty {
    margin: 1.5rem 0;
    color: var(--text-light);
    line-height: 1.5;
}
.org-empty p + p { margin-top: 1rem; }

/* Responsive */
@media (max-width: 768px) {
    .nav {
        padding: 0.5rem 0;
        height: auto;
        min-height: 56px;
    }
    /* the phone keeps its 1rem inset — on the inner box, as above */
    .nav-inner { gap: 0.4rem; padding: 0 1rem; }
    /* the four main links live in the bottom tab bar; what
       remains (alarm/login/logout, language) stays on ONE line
       right of the brand — nothing wraps left underneath */
    .nav-links {
        margin-left: auto;
        flex-wrap: nowrap;
        gap: 0.4rem;
    }
    .nav-links a, .nav-logout button {
        border: 1px solid var(--bar-btn-border-phone);
        border-radius: 8px;
        padding: 0.35rem 0.7rem;
        border-bottom-width: 1px;
    }
    .nav-links a:hover, .nav-links a.active {
        border-color: var(--accent);
    }
    /* the appearance switch is a form, not a link, so the rule above never
       reaches it: it takes the phone bar's own border here, and it refuses
       to be squeezed, so it stays on that one line instead of being
       compressed to a sliver next to the language toggle (OP22) */
    .theme-toggle {
        border-color: var(--bar-btn-border-phone);
        border-radius: 8px;
        flex-shrink: 0;
    }
    .cal-grid { min-width: 0; }
    .cal-grid th { padding: 0.2rem; font-size: 0.7rem; }
    .cal-grid td { height: 3.6rem; padding: 0.15rem; }
    .cal-daynum { font-size: 0.65rem; margin-bottom: 0.1rem; }
    .cal-entry { font-size: 0.6rem; padding: 0.1rem 0.25rem; }
    .cal-nav { gap: 0.5rem; }
    .cal-nav h2 { min-width: 0; font-size: 1.05rem; }
    .page-title { font-size: 1.7rem; }
    .hero .subtitle { font-size: 1rem; }
    main { padding: 1rem; }
    .cards-grid { grid-template-columns: 1fr; }
    .filter-top { flex-wrap: wrap; }
    .inst-detail-header { padding: 1.25rem; }
    .inst-detail-header h1 { font-size: 1.4rem; }
}

/* The table always fits: as the window narrows, the least useful columns
   step out one by one, widest first. Nothing is lost — the CSV and Excel
   downloads always contain every column. */
@media (max-width: 1180px) {
    #overview-table th:nth-child(n+9),
    #overview-table td:nth-child(n+9) { display: none; }
    /* columns 1, 3 and 6 keep their top-level shares (7.7 + 8 + 9), so
       the name column's line makes the eight on screen add up to 100 —
       the OP168 re-cut above changed 3 and 6, and this line moved with
       them (it was 21% while the eight summed to 100.9). */
    #overview-table th:nth-child(2) { width: 22.3%; }
    #overview-table th:nth-child(4) { width: 10%; }
    #overview-table th:nth-child(5) { width: 16%; }
    #overview-table th:nth-child(7) { width: 12%; }
    #overview-table th:nth-child(8) { width: 15%; }
}
@media (max-width: 900px) {
    #overview-table th:nth-child(8),
    #overview-table td:nth-child(8) { display: none; }
    #overview-table th:nth-child(1) { width: 9%; }
    #overview-table th:nth-child(2) { width: 26%; }
    #overview-table th:nth-child(3) { width: 11%; }
    #overview-table th:nth-child(4) { width: 11%; }
    #overview-table th:nth-child(5) { width: 18%; }
    #overview-table th:nth-child(6) { width: 9%; }
    #overview-table th:nth-child(7) { width: 16%; }
}
/* Below this the Fachrichtung steps out with the Zielgruppe, so the four
   columns that survive on a phone are exactly the four that survived
   before version 1.68 — the 360px layout OP130 measured is untouched. */
@media (max-width: 680px) {
    /* The opened filter panel is the tallest thing on a phone: six pill
       rows, each pill on a line of its own at this width, plus a sentence
       explaining every row. Measured at 360px it was 1382px against a
       760px screen. Trimming the side padding and the explainer type takes
       it to 1172, and the second fold keeps three of the six rows out of
       that number entirely (A5). The pills themselves are untouched — they
       stack one per line here and always have. */
    /* A 8.5rem label column beside the pills leaves 150px for the pills
       on a 360px screen. Here the name goes above its own row instead. */
    .stage-filter > .filter-body { padding: 0 0.6rem 1rem; }
    .pill-line { flex-direction: column; align-items: stretch; gap: 0.25rem; }
    .pill-row-label { flex: none; text-align: left; }
    .filter-note { margin-left: 0; }
    #overview-table th:nth-child(5),
    #overview-table td:nth-child(5) { display: none; }
    #overview-table th:nth-child(7),
    #overview-table td:nth-child(7) { display: none; }
    #overview-table th:nth-child(1) { width: 16%; }
    #overview-table th:nth-child(2) { width: 38%; }
    #overview-table th:nth-child(3) { width: 15%; }
    #overview-table th:nth-child(4) { width: 17%; }
    #overview-table th:nth-child(6) { width: 14%; }
    .overview-table { font-size: 0.8rem; }
    .overview-table th, .overview-table td { padding: 0.5rem 0.45rem; }
    /* the short headings take over here, with the first dropped column
       (OP130) */
    .th-wide { display: none; }
    .th-narrow { display: inline; }
    /* the arrow reserves 0.9em at every width so a heading does not shift
       when it becomes the sorted one. In the narrowest columns that
       reservation was the last thing that did not fit, and it pushed
       itself onto a second line, leaving every header cell two lines tall
       for nothing. Here it takes only the room it actually uses. */
    .overview-table th .sort-arrow { width: auto; }
}
@media (max-width: 460px) {
    #overview-table th:nth-child(4),
    #overview-table td:nth-child(4) { display: none; }
    /* Measured at 360px, where the four surviving columns share 294px.
       The widest wording each of them has to hold is FUNDER at 46px,
       STAGE at 37px and the deadline date itself at 64px; at the old
       18/16 shares and 0.45rem of side padding the funder and career
       headings had 39px and 33px, which is why even a short wording still
       broke mid-word (OP130). Half a millimetre off the side padding pays
       for most of it, so the name column only gives up four points. */
    #overview-table th, #overview-table td { padding: 0.5rem 0.3rem; }
    #overview-table th:nth-child(1) { width: 25%; }
    #overview-table th:nth-child(2) { width: 38%; }
    #overview-table th:nth-child(3) { width: 20%; }
    #overview-table th:nth-child(6) { width: 17%; }
}

/* The Konferenzen table drops columns in an order of its own (version
   1.73). The shared rules above are positional — nth-child — and were
   tuned for the funding table, whose columns are not these. Left alone
   they kept the submission TYPE on a phone and dropped the event date and
   the place, which is exactly backwards: a researcher looking at a
   conference on a phone wants to know when the call closes, what it is
   called, when it happens and where.

   The four that survive at 360px are therefore CfP deadline, name, event
   date and place. Everything else steps out, widest and least useful
   first, and the CSV and Excel downloads still carry every column.

   Every selector below carries the #overview-table id as well as the
   class, and it has to: the shared rules are written `#overview-table
   th:nth-child(5)`, and an id beats a class whatever the order in the
   file. Without it these rules are ignored and the table keeps the
   funding page's columns. */
@media (max-width: 1180px) {
    /* website out */
    #overview-table.conf-table th:nth-child(9),
    #overview-table.conf-table td:nth-child(9) { display: none; }
}
@media (max-width: 900px) {
    /* first-discovered out */
    #overview-table.conf-table th:nth-child(8),
    #overview-table.conf-table td:nth-child(8) { display: none; }
    /* OP165's third band, and the one the first fix missed: a phone in
       landscape, or a small desktop window. Two columns have stepped out
       here and none of the seven that remain had been given a share of
       their own, so they kept the ones written for the full table — ten
       plus twenty-one plus eleven and so on, 84% between them. The other
       16% went out by a rule nobody wrote and the Termin column landed
       near 70 pixels gross, under 62 inside its padding. A date is 68
       pixels at the 0.85rem this band still carries — wider than the
       phone's, not narrower — so `overflow-wrap: break-word` split it in
       the middle of the year exactly as it did on a phone.

       The seven below add up to 100. The Termin column's 16% is 98px at
       the narrow end of the band (615px of table at a 681-pixel window),
       81px of it inside the padding, against a 68px date. The CfP column
       beside it takes 14% for the same reason and needs every point of it:
       it is `white-space: nowrap`, so a date that does not fit does not
       wrap — it runs into its neighbour. */
    /* Re-cut 2026-08-02 with the desktop shares: columns 1 and 4 keep the
       OP165-measured 14% and 16% — the arithmetic in
       tests/test_stylesheet.py stands on them — and the points for
       Bewerbung come from the name, the Org and the Ort columns. */
    #overview-table.conf-table th:nth-child(1) { width: 14%; }
    #overview-table.conf-table th:nth-child(2) { width: 19%; }
    #overview-table.conf-table th:nth-child(3) { width: 8%; }
    #overview-table.conf-table th:nth-child(4) { width: 16%; }
    #overview-table.conf-table th:nth-child(5) { width: 10%; }
    #overview-table.conf-table th:nth-child(6) { width: 21%; }
    #overview-table.conf-table th:nth-child(7) { width: 12%; }
}
@media (max-width: 680px) {
    /* subject and submission type out; the event date and the place come
       back in, because the shared rule at this width hides column 5 and 7
       for the funding table's sake */
    #overview-table.conf-table th:nth-child(6),
    #overview-table.conf-table td:nth-child(6) { display: none; }
    #overview-table.conf-table th:nth-child(7),
    #overview-table.conf-table td:nth-child(7) { display: none; }
    #overview-table.conf-table th:nth-child(5),
    #overview-table.conf-table td:nth-child(5) { display: table-cell; }
    /* OP165. The Termin column took six points off the conference name:
       at 16% it was narrower than the date it holds, so the break-word
       rule at the top of the table section split every single one of them
       in the middle of the year — "22.10.20" over "26", read as 2020. The
       date is 64px at the 0.8rem this band carries; 22% of the narrowest
       table this block sees (395px at a 461-pixel window) is 87px, and
       14.4px of that is side padding, which leaves 72px for a 64px date.
       tests/test_stylesheet.py does that arithmetic at every width in this
       range, so a future point taken back off this column fails rather
       than quietly splitting dates again.

       The width is the whole cure and it is the only one: `break-word`
       breaks a word only where it cannot fit the line by itself, so a
       column that holds a whole date never splits one. The rule stays in
       force here on purpose — it is the net under the widths, and it is
       what makes a window narrower than anything measured wrap a date
       instead of running it into the next column. */
    #overview-table.conf-table th:nth-child(1) { width: 22%; }
    #overview-table.conf-table th:nth-child(2) { width: 28%; }
    #overview-table.conf-table th:nth-child(3) { width: 14%; }
    #overview-table.conf-table th:nth-child(4) { width: 22%; }
    #overview-table.conf-table th:nth-child(5) { width: 14%; }
}
@media (max-width: 460px) {
    /* the organiser is the one that goes at the very narrowest: it is
       repeated down the column and the conference's own name usually
       carries it anyway */
    #overview-table.conf-table th:nth-child(3),
    #overview-table.conf-table td:nth-child(3) { display: none; }
    #overview-table.conf-table th:nth-child(4),
    #overview-table.conf-table td:nth-child(4) { display: table-cell; }
    /* OP165 again, and this is where it was measured: at 390 pixels the
       Termin column was 64.8px with 55.2px inside its padding, and 46 of
       46 rows broke their date apart. Eight points off the conference name
       buy 90.7px here and 82.3px at 360 pixels — 72.7px of content against
       a 64px date, which is the narrowest this table ever gets.

       The cure was width — a column that
       holds its longest word never splits one — and the 64px is one
       browser's rendering of "22.10.2026" at 12.8px; the English form
       "22/10/2026" is the same width to the pixel in this family, so both
       languages sit on the same number.

       Re-cut for OP173: the Ort column broke every second place name the
       same way — "Greifsw/ald", "Längse/e" — because 18% left it 43px
       against words of 49-70px. This band is zero-sum: the two date
       columns, the name's own words ("International" is 70.4px) and a
       place word add up to more than a 294px table can hold. So the
       conference cells give up 0.1rem of side padding each (the OP130
       move — 12.8px back across four columns), the Ort column takes 22%,
       and the name pays the rest. At 360 that is, content against
       longest word: CfP 67.1 vs 64.1, name 73.0 vs 70.4, Termin 70.0 vs
       64.1, Ort 58.3 vs 49.1 ("Längsee") — and at 390: Ort 64.9 against
       60.5 ("Greifswald"). What this cannot save at 360 is the ten-letter
       tier and up — "Klagenfurt" misses by 0.1px, "Greifswald" by 2.2,
       "Philadelphia" (69.7) misses throughout the band: they wrap, once,
       at the break-word net, which stays in force exactly as OP165 left
       it. tests/test_stylesheet.py
       does this arithmetic too, so a point taken back off the Ort column
       fails there rather than quietly splitting names again. */
    #overview-table.conf-table th, #overview-table.conf-table td {
        padding: 0.5rem 0.2rem;
    }
    #overview-table.conf-table th:nth-child(1) { width: 25%; }
    #overview-table.conf-table th:nth-child(2) { width: 27%; }
    #overview-table.conf-table th:nth-child(4) { width: 26%; }
    #overview-table.conf-table th:nth-child(5) { width: 22%; }
}

@media (max-width: 480px) {
    .nav-brand span { display: none; }
    .grant-meta { flex-direction: column; gap: 0.25rem; }
}

/* Print: the table on paper, nothing else. */
@media print {
    .nav, .tabbar, footer, .overview-toolbar, .stage-filter,
    .table-hint, .skip-link, .bell-btn, .cal-pill { display: none !important; }
    body { background: var(--print-bg); }
    main, main.wide { max-width: none; padding: 0; }
    .table-scroll { border: none; }
    .overview-table { font-size: 9pt; }
    .overview-table thead th { position: static; }
    .overview-table tbody tr { page-break-inside: avoid; }
    a { color: var(--print-ink); text-decoration: none; }
}

/* Skip link: first thing keyboard and screen-reader users reach. */
.skip-link {
    position: absolute;
    left: -9999px;
    top: 0;
    z-index: 200;
    background: var(--bar-bg);
    color: var(--on-bar);
    padding: 0.6rem 1rem;
    border-radius: 0 0 8px 0;
}
.skip-link:focus {
    left: 0;
    color: var(--on-bar);
}
main:focus { outline: none; }

/* Tap targets on phones: every pill and small button reaches 44px. */
@media (max-width: 768px) {
    /* .btn joined this list on 2026-07-26: the 390px check found the
       account page's buttons at 28px, well under what a finger needs */
    .pill, .cal-pill, .cal-kind, .bell-btn, .btn { min-height: 44px; }
    .cal-pill { display: inline-flex; align-items: center; }
    /* the "?" beside the reset pill: 28px is right for a mouse and well
       under what a finger needs, so it grows here like everything else */
    .filter-help-btn {
        min-width: 44px;
        min-height: 44px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }
    /* the panel's own ✕ for the same reason */
    .help-panel-close { min-width: 44px; min-height: 44px; }
    /* the search box on both list pages: the card page's own three
       controls were 34-36px tall (audit OP107) and went with OP111, but
       the box that replaced them needs the same floor */
    #table-search { min-height: 44px; }
    /* The table's own links were left out of the list above and measured 17px,
       under the 24px minimum (audit OP117): every grant name, every funder
       name and the nine sort buttons. 0.35rem top and bottom takes them to
       28px. The rows are tall enough to absorb it, and since nothing here
       touches a width, the column drop-out order is unchanged. */
    #overview-table td.cell-name a,
    #overview-table td:nth-child(3) a {
        display: block;
        padding: 0.35rem 0;
    }
    #overview-table th .sort-btn { padding: 0.35rem 0; }
    /* OP136: the log-out button in the top bar measured 39px where every
       other control there keeps 44. It is a bare <button> inside a form, so
       the list above never reached it. A min-height on a plain button does
       not centre what is inside it — the word would sit against the top
       edge — hence the flex centring alongside the floor. */
    .nav-logout button {
        min-height: 44px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }
    /* OP140: OP136 raised the log-out button and its own text claimed every
       other control in the bar had already been raised. That was not true of
       the links beside it — alarm, admin, log-in and the language toggle all
       measured 38px, because .btn never reached a plain <a>. Same shape as the
       log-out rule above, and for the same reason: a min-height alone would
       leave the word against the top edge.

       The four main links stay hidden on a phone: .nav-links a.nav-main
       carries display:none at two classes to this rule's one, so the higher
       specificity wins wherever the two sit in the file. */
    .nav-links a {
        min-height: 44px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }
    /* the appearance switch (OP22) reaches the same floor in both
       directions: its content is an 18px icon, which on its own leaves a
       target far under what a finger needs */
    .theme-toggle button { min-height: 44px; min-width: 44px; }
}

/* Irreversible action: it must not look like the ordinary save button. */
.btn-danger {
    background: var(--danger);
    color: var(--on-primary);
}
.btn-danger:hover { background: var(--danger-hover); color: var(--on-primary); }

/* Style attributes lifted out of the page markup into classes — the repeated
   ones in OP72, the last single ones in OP135. Each rule carries exactly the
   declarations its attribute carried, so nothing on any page moves. They sit
   at the end on purpose: an attribute always beat the rule it duplicated, so
   the class has to come after that rule to win the same way.
   There are no style attributes left in the templates. Adding one back is not
   a security fault — style-src still allows inline styles — it is simply a
   look written somewhere other than the one place looks live. */
.subtitle-sm { font-size: 0.95rem; }
.spaced-top { margin-top: 0.5rem; }
.spaced-top-md { margin-top: 0.6rem; }
.spaced-top-lg { margin-top: 0.75rem; }
.flush-top { margin-top: 0; }
.flush-bottom { margin-bottom: 0; }
.flush { margin: 0; }
.inset-y { padding: 1rem 0; }
.days-input { width: 4.5rem; }
.section-heading { font-size: 1.1rem; margin: 2rem 0 0.35rem; }
.meta-note { color: var(--text-light); font-size: 0.85rem; }
.form-spaced { margin: 0.6rem 0; }

/* One release on the version page: the number and its date on one line, the
   headline under it, then the change list. */
.version-entry { margin-top: 1.25rem; }
.version-head {
    display: flex;
    align-items: baseline;
    gap: 0.6rem;
    flex-wrap: wrap;
}
.version-number { font-size: 1.05rem; }
.version-commit { color: var(--text-faint); font-size: 0.75rem; }
.version-title { font-weight: 600; margin-top: 0.15rem; }
.version-changes {
    margin: 0.35rem 0 0 1.2rem;
    font-size: 0.9rem;
    color: var(--text-muted);
}

/* ---------------------------------------------------------------------
   The landing page at "/" (version 1.94). Everything home-* below is
   drawn only there. The radar disc is the brand icon made honest — every
   dot a real upcoming deadline — and it takes no colour of its own:
   rings on --border, sweep and hub on --primary, blips on --accent. The
   sweep is the page's one animation and stands still for a visitor who
   asked for reduced motion.
   --------------------------------------------------------------------- */
.home { max-width: 1080px; margin: 0 auto; }
.home-hero {
    display: grid;
    grid-template-columns: 1fr minmax(220px, 320px);
    gap: 3rem;
    align-items: center;
    padding: 2rem 0 2.5rem;
}
.home-eyebrow {
    color: var(--accent);
    font-size: 0.92rem;
    font-weight: 600;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    margin-bottom: 0.7rem;
}
.home-title {
    font-size: clamp(2rem, 5vw, 3.1rem);
    line-height: 1.1;
    color: var(--heading);
    margin-bottom: 1rem;
}
.home-hero-lead {
    font-size: 1.12rem;
    line-height: 1.55;
    color: var(--text-light);
    max-width: 34em;
}
.home-cta { display: flex; gap: 0.75rem; flex-wrap: wrap; margin: 1.5rem 0 1.1rem; }
.home-stats { color: var(--text-muted); font-size: 0.95rem; font-variant-numeric: tabular-nums; }
.home-radar { width: 100%; height: auto; display: block; }
.home-radar-rings { stroke: var(--border); fill: none; }
.home-radar-sweep {
    fill: var(--primary);
    opacity: 0.16;
    transform-origin: 100px 100px;
    animation: home-sweep 9s linear infinite;
}
@keyframes home-sweep { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
    .home-radar-sweep { animation: none; }
}
.home-radar-hub { fill: var(--primary); }
.home-radar-blips circle { fill: var(--accent); }

.home-h2 { font-size: 1.45rem; color: var(--heading); margin: 2.4rem 0 1rem; }
.home-h3 { font-size: 1.02rem; margin-bottom: 0.5rem; }
.home-columns { display: grid; grid-template-columns: 1fr 1fr; gap: 1.25rem; }
.home-panel {
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 1.1rem 1.25rem;
}
.home-list { list-style: none; margin: 0; padding: 0; }
.home-list li + li { border-top: 1px solid var(--border); }
.home-list a {
    display: grid;
    grid-template-columns: auto 1fr;
    column-gap: 0.9rem;
    align-items: baseline;
    padding: 0.6rem 0;
    text-decoration: none;
    color: var(--text);
}
.home-item-date {
    color: var(--accent);
    font-size: 0.92rem;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
.home-item-name { font-weight: 600; line-height: 1.35; }
.home-item-who { grid-column: 2; color: var(--text-muted); font-size: 0.88rem; }
.home-list a:hover .home-item-name { color: var(--primary-light); }

.home-steps { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.25rem; }
.home-step {
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 1.15rem 1.25rem;
}
.home-step-n {
    display: inline-flex;
    width: 1.9rem;
    height: 1.9rem;
    border-radius: 50%;
    background: var(--primary-bg);
    color: var(--primary-light);
    align-items: center;
    justify-content: center;
    font-weight: 700;
    margin-bottom: 0.7rem;
}
.home-step p { color: var(--text-light); font-size: 0.95rem; line-height: 1.5; }

.home-trust {
    border-left: 3px solid var(--accent);
    padding: 0.4rem 0 0.4rem 1.1rem;
    margin: 2.4rem 0;
}
.home-trust-line { font-size: 1.05rem; max-width: 46em; }

.home-signup {
    text-align: center;
    background: var(--primary-bg);
    border-radius: var(--radius);
    padding: 2rem 1.25rem 2.2rem;
    margin-bottom: 1rem;
}
.home-signup .home-h2 { margin: 0 0 0.5rem; }
.home-signup-text { color: var(--text-light); margin: 0 auto 1.3rem; max-width: 38em; }

@media (max-width: 820px) {
    .home-hero { grid-template-columns: 1fr; gap: 1.75rem; padding-top: 1rem; }
    .home-hero-radar { width: min(64vw, 280px); margin: 0 auto; }
    .home-columns, .home-steps { grid-template-columns: 1fr; }
}
