body {
    font-family: system-ui, sans-serif;
    margin: 0;
    color: #1a1a1a;
}

/* AC-9 point 1: 100dvh, not 100vh — on mobile, 100vh includes space the browser's own
   address-bar chrome later reclaims, which would clip the bottom of the review view a moment
   after it first renders. header/main/footer share this fixed height instead of the page
   growing past it, so only main (and, inside it, the two review panes) ever scrolls. */
.page {
    display: flex;
    flex-direction: column;
    height: 100dvh;
}

.page-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 0.75rem 1.5rem;
    border-bottom: 1px solid #ddd;
    flex: 0 0 auto;
}

.app-title {
    font-weight: 600;
}

.logout-link {
    background: none;
    border: none;
    padding: 0;
    color: #0645ad;
    text-decoration: underline;
    cursor: pointer;
    font: inherit;
}

.page-footer {
    padding: 0.5rem 1.5rem;
    border-top: 1px solid #ddd;
    flex: 0 0 auto;
}

.budget-indicator {
    margin: 0;
    color: #777;
    font-size: 0.85rem;
}

.recognition-cost {
    color: #444;
    font-size: 0.9rem;
}

main {
    padding: 1.5rem 2rem;
    /* Olaf, live feedback: the review view (image + table) left most of the viewport unused
       on wide screens. No hard cap here anymore — the review layout below now uses whatever
       width is available; .page-intro keeps its own 70ch cap so prose stays readable. */
    max-width: 2400px;
    margin: 0 auto;
    /* AC-9: main is .page's only growing child (header/footer are flex:0 0 auto above), and a
       flex column itself so .review-view below can claim "whatever's left of 100dvh" via its
       own flex:1 instead of a hardcoded height. overflow-y:auto is main's fallback for the
       non-review stages, which have no height constraint of their own and stay exactly as
       before — it only ever activates for a viewport too short even for their few lines.
       flex-basis 0%, not auto: with auto, main's "preferred" height for the flexbox algorithm
       is its own unconstrained content height — on the Review stage that's the full table (can
       easily be 1000px+), which made main compete for shrink space against header/footer using
       that huge number and, one level down, made the exact same mistake collapse .review-layout
       to 0 (see its own comment below) instead of just filling whatever 100dvh leaves over. */
    flex: 1 1 0%;
    min-height: 0;
    display: flex;
    flex-direction: column;
    overflow-y: auto;
}

/* AC-9: wraps everything the Review stage renders (Upload.razor) so review-layout is the one
   part of it that grows — the heading/route/cost/hint/legend above and the Approve button
   below keep their natural height, same as page-intro/stepper above this whole block.
   flex-basis 0%, same reason as main above: with auto, this item's own huge unconstrained
   content height (the full review-layout) makes it the biggest thing in the shrink
   calculation instead of just growing into whatever's left of main. */
.review-view {
    display: flex;
    flex-direction: column;
    flex: 1 1 0%;
    min-height: 0;
}

.page-intro {
    color: #444;
    max-width: 70ch;
}

.upload-form {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    max-width: 40ch;
}

/* Same specificity as .upload-form beats the UA [hidden] rule on origin order alone —
   this selector needs the extra specificity to actually hide the file picker while
   Stage == Uploading (see Upload.razor). */
.upload-form[hidden] {
    display: none;
}

.error-message {
    color: #9b1c1c;
}

/* AC-3/AC-9: image and table visible side by side, each scrolling independently within a row
   that fills whatever height .review-view (above) leaves them — no gap here, the draggable
   divider between the two panes is the only separation (a plain gap would double up with it).
   align-items:stretch (the flex default, named explicitly here) is what makes both panes match
   review-layout's own height — the whole point of min-height:0 below, see the flex/overflow
   pattern note on .review-table.
   flex-basis 0%, not auto (Karpathy note to a future reader who "fixes" this back to auto and
   reintroduces the bug): review-layout's own unconstrained content is the WHOLE table, easily
   1000px+ tall on a real document. With auto, that huge number is what the CSS flexbox
   shrink algorithm distributes shrink-space by — review-layout, being by far the largest of
   review-view's children, absorbs almost the entire deficit and collapses to min-height:0
   (verified live: measured exactly 0px height, table present in the DOM but invisible). With
   0%, review-layout's own size is ignored for that calculation; it just grows to fill
   whatever review-view has left after its small, fixed-size siblings (h2, hints, Approve
   button) take their natural height. */
.review-layout {
    display: flex;
    align-items: stretch;
    flex: 1 1 0%;
    min-height: 0;
}

/* AC-9 point 2/4: at least half the window width to start (Olaf: a drag handle, not a toggle) —
   --image-pane-width is written directly by reviewAnchor.js#initSplitResize while dragging the
   divider, entirely client-side. The var()'s own 50% fallback IS the starting value, so no JS
   needs to run before the first paint. Dragging toward SplitMinPercent/SplitMaxPercent
   (Upload.razor.cs) minimizes one pane without a separate collapse control (AC-9 point 4). */
.review-image {
    flex: 0 0 var(--image-pane-width, 50%);
    min-width: 0;
    /* Independent scroll (AC-9 point 1) for the rare case the unzoomed page image is taller
       than the pane — pan/zoom (AC-12, untouched) still does the fine-grained work once
       zoomed in; this is only the coarse "see the rest of a very tall page" fallback. Deliberately
       NOT display:flex — .review-image-viewport must keep its intrinsic (image-driven) size,
       see the AC-12 comment on it in Upload.razor, so a native scrollbar on this outer pane is
       the fix, not stretching the viewport to fill the pane. */
    min-height: 0;
    overflow-y: auto;
}

/* AC-9 point 4: draggable, not just an on/off toggle (Olaf) — see reviewAnchor.js#initSplitResize.
   ARIA "separator"/window-splitter pattern (Upload.razor): focusable, arrow keys resize by a
   fixed step. Dragging close to either edge functionally minimizes the other pane, which is
   what satisfies "structure stays readable in the table alone" without a separate button. */
.review-split-divider {
    flex: 0 0 8px;
    cursor: col-resize;
    background: #ddd;
    touch-action: none;
}

.review-split-divider:hover {
    background: #999;
}

.review-split-divider:focus-visible {
    outline: 3px solid #1565c0;
    outline-offset: -1px;
}

.review-table {
    flex: 1 1 auto;
    min-height: 0; /* flex items default to min-height:auto (their own content size), which
                       would otherwise stop this pane from ever shrinking below "every row at
                       once" and defeat the overflow-y below — the actual AC-9 point 1 fix. */
    overflow-y: auto;
    /* No overflow-x here on purpose: table-layout:fixed below already keeps the table within
       its own width, and CSS quietly turns overflow-y into "auto" too the moment overflow-x
       isn't "visible" (the spec's overflow-axis-coupling rule) — that clipped the AC-12
       cell-focus preview, which is deliberately positioned to stick out above its row. */
}

table {
    border-collapse: collapse;
    width: 100%;
    table-layout: fixed;
    /* AC-9 point 3: compact rows so more positions are visible without scrolling. */
    font-size: 0.85rem;
}

th, td {
    border: 1px solid #ddd;
    padding: 0.25rem 0.4rem;
    text-align: left;
    vertical-align: top;
}

/* AC-9 point 3: the column headers stay visible while .review-table scrolls — with rows this
   compact a 20+ position document scrolls several screens, and losing the header first makes
   the AC-9 point 4 "structure stays readable without the image" goal worse, not better. */
thead th {
    position: sticky;
    top: 0;
    background: #fff;
    z-index: 1;
}

/* Olaf (Leseschwäche, live feedback): Description was being truncated ("FACILITIES FOR T",
   "Provision of Field C") — fixed table-layout above needs explicit column widths so the
   description gets the room the narrow numeric columns don't need.
   WI-046: was nth-child(2) (fixed 36%), which assumed the classic 6-column Bill-of-Quantities
   shape with Description always in the second position. An AC-11 document with a different
   column count/order — a Program-of-Works cover sheet, Description first, then Quantity | Unit |
   % Total | Total Direct Cost | % Total | Total Direct Cost — put Description in nth-child(1)
   instead, which the old rule sized to 8%, squeezing "Part A: Facilities for the Engineer" down
   to "Part A: Faciliti" while the empty percentage columns got the leftover width. Driven by
   Upload.razor.cs's ColumnCssClass (role/type, not position) instead, so this holds for any
   column order or count: the Description column stays dominant, every numeric/unit column stays
   narrow, and horizontal scrolling (.review-table has no overflow-x, see above) is not needed for
   this width fix specifically. */
.col-description { width: 30%; min-width: 220px; }
.col-number { width: 70px; }
.col-text { width: 100px; }

td input[type="text"], td input:not([type]) {
    width: 100%;
    box-sizing: border-box;
}

/* Description now wraps across lines instead of truncating in a single-line input. Explicit
   overflow-wrap since .review-table no longer has overflow-x:auto as a safety net (see
   above) — a long unbroken token (rare, but possible OCR output) must still wrap, not push
   the column wider. */
td textarea {
    width: 100%;
    box-sizing: border-box;
    font: inherit;
    resize: vertical;
    min-height: 2.4em;
    overflow-wrap: break-word;
}

.plausibility-reason {
    color: #7a5b00;
    font-size: 0.85rem;
    margin: 0.25rem 0;
}

/* AC-12: the three-state box-border status, driven by --status-color so both the table row
   and the image box read it from the same place. */
.status-open {
    --status-color: #8a8a8a;
}

.status-reviewed {
    --status-color: #2e7d32;
}

.status-flagged {
    --status-color: #b25900;
    background-color: #fff6db;
}

tr[class*="status-"] {
    border-left: 4px solid var(--status-color);
    cursor: pointer;
}

tr.selected {
    outline: 2px solid #1a1a1a;
    outline-offset: -2px;
}

tr:focus-visible, .review-box:focus-visible {
    outline: 3px solid #1565c0;
    outline-offset: 1px;
}

/* AC-12 point 3: the non-committing hover preview — toggled by reviewAnchor.js, never by a
   Blazor event, so hovering 30 rows never round-trips to the server. */
tr.hover-preview, .review-box.hover-preview {
    filter: brightness(0.95);
}

.no-box-hint {
    margin-left: 0.35rem;
    cursor: help;
}

/* Confirmed column, compact (Olaf, live feedback): a title/aria-label tooltip on the badge
   carries the AC-4 reason text instead of a full <p> sentence, and the checkbox sits inline
   next to it instead of stacked below — one row-height line instead of three. */
.confirm-cell {
    white-space: nowrap;
}

.reason-badge {
    cursor: help;
    margin-right: 0.35rem;
}

.confirm-inline {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    font-size: 0.8rem;
    white-space: nowrap;
}

/* Statusfarbe direkt am Feld (Olaf, live feedback): the compact badge above names the reason,
   this is the immediately-visible signal — only on the fields PlausibilityChecker actually
   evaluates (Quantity, UnitPrice, Amount). */
.input-flagged {
    border-color: #c62828;
    border-width: 2px;
}

.keyboard-hint {
    color: #666;
    font-size: 0.85rem;
}

.status-legend {
    display: flex;
    gap: 1rem;
    list-style: none;
    padding: 0;
    margin: 0 0 0.75rem;
    font-size: 0.85rem;
}

.status-legend-item {
    display: flex;
    align-items: center;
    gap: 0.35rem;
}

.status-swatch {
    display: inline-block;
    width: 0.85rem;
    height: 0.85rem;
    border-radius: 2px;
    background: var(--status-color);
}

/* AC-12 point 6: zoom/pan/fit, pure CSS transform driven entirely by C# state
   (Upload.razor.cs) — no JS zoom framework. The viewport clips to its own (unscaled) size,
   which is exactly the image's rendered size since it just wraps it — that equality is what
   lets the centering math skip measuring the image itself, only the viewport once. */
.zoom-controls {
    display: flex;
    gap: 0.35rem;
    margin: 0.35rem 0;
}

/* AC-10: XLSX/CSV download buttons, same plain flex-row layout as .zoom-controls above. */
.export-buttons {
    display: flex;
    gap: 0.35rem;
    margin: 0.5rem 0;
}

.review-image-viewport {
    overflow: hidden;
    position: relative;
    border: 1px solid #ddd;
    touch-action: none;
    cursor: grab;
}

.review-image-viewport:active {
    cursor: grabbing;
}

.review-image-content {
    position: relative;
    transform-origin: 0 0;
}

.review-image-content img {
    display: block;
    width: 100%;
    height: auto;
}

.review-box-overlay {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
}

.review-box {
    fill: transparent;
    /* transparent, not none — an SVG shape with fill:none doesn't receive pointer events over
       its interior, only its stroke line. AC-12 point 1 needs the whole box clickable. */
    stroke: var(--status-color, #8a8a8a);
    stroke-width: 1.5;
    pointer-events: all;
    cursor: pointer;
}

.review-box.selected {
    stroke-width: 3;
}

/* WI-042 AC-1/AC-3: a borderless image crop of the row's own bounding box, rendered directly
   above the cell's own input inside the cell — not a floating overlay (position:absolute over
   "auto" height fails: a <td>'s computed height is the literal keyword "auto", not a resolved
   px number, so neither `height: inherit` nor `height: 100%` on an absolutely positioned child
   has anything real to inherit/resolve against — confirmed live, WI-042 review). In-flow,
   width: 100% instead: it takes the cell's own rendered column width (itself not fixed, and the
   one dimension a <td> DOES resolve before layout) and derives its height from the box's own
   aspect-ratio (set inline, per row, in Upload.razor.cs#InlineCropStyle) — same "nothing fixed"
   intent, the adapting axis is the cell's width instead of its height.
   Every row's crop is in the DOM already (see Upload.razor) — only display toggles on focus, so
   there's nothing left to fetch or compute at that point. */
.inline-crop {
    display: none;
    width: 100%;
    margin-bottom: 0.25rem;
    background-repeat: no-repeat;
    border: 1px solid #999;
}

.inline-crop-visible {
    display: block;
}

/* AC-8: four visible, ordered steps — done/current/pending stay visually distinct so the
   app never looks frozen while analysis (~20s) runs. */
.stepper {
    display: flex;
    gap: 0;
    list-style: none;
    padding: 0;
    margin: 1rem 0;
    max-width: 60ch;
}

.stepper .step {
    flex: 1;
    padding: 0.5rem 0.75rem;
    border-bottom: 3px solid #ddd;
    color: #999;
    font-size: 0.9rem;
}

.stepper .step-done {
    border-bottom-color: #2e7d32;
    color: #2e7d32;
}

.stepper .step-current {
    border-bottom-color: #1a1a1a;
    color: #1a1a1a;
    font-weight: 600;
}

.analyzing-hint {
    color: #666;
    font-size: 0.9rem;
}

.culture-selector {
    display: flex;
    gap: 0.25rem;
}

.culture-button {
    min-width: 2.5rem;
    min-height: 2rem;
    border: 1px solid #999;
    background: #fff;
    cursor: pointer;
}

.culture-button--active {
    background: #1a1a1a;
    color: #fff;
    cursor: default;
}

/* AC-9 is a desktop-width fix (docs/specs/US-001-upload-und-freigabe.md's own AC-3 scopes the
   side-by-side requirement to desktop width). Below this breakpoint, undo it back to the
   pre-AC-9 behaviour — a whole-page-scrolling stack — rather than trying to make a draggable
   side-by-side split and two independently-scrolling panes work on a phone-width screen too,
   which nothing here has actually verified. */
@media (max-width: 900px) {
    main {
        display: block;
        overflow-y: visible;
    }

    .review-view {
        display: block;
    }

    .review-layout {
        display: flex;
        flex-direction: column;
        flex: none;
        min-height: 0;
    }

    .review-image {
        flex-basis: auto;
        min-height: 0;
        overflow-y: visible;
    }

    .review-split-divider {
        display: none;
    }

    .review-table {
        flex: none;
        overflow-y: visible;
    }
}
