﻿/* Request detail page — hero, the submitted record, and a sticky decision rail.
   Shares the app-wide dialog language from dialog.css; this file layers the
   detail-specific pieces on top. */

/* Roughly the RadzenFooter's own height — the one number here that isn't
   derived. Nudge it if the footer's padding ever changes. */
.case-page.is-locked { --case-sticky-height: 12.3rem; }

/* The three bars as one. Sticky on the box rather than on each of its children: three
   sticky siblings each needed to know the height of the one above it, and any error in
   those numbers showed as page content slipping through the seam between them. */
.case-sticky {
    position: sticky;
    top: var(--admin-header-height);
    z-index: 50;
    background: var(--surface-base);
}

/* --app-footer-height and --admin-header-height are shell tokens, defined in admin.css
   alongside the bars they measure. Used here so the page fits between them exactly. */

/* The page scrolls as one document. It used to own the viewport — a fixed height with
   two independently scrolling panes — which is what forced the work into a narrow rail
   and let a long note push the next action off screen. Full-bleed still, so the shell's
   padding doesn't inset the bands. */
.admin-content:has(> .case-page) {
    padding: 0;
    margin-bottom: 0;
}

.case-page {
    /* How much of the page the sticky box covers. Nothing inside it needs this — they are
       one element now — but the progress rail sticks below it and cannot ask a sticky
       ancestor how tall it is. Back bar plus hero; the lock notice adds its own row and is
       only sometimes there, hence the modifier below. */
    --case-sticky-height: 9.8rem;
    display: flex;
    flex-direction: column;
    font-family: var(--font-body);
    background: var(--surface-base);
}

.case-topbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding-right: 2rem;
}

.case-tools {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    padding-top: 1rem;
}

/* Tools, not actions — they belong with the back link, nowhere near the decision. */
/* Top-bar chrome, so smaller than the rail actions — but the same 8px box and
   border, not a pill. A single radius across the page is most of what made the
   buttons read as one set. */
.case-tool {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.3rem 0.7rem;
    background: transparent;
    border: 1px solid var(--border-default);
    border-radius: 8px;
    cursor: pointer;
    font-family: inherit;
    font-size: var(--text-xs);
    font-weight: var(--weight-medium);
    color: var(--text-muted);
}

.case-tool:hover:not([disabled]) {
    color: var(--text-primary);
    border-color: var(--border-strong);
}

.case-tool[disabled] { opacity: 0.55; cursor: default; }
.case-tool .rzi { font-size: 0.95rem; }

/* Back out to wherever you came from — quiet, but the first thing in the tab order. */
.case-back {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 1rem 2rem 0;
    color: var(--text-muted);
    font-size: var(--text-sm);
    font-weight: var(--weight-medium);
    text-decoration: none;
}

.case-back:hover { color: var(--liu-blue-dark); }
.case-back .rzi { font-size: var(--text-md); }

/* Progress rail beside the record, then the thread underneath at full width. The rail
   is fixed rather than fractional: it holds a stepper and one button, and there is no
   width at which it should grow. */
.case-upper {
    display: grid;
    grid-template-columns: 22rem minmax(0, 1fr);
    align-items: start;
}

/* No rail (clinic users): the record is the whole page. Capped and centred rather
   than run to the viewport edge, so the spec sheet keeps its columns instead of
   fanning out on a wide monitor. */
.case-upper--solo { grid-template-columns: minmax(0, 1fr); }

.case-upper--solo .case-record {
    width: 100%;
    max-width: 60rem;
    margin-inline: auto;
}

/* A container, so the record's own width decides its column count. The viewport can't:
   the record is the window minus a 22rem rail minus padding, so a viewport breakpoint is
   always guessing at the number that actually matters — and it would have to be re-derived
   every time the rail changed width. */
.case-record {
    min-width: 0;
    container-type: inline-size;
}

/* The rail changed sides, so its edge did too. align-self keeps the tinted ground
   running the full height of the band rather than stopping at its last child. */
/* Same ground as the record beside it. The rail used to be tinted to mark it as a
   different kind of thing, but the page now marks that boundary once and properly — the
   internal band below — and a second tint up here only made the top half look like two
   pages. The hairline is enough to say where one column ends. */
.case-rail {
    padding: 1.35rem 1.4rem 1.75rem;
    border-right: 1px solid var(--border-subtle);
    background: var(--surface-base);
    align-self: stretch;
}

/* The record is long and the rail is short. Following the scroll was one answer, but it
   left the rail's own height unused and made a second thing move while the header above
   was already moving. So: the rail fills the band instead — progress at the top, the
   assignment at the foot, and the slack between two anchored blocks rather than trailing
   under both of them. */
.case-rail-inner {
    display: flex;
    flex-direction: column;
    min-height: 100%;
}

/* A last block sits on the floor only when there is something above it to be the top.
   Plain :last-child caught the Progress block on an unassigned case, where it is also the
   first, and pushed the whole rail to the bottom of an empty column. */
.case-rail-inner > .rail-block + .rail-block:last-child { margin-top: auto; }

/* The stages spread into the room they have rather than bunching at the top — the
   connector between the dots becomes a real track down the page, which is what a
   six-stage journey should look like. Capped, because on a very long record an
   uncapped stretch would put four blank centimetres between "Approved" and "Received"
   and stop reading as a sequence at all. */
.case-rail-inner > .rail-block:first-child { display: flex; flex-direction: column; }


/* Tinted, and the tint is the Progress rail's — the two areas of this page that the
   submitting clinic never sees now share a ground, so "internal" is something you can
   see rather than something you have to be told. Everything above the line is what the
   clinic sent and can read back; everything on this ground is LIU's own working record.
   Thread left at its reading measure, the doctor's own material right — both are the
   internal side of the case, so they share the band. The panel is also what fills the
   right-hand space the band used to leave empty. */
.case-activity {
    /* White, like the record above it. Two tints were tried here — raised, then sunken —
       and both read as washed rather than deliberate, because the tint was a third signal
       for something already said twice: the strip says "internal use" in words, and the
       species watermark marks the half visually. What was left was grey that carried no
       information. The page is one surface now with a labelled divide across it, and the
       boundary comes from the strip and the rule, which are the parts that mean something. */
    background: var(--surface-base);
    /* Both for the watermark below: it is positioned against this box, and the band is
       what crops it. */
    position: relative;
    overflow: hidden;
    display: grid;
    /* Both columns absorb the slack instead of leaving it stranded between them. The
       thread was capped at 60rem and Material pinned at 22rem, so on a 1900px screen
       everything left over pooled in the middle — the one place it does no good.
       Material's upper bound matters most: these are machine-generated filenames and at
       22rem they truncate. */
    /* Proportional, not capped. Fixed widths meant the split landed wherever 72rem
       happened to fall — half the page on a wide monitor, with the slack pooling between
       the two panels. Two thirds / one third holds at any size, and the gap grows with the
       viewport instead of staying a fixed 5rem that looks tight at 1400px and mean at
       2400px. The floor on Documentation is what stops a file row from being crushed. */
    grid-template-columns: minmax(0, 2fr) minmax(20rem, 1fr);
    gap: 1rem;
    column-gap: clamp(1.5rem, 3.5vw, 6rem);
    align-items: start;
    border-top: 1px solid var(--border-default);
    padding: 1.4rem 2rem 2rem;
}

/* The column governs the measure now, not this. It was 72rem, which on a wide monitor cut
   the thread short of its own column and left the difference as dead space — the exact
   thing the two-thirds split is there to prevent. The cap survives only as a backstop for
   an ultrawide, where two thirds of 3000px really would be unreadable. */
.case-activity-inner {
    max-width: 96rem;
    min-width: 0;
}

/* Faintly sunken on a white band — a white field on a white ground has no edge but its
   border, and a border alone does not read as somewhere to type. */
.case-activity .composer-input { background: var(--surface-raised); }

/* ---- what this half is ----
   The tint says these two panels are a different kind of thing from the record above it;
   this says which kind. Full-bleed across both columns, because both are internal — a
   label that stopped at the thread would be describing less than it covers.
   "Internal" is the word a records or IT policy already uses for this tier, and the
   sentence carries the one fact that changes how someone writes: the clinic never reads
   any of it. */
.case-internal-bar {
    grid-column: 1 / -1;
    display: flex;
    align-items: center;
    gap: 0.55rem;
    margin: -1.4rem -2rem 0;
    padding: 0.55rem 2rem;
    background: var(--surface-sunken);
    border-bottom: 1px solid var(--border-default);
    font-size: var(--text-xs);
    color: var(--text-muted);
}

.case-internal-bar b {
    color: var(--text-secondary);
    font-weight: var(--weight-bold);
    text-transform: uppercase;
    letter-spacing: var(--tracking-uppercase);
    font-size: 0.66rem;
}

.case-internal-bar .rzi { font-size: 1rem; color: var(--text-subtle); flex: 0 0 auto; }

/* ---- the species watermark ----
   The treatment the confirmation page uses, on the other half of the same case: the mark
   set large, faint enough to read second, and cropped by the band on two sides — off the
   right edge and off the foot. Anchored to the bottom rather than the top: the thread has
   no height limit, so the top of the band is where the work is and the foot is the one
   place a large mark can sit without ever being behind something being read. */
.case-beast {
    position: absolute;
    left: 90%;
    bottom: -9rem;
    transform: translate(-50%, -50%);
    z-index: 0;
    font-size: 13rem;
    line-height: 1;
    /* Colour comes from .animal-ink--* on the element — a pet case is drawn in the pet's
       blue, a horse in amber. Slightly stronger than the confirmation page's 9%: this one
       sits on the band's grey rather than on white, so it has less contrast to spend. */
    opacity: 0.11;
    pointer-events: none;
    user-select: none;
}

/* Everything real stacks above it. :not() matters — reaching the watermark would turn its
   absolute position back into relative and it would never be drawn. */
.case-activity > *:not(.case-beast) {
    position: relative;
    z-index: 1;
}

@media (max-width: 900px) {
    .case-beast { font-size: 8rem; bottom: -5.5rem; }
}

.case-material { min-width: 0; }

.case-material-why {
    margin: -0.35rem 0 0.75rem;
    font-size: var(--text-xs);
    line-height: var(--leading-snug);
    color: var(--text-subtle);
}

/* Quiet until you go looking — the same rule as the edit pencil and the focus control.
   Removing a file is rare and looks irreversible; it shouldn't sit lit beside every
   filename. */
.attachment-remove {
    padding: 0.2rem;
    background: none;
    border: 0;
    border-radius: var(--r-input);
    cursor: pointer;
    color: var(--text-subtle);
    line-height: 1;
    opacity: 0;
    transition: opacity 0.15s ease, color 0.15s ease;
}

.attachment-row:hover .attachment-remove,
.attachment-remove:focus-visible { opacity: 1; }
.attachment-remove:hover { color: var(--status-danger); }
.attachment-remove .rzi { font-size: 0.9rem; }

/* ===== Spec sheet =====
   The record reads like a lab report: one continuous two-column list, every row
   labelled, group names sitting in the flow as quiet markers. The zebra stripe
   does the separating that forty hairline rules used to, for none of the ink. */
.case-record { padding: 1.25rem 2rem 2rem; }

/* The spec sheet is shared: the case page reads a filed submission back with it,
   and the payment page reviews one before it's filed. Only .case-record's padding is
   page-specific — everything below works anywhere the classes appear. */

/* A grid of section blocks rather than two long tables. */
/* Fixed, not auto-fit: auto-fit fanned six short sections into five ragged columns on
   a wide monitor and left a screen of dead space under them. Three is the count the
   record actually has content for. */
/* stretch, not start: every card in a row ends on the same line. The slack that used
   to fall BETWEEN the cards as a ragged gap now sits inside the shorter card, which
   only reads as deliberate because the card has an edge — hence the border below.
   Groups are uneven by nature (Clinical notes is 2 rows, Animal is 5), so this trades
   a gap you notice for whitespace you don't. */
.case-spec {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 1.25rem;
    /* stretch, not start: cards in a row share a height. Ragged bottoms were tried and
       lost — a row of cards that end on one line reads as a set. */
    align-items: stretch;
}

/* Three across a wide monitor, two on a laptop, one on a tablet. */
@container (max-width: 68rem) {
    .case-spec { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@container (max-width: 44rem) {
    .case-spec { grid-template-columns: minmax(0, 1fr); }
}


/* Column flex so the table keeps its natural height at the top of the card and the
   leftover height falls below it, rather than stretching the rows apart. */
.spec-group {
    position: relative;
    min-width: 0;
    display: flex;
    flex-direction: column;
    padding: 0.85rem 1rem 1rem;
    background: var(--surface-base);
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-card);
}

/* ===== Shared card height, and focus mode =====
   One height for every card so a row can't be dragged tall by its longest member. A
   card that doesn't fit clips and fades, and offers to take over the record area
   instead of growing in place — 200 words in a third-width column is a 200px measure
   and reads worse than the clipping did.
   The control only appears once app.js's specClamp marks a card as actually
   overflowing, so cards that fit carry no chrome at all.
   Scoped to .spec-record rather than .case-record: the payment review reads the same
   sheet back before filing and wants the same behaviour, so the scope is a class both
   pages put on the box that holds the sheet, not the case page's own record pane. */
.spec-record .spec-group {
    max-height: 16rem;
    overflow: hidden;
    transition: max-height 0.38s cubic-bezier(0.22, 0.61, 0.36, 1),
                opacity 0.22s ease, transform 0.28s cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* The fade is what tells you there's more before you read the control. Purely an
   overlay — this class is re-measured on every resize, so if it changed any box
   dimension the page would shift under you each time you zoomed. */
.spec-record .spec-group.is-clipped:not(.is-open)::after {
    content: "";
    position: absolute;
    inset: auto 1px 1px 1px;
    height: 3.25rem;
    border-radius: 0 0 var(--r-card) var(--r-card);
    background: linear-gradient(to bottom, rgba(255, 255, 255, 0), var(--surface-base) 70%);
    pointer-events: none;
}

/* ----- focus mode ----- */
/* Everything the focused card isn't: the other sections and the attachments block.
   Collapsed to nothing rather than display:none so the record area shrinks around the
   survivor instead of snapping. */
.spec-record.is-focused > .case-spec > .spec-group:not(.is-open),
.spec-record.is-focused > .spec-block {
    opacity: 0;
    transform: scale(0.98);
    max-height: 0;
    margin: 0;
    padding-block: 0;
    border-width: 0;
    pointer-events: none;
    overflow: hidden;
}

/* One column, so the survivor has the whole width without needing a span. */
.spec-record.is-focused > .case-spec {
    grid-template-columns: minmax(0, 1fr);
    gap: 0;
}

.spec-record .spec-group.is-open {
    max-height: 150rem;
}

/* Full width is for the card, not the line. */
.spec-record .spec-group.is-open .spec { max-width: 70rem; }

/* The four-line cell clamp is what a closed card needs; a focused card is the reader
   asking for all of it, so its notes release. */
.spec-record .spec-group.is-open .spec-note.is-clamped {
    display: block;
    overflow: visible;
}

/* SpecNote's own per-note "Show more" is redundant here — focus mode reads the whole
   section, and two expand affordances on the same paragraph is what made this messy.
   Scoped rather than removed from the component: SpecNote is still correct on its own
   anywhere the sheet is rendered without a focus scope around it. */
.spec-record .spec-more { display: none; }

/* ----- the control ----- */
/* Revealed on approach, the same way the edit pencil beside it is — quiet until you
   go looking. Rendered always rather than gated on a JS overflow measurement: when
   that measurement didn't fire, the control wasn't in the DOM at all. */
.spec-focus {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    margin-left: auto;
    padding: 0;
    background: none;
    border: 0;
    cursor: pointer;
    color: var(--text-subtle);
    line-height: 1;
    opacity: 0;
    transition: opacity 0.15s ease, color 0.15s ease;
}

/* Focus-visible so a keyboard can reach it, and always solid while it's the way back
   out of focus mode. */
.spec-group:hover > .spec-cap > .spec-focus,
.spec-focus:focus-visible,
.spec-record .spec-group.is-open > .spec-cap > .spec-focus { opacity: 1; }

.spec-focus:hover { color: var(--liu-blue-dark); }

.spec-focus .rzi { font-size: 0.95rem; }

/* Nothing to hover with, so nothing would ever reveal it — and it is the only way to
   read a clamped note on a phone. */
@media (hover: none) {
    .spec-focus { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
    .spec-record .spec-group,
    .spec-record.is-focused > .case-spec > .spec-group:not(.is-open),
    .spec-record.is-focused > .spec-block {
        transition: none;
    }
}

/* The section heading. Was a table row, which is why it consumed a stripe slot and
   couldn't align across columns — it's a real heading now. */
.spec-cap {
    /* Flex so the edit affordance sits on the heading's baseline instead of relying on
       vertical-align, and so the pencil can't push the label off its left edge. */
    display: flex;
    align-items: center;
    margin: 0 0 0.5rem;
    font-size: 0.62rem;
    font-weight: var(--weight-bold);
    text-transform: uppercase;
    letter-spacing: var(--tracking-uppercase);
    color: var(--liu-blue-dark);
}

/* ===== Long free text =====
   Clamped to four lines, with the remainder one click away. */
/* pre-line keeps the clinic's paragraph breaks. overflow-wrap:anywhere is what makes
   the clamp actually work: a run with no spaces in it offers no break opportunity, so
   it stayed one enormous line and overflow:hidden simply sliced it off mid-word — the
   line-clamp had nothing to count. */
.spec-note {
    white-space: pre-line;
    overflow-wrap: anywhere;
}

.spec-note.is-clamped {
    display: -webkit-box;
    -webkit-line-clamp: 4;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.spec-more {
    margin-top: 0.2rem;
    padding: 0;
    background: none;
    border: 0;
    cursor: pointer;
    font-family: inherit;
    font-size: 0.7rem;
    font-weight: var(--weight-semi);
    color: var(--liu-blue-dark);
}

.spec-more:hover { text-decoration: underline; }

.spec {
    width: 100%;
    /* Fixed layout so a value can never widen its column. Under auto layout a token with
       no break opportunity — a transaction hash, an order ID — sets the column's
       min-content width, which pushes the table past the pane and clips it. Fixed pins
       the key column to its declared 8.5rem and gives the rest to the value, which then
       has to wrap rather than escape. */
    table-layout: fixed;
    border-collapse: collapse;
    font-size: 0.82rem;
    color: var(--text-secondary);
}

.spec td {
    padding: 0.35rem 0.65rem;
    border: none;
    vertical-align: top;
    line-height: 1.45;
    /* anywhere, not break-word: break-word leaves min-content sizing alone, which is
       exactly the thing that let an unbreakable ID widen the column. With table-layout
       fixed above, the column width is already settled, so this only ever engages on a
       token that genuinely cannot fit — dates and short IDs are untouched. */
    overflow-wrap: anywhere;
}

/* Striped per group, not per table. Every group's rows live in their own tbody and
   the caption sits in one of its own, so counting restarts at each heading. It used
   to be tr:nth-child(odd) over the whole table — the caption rows consumed a slot,
   so a group with an odd row count flipped the phase of the next one and Clinic
   started white while People started grey. */
/* Each section is its own table now, so the stripe restarts per section naturally —
   no caption row in the count to flip the phase of the next group. */
.spec tbody tr:nth-of-type(odd) td { background: var(--surface-raised); }

/* And for rows with no tbody around them. The record's own sections write <tbody> by hand;
   Record details builds its rows from the Row() fragment, which emits a bare <tr>. Blazor
   appends those straight to the <table> — it constructs the DOM rather than parsing HTML,
   so the implied <tbody> the parser would have inserted never appears, and a tbody-scoped
   stripe matched nothing. That is why one card in the record had no stripes at all. */
.spec > tr:nth-of-type(odd) td { background: var(--surface-raised); }

.spec .spec-k {
    width: 8.5rem;
    padding-top: 0.45rem;
    font-size: 0.6rem;
    font-weight: var(--weight-bold);
    text-transform: uppercase;
    letter-spacing: var(--tracking-uppercase);
    color: var(--text-subtle);
    white-space: nowrap;
}


/* An attribute of the value beside it, not a status of its own. */
.spec-tag {
    display: inline-block;
    margin-left: 0.35rem;
    padding: 0.05rem 0.4rem;
    border-radius: 999px;
    background: var(--surface-sunken);
    font-size: 0.6rem;
    font-weight: var(--weight-semi);
    text-transform: uppercase;
    letter-spacing: var(--tracking-uppercase);
    color: var(--text-muted);
}

.spec-tag--ok { background: var(--status-success-bg); color: var(--status-success-text); }
.spec-tag--warn { background: var(--status-warning-bg); color: var(--status-warning-text); }

/* A person and their role are one value, not two. Inline-flex with a real gap keeps
   the tag beside the name at any width the column allows, and wraps them together
   rather than leaving the tag stranded on its own line. */
.spec-person-row {
    display: inline-flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.3rem;
}

/* The name half — a proper noun, so ink rather than a pill. The old margin-right is
   gone; the flex gap owns the spacing now. */
.spec-person { font-weight: var(--weight-semi); color: var(--text-primary); }

/* A secondary line under a value — a tracking number under its transport method.
   Its own line because a long unbroken digit run butted against the method read as
   one word and pushed the column wider than the pane. */
.spec-sub {
    display: block;
    margin-top: 0.2rem;
    overflow-wrap: anywhere;
}

.spec-sub-k {
    margin-right: 0.35rem;
    font-size: 0.6rem;
    font-weight: var(--weight-bold);
    text-transform: uppercase;
    letter-spacing: var(--tracking-uppercase);
    color: var(--text-subtle);
}

/* Words the clinic typed, marked as theirs. */
.spec-quote {
    display: block;
    margin-top: 0.15rem;
    color: var(--text-muted);
    font-style: italic;
}

.spec-block { margin-top: 1.5rem; }

.spec-gname {
    margin-bottom: 0.5rem;
    padding-left: 0.65rem;
    font-size: 0.62rem;
    font-weight: var(--weight-bold);
    text-transform: uppercase;
    letter-spacing: var(--tracking-uppercase);
    color: var(--liu-blue-dark);
}

/* Framed with .case-material .empty further down — one look for both file panels. */

/* On the submission form the sheet sits inside a card that already has padding, so the
   columns get less room than the case page's record pane.
   The floor was 17rem, which fits three cards across — and 17rem minus the 8.5rem label
   column leaves under ten characters for the value, so every email address broke in half
   ("billing@example.c om") and the species wrapped mid-phrase. 25rem fits two, and two
   cards that can be read beat three that cannot. */
.review-spec {
    margin-top: 0.5rem;
    grid-template-columns: repeat(auto-fit, minmax(25rem, 1fr));
}

/* And the labels give some of that width back. They are short here — the longest is
   "Additional info" — where the case page's column is sized for its own longest run. */
.review-spec .spec .spec-k { width: 7rem; }

/* A consequence of the entry above it, not an event of its own. The margin is
   load-bearing: Razor trims the whitespace after a code block or comment, and
   this span follows both, so it would otherwise butt against the pill. */
.tl-sub {
    margin-left: 0.25rem;
    color: var(--text-muted);
}

/* Used by the timeline and the not-found state, and never actually defined
   anywhere until now — it was rendering as unstyled body text. */
.empty {
    padding: 1.25rem 0.65rem;
    font-size: var(--text-sm);
    line-height: 1.5;
    color: var(--text-muted);
}


/* Reference material, not the case: one line by default, open it if you need it. */
/* Card, not disclosure — .spec-group carries the frame, this only has to stop the old
   loose-block spacing from applying. */
/* Only the old loose-block spacing is reset. border-top was in here too and it was
   removing one side of the card's own frame — a card with three borders. */


/* Motion is decoration here — anyone who's asked for less gets none of it. */
@media (prefers-reduced-motion: reduce) {
    .stage-action,
    .status-override-panel { animation: none; }

    .step.is-now .step-dot {
        animation: none;
        box-shadow: 0 0 0 3px var(--liu-blue-light);
    }

    .step-dot,
    .step:not(:last-child)::before,
    .decision-btn,
    .step-next { transition: none; }

    /* Keep the fade-out — it's how the confirmation leaves — but drop the pop. */
    .save-flash { animation: save-flash-out 0.4s ease-in 2.1s both; }

    .decision-btn:hover:not([disabled]),
    .step-next:hover:not([disabled]) { transform: none; }
}

/* ===== Print =====
   Paper has no chrome, no scroll panes and nothing to click. What's left is the
   record and the facts around it — which is exactly what someone printing a case
   wants to keep. Whatever the role can't see on screen isn't rendered, so the
   printout is role-correct for free. */
@media print {
    .admin-header,
    .admin-sidebar,
    .admin-sidebar-toggle,
    .rz-footer,
    .case-tools,

    .admin-content:has(> .case-page) { padding: 0; }

    .case-page {
        height: auto;
        overflow: visible;
    }

    .case-upper {
        display: block;
    }

    .case-record,
    .case-rail {
        overflow: visible;
        height: auto;
        border-right: none;
        background: none;
        padding: 0.5rem 0;
    }

    /* Controls are meaningless on paper; the facts they set are not.
       .step-next and the status toggle were missing here, so the stage action and
       "Change status" printed as live-looking buttons on the paper copy. */
    .decision-actions,
    .decision-hold,
    .decision-change,
    .step-next,
    .rail-action,
    .status-override-toggle,
    .comment-composer,
    .rail-field,
    .rail-info,
    .rail-flag,
    .rail-block .rz-button,
    .case-back { display: none !important; }

    .case-spec { gap: 0 1.5rem; }
    .spec tr:nth-child(odd) td { background: #f6f7f9 !important; -webkit-print-color-adjust: exact; print-color-adjust: exact; }

    /* Don't split a row or a comment across a page break. */
    .spec tr,
    .tl-group,
    .tl-act { break-inside: avoid; }
}

/* Three columns of record need room. Step down before they crush, not after. */
@media (max-width: 1400px) {
    .case-spec { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Too narrow to sit side by side. The rail already leads the DOM, so stacking keeps
   the decision first — no column-reverse needed any more. */
@media (max-width: 1100px) {
    .case-upper { grid-template-columns: minmax(0, 1fr); }

    .case-rail {
        border-right: none;
        border-bottom: 1px solid var(--border-subtle);
        padding: 1.5rem 2rem;
    }

    .case-spec { grid-template-columns: minmax(0, 1fr); }

    /* The files panel drops under the thread rather than squeezing beside it. */
    .case-activity { grid-template-columns: minmax(0, 1fr); }
}

.rail-block + .rail-block {
    margin-top: 1.5rem;
    padding-top: 1.5rem;
    border-top: 1px solid var(--border-subtle);
}


/* .spec's padding assumes a card edge either side of it; inside this one the tables sit
   flush, so the first column's labels line up with the cap above them. */

.rail-cap {
    display: flex;
    align-items: center;
    gap: 0.3rem;
    margin: 0 0 0.75rem;
    font-size: 0.66rem;
    font-weight: var(--weight-bold);
    text-transform: uppercase;
    letter-spacing: var(--tracking-uppercase);
    color: var(--text-subtle);
}

.rail-field { margin-top: 1rem; }

.rail-field label {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    margin-bottom: 0.3rem;
    font-size: 0.68rem;
    font-weight: var(--weight-bold);
    text-transform: uppercase;
    letter-spacing: var(--tracking-uppercase);
    color: var(--text-subtle);
}

.rail-meta {
    display: flex;
    align-items: center;
    gap: 0.3rem;
    margin-top: 0.75rem;
    font-size: var(--text-xs);
    color: var(--text-subtle);
}

.rail-assignee {
    font-size: var(--text-sm);
    font-weight: var(--weight-semi);
    color: var(--text-primary);
}

.rail-assignee--none {
    font-weight: var(--weight-regular);
    font-style: italic;
    color: var(--text-subtle);
}

/* ===== Flagged assignment =====
   A stale assignee is an exception, not an emergency: it earns a mark on the
   fact that's wrong, not a banner across the rail. The sentence is one click
   away for whoever wants it. */
.rail-meta--flagged { color: #8a6a12; }

.rail-flag {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    width: 1.15rem;
    height: 1.15rem;
    background: transparent;
    border: none;
    border-radius: 50%;
    cursor: pointer;
    color: #b8860b;
    line-height: 1;
}

.rail-flag:hover,
.rail-flag[aria-expanded="true"] {
    background: #fdf3d8;
    color: #6b4e0e;
}

.rail-flag .rzi { font-size: 0.95rem; }

.rail-why {
    margin: 0.5rem 0 0;
    font-size: var(--text-xs);
    line-height: var(--leading-relaxed, 1.55);
    color: #6b4e0e;
}

/* Same disclosure, no alarm attached — an explanation, not an exception. */
.rail-why--neutral {
    margin: 0 0 0.85rem;
    color: var(--text-muted);
}

.rail-info {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    width: 1.05rem;
    height: 1.05rem;
    background: transparent;
    border: none;
    border-radius: 50%;
    cursor: pointer;
    color: var(--text-subtle);
    line-height: 1;
}

.rail-info:hover,
.rail-info[aria-expanded="true"] {
    background: var(--surface-sunken);
    color: var(--text-primary);
}

.rail-info .rzi { font-size: 0.85rem; }

.rail-block .rz-button { margin-top: 0.75rem; }

/* The heading owns its own row: title left, escape hatch right. */
.progress-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
}

.progress-head .rail-cap { margin-bottom: 0; }

/* Stacked, not side by side: 17rem has no room for two columns. The journey reads
   down, then the thing to do about it sits under it. */
.progress-body {
    display: block;
    margin-top: 0.9rem;
}

.stage-action {
    margin-top: 1rem;
    animation: stage-in 0.28s ease-out both;
}

@keyframes stage-in {
    from { opacity: 0; transform: translateY(4px); }
    to   { opacity: 1; transform: none; }
}

/* Assignee left, the action on it right. */
/* Stacked, not side by side. This used to sit in a 26–34rem rail where a name, a role
   pill, a flag and a date fitted on one row beside a button; at 20rem they don't, and
   the date was breaking across two lines into the button. The action gets its own row
   and the facts get the full width. */
.assignee-row {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0.75rem;
}

/* The assignee is a person, so they are drawn like one — the same initials disc the timeline
   gutter and the dashboard's caseload list use, name on the first line, role and date on a
   quiet second. Three facts stacked flush left with nothing to hang from read as a pile;
   the disc gives the block a left edge, which is most of what was wrong with it. The card
   is what lets the two text lines sit close together without touching the rule above. */
.assignee-who {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    grid-template-rows: auto auto;
    column-gap: 0.7rem;
    row-gap: 0.15rem;
    align-items: center;
    min-width: 0;
    padding: 0.85rem 0.9rem;
    border: 1px solid var(--border-subtle);
    border-radius: 12px;
    background: var(--surface-raised);
}

/* Spans both rows: the name and the meta line are what it sits beside. */
.assignee-avatar { grid-row: 1 / span 2; width: 2.4rem; height: 2.4rem; font-size: 0.8rem; }

.assignee-who .rail-assignee { font-size: var(--text-base); }
.assignee-who .rail-meta { margin-top: 0; gap: 0.4rem; }

/* The role as a word, not a second tinted chip. The avatar beside it already carries the
   role's colour, and a pill here made the block say "Doctor" twice in two shapes. */
.assignee-role { font-weight: var(--weight-semi); color: var(--text-secondary); }
.assignee-when::before { content: "· "; }

/* Nobody yet, and the flag's explanation, both want the full width — there is no avatar
   in the first case and the sentence is a paragraph in the second. */
.assignee-who .rail-assignee--none,
.assignee-who .rail-why { grid-column: 1 / -1; }

.assignee-row .rz-button,
.assignee-row .rail-action {
    align-self: flex-start;
    margin-top: 0;
}

/* The date is the wrapping culprit: a role pill, a flag and "· Aug 4, 2026" only fit
   on one line for short roles. Let it drop cleanly instead of splitting the year off. */
.rail-meta { flex-wrap: wrap; }
.rail-meta > time,
.rail-meta > span:last-child { white-space: nowrap; }

/* Not a role and not a badge — just the word that saves you comparing the name
   against your own. Quiet enough to sit inside the name line. */
.rail-assignee-you {
    margin-left: 0.35rem;
    padding: 0.05rem 0.35rem;
    border-radius: 4px;
    background: var(--liu-blue-light);
    font-size: 0.62rem;
    font-weight: var(--weight-semi);
    text-transform: uppercase;
    letter-spacing: var(--tracking-uppercase);
    color: #1f5f8b;
    vertical-align: middle;
}

/* ===== Stepper =====
   The whole journey, always visible. Stages nobody has reached read as pending
   rather than hiding in a dropdown, which is what makes "one action at a time"
   feel like progress instead of a missing feature. */
/* No bottom margin on either column — they're siblings in a stretched grid, so
   any trailing space on one throws the other's bottom edge out of line. */
.stepper {
    list-style: none;
    margin: 0;
    padding: 0;
    /* Stretches into the rail's spare height rather than bunching at the top, so the
       connector between the dots reads as a track down the page. Capped: uncapped, a long
       record would put four blank centimetres between "Approved" and "Received" and stop
       reading as a sequence at all. */
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    max-height: 24rem;
}

.step {
    position: relative;
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.28rem 0;
    font-size: var(--text-xs);
    color: var(--text-subtle);
}

/* The line joins the dots, and stops before the last one. */
.step:not(:last-child)::before {
    content: "";
    position: absolute;
    left: 0.3rem;
    top: 1.1rem;
    /* Reaches the next dot rather than stopping where this step's text ends — the rows
       are taller now that the stepper stretches. */
    bottom: -0.6rem;
    width: 1px;
    background: var(--border-default);
}

.step-dot {
    flex: 0 0 auto;
    margin-top: 0.15rem;
    width: 0.65rem;
    height: 0.65rem;
    border-radius: 50%;
    border: 1px solid var(--border-strong);
    background: var(--surface-base);
    z-index: 1;
    /* So advancing a stage reads as the line filling, not a repaint. */
    transition: background 0.35s ease, border-color 0.35s ease, box-shadow 0.35s ease;
}

.step:not(:last-child)::before { transition: background 0.35s ease; }

.step-text { display: flex; flex-direction: column; }

.step-when {
    margin-top: 0.05rem;
    font-size: 0.62rem;
    font-weight: var(--weight-regular);
    color: var(--text-subtle);
}

.step.is-done { color: var(--text-muted); }
.step.is-done .step-dot { background: var(--border-strong); border-color: var(--border-strong); }
.step.is-done::before { background: var(--border-strong); }

.step.is-now {
    color: var(--text-primary);
    font-weight: var(--weight-semi);
}

/* The halo breathes so "where we are" is findable without hunting for bold. */
.step.is-now .step-dot {
    background: var(--liu-blue-dark);
    border-color: var(--liu-blue-dark);
    animation: step-pulse 2.6s ease-in-out infinite;
}

@keyframes step-pulse {
    0%, 100% { box-shadow: 0 0 0 3px var(--liu-blue-light); }
    50%      { box-shadow: 0 0 0 5px rgba(105, 179, 231, 0.18); }
}

/* The stage the case was standing on when it left the line. Same dot as any other, in
   the colour of the exit, and the connector below it stops — there is nothing after. */
.step.is-stopped {
    color: var(--status-danger);
    font-weight: var(--weight-semi);
}

.step.is-stopped .step-dot {
    background: color-mix(in srgb, var(--status-danger) 62%, #fff);
    border-color: color-mix(in srgb, var(--status-danger) 62%, #fff);
}

.step.is-stopped::before { background: var(--border-strong); }

/* Stages the case never reached because it left before them. */
.step.is-stopped ~ .step { opacity: 0.55; }
.step.is-stopped ~ .step::before,
.step.is-stopped:not(:last-child)::before {
    background: repeating-linear-gradient(
        to bottom,
        var(--border-default) 0 3px,
        transparent 3px 6px
    );
}

/* The one action this stage is waiting on. Neutral — the coloured pair is
   reserved for the decision. */
/* Solid. It used to be the same tinted blue as the NOW card directly above it, so the
   rail opened with two blue panels and the one thing to do on the page did not separate
   from the thing telling you where the page is. Filled against tinted is the only pair
   here that reads as state-then-action. */
.step-next {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    width: 100%;
    margin-top: 0.7rem;
    padding: 0.62rem;
    background: var(--liu-blue-dark);
    border: 1px solid var(--liu-blue-dark);
    border-radius: 8px;
    cursor: pointer;
    font-family: inherit;
    font-size: var(--text-sm);
    font-weight: var(--weight-bold);
    color: #fff;
}

.step-next { transition: filter 0.12s ease, transform 0.12s ease; }
.step-next:hover:not([disabled]) { filter: brightness(0.97); transform: translateY(-1px); }
.step-next:active:not([disabled]) { transform: none; }
.step-next[disabled] { opacity: 0.55; cursor: default; }
.step-next .rzi { font-size: 1.05rem; }

/* A terminal or waiting stage has no button, but it still occupies the action
   slot — so it takes the same box the buttons do rather than reading as a stray
   sentence floating in the column. */
/* Not a warning — a statement of fact about who holds the case, sitting where the
   decision buttons would be. */
.held-by {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    margin-bottom: 0.6rem;
    padding: 0.55rem 0.7rem;
    background: var(--surface-raised);
    border: 1px solid var(--border-default);
    border-radius: 8px;
    font-size: var(--text-xs);
    color: var(--text-muted);
}

.held-by .rzi { font-size: var(--text-md); }
.held-by strong { color: var(--text-primary); }

.rail-waiting {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    margin: 0;
    padding: 0.6rem;
    border: 1px dashed var(--border-default);
    border-radius: 8px;
    background: var(--surface-raised);
    font-size: var(--text-xs);
    line-height: 1.4;
    text-align: center;
    color: var(--text-muted);
}

/* ===== On hold =====
   A pause on a stage, not a stage — so it's a strip over the current step rather
   than a step of its own. */
.hold-strip {
    display: flex;
    align-items: flex-start;
    gap: 0.5rem;
    margin-bottom: 0.6rem;
    padding: 0.6rem 0.7rem;
    background: #fdf3d8;
    border: 1px solid #ecd9a3;
    border-radius: 8px;
    color: #6b4e0e;
}

.hold-strip .rzi { font-size: 1.1rem; }
.hold-strip-title { font-size: var(--text-sm); font-weight: var(--weight-semi); }
.hold-strip-why { margin-top: 0.15rem; font-size: var(--text-xs); font-style: italic; }

/* ===== Status override =====
   The full list, demoted to an escape hatch: going backwards, skipping a step,
   fixing a mistake. Not a peer of the action above it. */
.status-override {
    position: relative;
    display: flex;
    align-items: center;
    gap: 0.4rem;
    flex: 0 0 auto;
}

/* Quiet on purpose — it's the escape hatch, not the action. The label stays
   because a sliders icon alone doesn't tell you it can move a case backwards. */
.status-override-toggle {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.2rem 0.35rem;
    margin-right: -0.35rem;
    background: transparent;
    border: none;
    border-radius: 6px;
    cursor: pointer;
    font-family: inherit;
    font-size: 0.62rem;
    font-weight: var(--weight-semi);
    text-transform: uppercase;
    letter-spacing: var(--tracking-uppercase);
    color: var(--text-subtle);
}

.status-override-toggle:hover,
.status-override-toggle[aria-expanded="true"] {
    background: var(--surface-sunken);
    color: var(--text-primary);
}

.status-override-toggle .rzi { font-size: 0.9rem; }

/* Catches the click that dismisses the panel without a document listener. */
.status-override-backdrop {
    position: fixed;
    inset: 0;
    z-index: 19;
}

/* Floats over the panel rather than shoving the stepper down when opened. */
.status-override-panel {
    position: absolute;
    top: calc(100% + 0.4rem);
    right: 0;
    z-index: 20;
    min-width: 13rem;
    padding: 0.5rem;
    background: var(--surface-base);
    border: 1px solid var(--border-default);
    border-radius: 8px;
    box-shadow: 0 10px 28px rgba(16, 24, 40, 0.14);
    animation: stage-in 0.16s ease-out both;
}

/* ===== Decision =====
   The two outcomes get equal weight and real size. This is the one place on the
   page meant to be clicked, so it doesn't share the button language used for
   secondary actions elsewhere. */
/* auto-fit rather than 1fr 1fr: the pair now lives in a 17rem rail, and below the
   width where both fit on one row they stack instead of squeezing the labels. */
.decision-actions {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(6.5rem, 1fr));
    gap: 0.5rem;
}

.decision-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    padding: 0.7rem 0.5rem;
    border: 1px solid transparent;
    border-radius: 8px;
    cursor: pointer;
    font-family: inherit;
    font-size: var(--text-sm);
    font-weight: var(--weight-semi);
    transition: filter 0.12s ease;
}

.decision-btn .rzi { font-size: 1.15rem; }
.decision-btn { transition: filter 0.12s ease, transform 0.12s ease; }
.decision-btn:hover:not([disabled]) { filter: brightness(0.95); transform: translateY(-1px); }
.decision-btn:active:not([disabled]) { transform: none; }
.decision-btn[disabled] { opacity: 0.55; cursor: default; }

/* The decision pair wears the Approved and Denied PILLS: same tint, same darker ink,
   same edge — so the button that produces a status is the colour that status will be,
   and the pair reads as two answers rather than a primary and its opt-out.
   Read from the :root status tokens rather than from .tone-ok / .tone-bad on the button.
   Those set --tone-bg/-ink/-edge, so a stylesheet that referenced them while the running
   build still had the old markup resolved every var() to nothing and rendered the pair
   as two blank outlined boxes. The tokens below exist whatever the markup says. */
/* The ink is mixed a little toward white, and the border is that same ink softened
   further so the two read as one weight. Identical values did NOT look identical: a 1px
   line is solid colour edge to edge while text is mostly antialiased pixels, so the
   border came out visibly heavier than the label it was supposed to match.
   Declared as a local property on each button: one hue, used twice, and defined on the
   element itself — nothing here depends on a class the markup has to supply. */
.decision-btn--approve {
    --decide-ink: color-mix(in srgb, var(--status-success) 86%, #fff);
    background: var(--status-success-bg);
    border-color: color-mix(in srgb, var(--decide-ink) 55%, #fff);
    color: var(--decide-ink);
    font-weight: var(--weight-semi);
}

.decision-btn--deny {
    --decide-ink: color-mix(in srgb, var(--status-danger) 86%, #fff);
    background: var(--status-danger-bg);
    border-color: color-mix(in srgb, var(--decide-ink) 55%, #fff);
    color: var(--decide-ink);
    font-weight: var(--weight-semi);
}

/* The neutral member of the action family. Everything on this page that isn't the
   one thing the stage is waiting on wears this: same 8px box, same border, same
   text size as the Progress buttons, no fill. .rail-action is the inline variant
   for actions that sit beside content rather than under it. */
.decision-hold,
.decision-change,
.rail-action {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.35rem;
    width: 100%;
    margin-top: 0.5rem;
    padding: 0.5rem;
    background: transparent;
    border: 1px solid var(--border-default);
    border-radius: 8px;
    cursor: pointer;
    font-family: inherit;
    font-size: var(--text-sm);
    color: var(--text-muted);
}

.decision-hold:hover,
.decision-change:hover,
.rail-action:hover:not([disabled]) {
    color: var(--text-primary);
    border-color: var(--border-strong);
}

/* Sits in a row next to the assignee, so it takes its own width instead of the
   column's and loses the stacked-button top margin. */
.rail-action {
    width: auto;
    margin-top: 0;
    padding: 0.45rem 0.8rem;
    white-space: nowrap;
}

.rail-action[disabled] { opacity: 0.55; cursor: default; }
.rail-action .rzi { font-size: var(--text-md); }

.decision-hold .rzi { font-size: var(--text-md); }

.decision-meta {
    font-size: var(--text-xs);
    color: var(--text-subtle);
}

/* ===== Hero =====
   Reading order is species, number, name. The animal is drawn at the far left and the
   case number leads the text, because the number is what every phone call, courier label
   and email about this case uses — the name is how LIU talks about it internally.
   Left padding rather than a flex child for the silhouette: the mark bleeds and is
   positioned, so it can't take part in the row's sizing without dragging the rest of the
   header around with it. */
.modal-hero {
    /* Kept on screen by .case-sticky above, not by a rule of its own. */
    position: relative;
    overflow: hidden;
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 1rem 1.5rem;
    padding: 1.15rem 2rem 1.15rem 7.75rem;
    background: var(--surface-base);
    border-bottom: 1px solid var(--border-default);
}

/* The species, in a tinted tile of its own colour. Loose on the header the glyph had
   nothing holding it — a paw floating beside a rule, at a size that made it the loudest
   thing on a band whose job is the case number.
   Square and sized to the identity block beside it, so the tile spans name + clinic + vet
   rather than sitting against the first line of them. The box is fixed, so a wide paw, a
   tall knight and a wide tractor all leave the number in the same place instead of each
   setting the header's left indent themselves. */
.hero-beast {
    position: absolute;
    left: 1.75rem;
    top: 50%;
    transform: translateY(-50%);
    display: flex;
    align-items: center;
    justify-content: center;
    width: 4.75rem;
    height: 4.75rem;
    border-radius: 1.15rem;
    font-size: 2.4rem;
    line-height: 1;
    pointer-events: none;
    user-select: none;
}

/* Only the ground. The ink comes from .animal-ink--* in app.css, which the watermarks on
   this page and the confirmation page take too — one place decides what colour a horse is. */
.hero-beast.animal-ink--pet   { background: #EAF5FD; }
.hero-beast.animal-ink--horse { background: #FFF6D1; }
.hero-beast.animal-ink--farm  { background: #F1E8F4; }
.hero-beast.animal-ink--other { background: var(--border-subtle); }

/* Number first, then a hairline, then the name. The rule is what stops the two reading
   as one phrase — without it "Case 59 Buddy" runs together. */
.hero-plate {
    flex: 0 0 auto;
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    padding-right: 1.5rem;
    border-right: 1px solid var(--border-default);
}

.hero-plate-cap {
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: var(--tracking-uppercase);
    color: var(--liu-blue-dark);
    font-weight: var(--weight-bold);
    opacity: 0.75;
}

.hero-identity {
    display: flex;
    align-items: center;
    gap: 0.9rem;
    min-width: 0;
    flex: 1 1 260px;
}

.hero-title {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

/* Name, number and state on one line — the three things you need to know before
   anything else on the page. */
.hero-line {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    flex-wrap: wrap;
}

.hero-line h2 {
    margin: 0;
    font-size: var(--text-2xl);
    font-weight: var(--weight-bold);
    color: var(--text-primary);
    line-height: var(--leading-tight);
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The confirmation page's numeral, at the confirmation page's scale. It was a small chip
   on a blue tint, which is the treatment a label gets, not a headline. Tabular figures so
   it doesn't re-space itself between one case and the next. */
.hero-id {
    font-size: 2rem;
    line-height: 1;
    font-weight: var(--weight-bold);
    color: var(--liu-blue-dark);
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.02em;
}

/* Notation, not a digit — the same rule the confirmation page's # follows. At full size
   it reads as the first character of the number. */
.hero-id-hash {
    font-size: 0.62em;
    font-weight: var(--weight-medium);
    vertical-align: 0.12em;
    opacity: 0.4;
    margin-right: 0.02em;
}

/* Always stacked, never inline-wrapped. Inline, a long clinic name pushed the
   vet onto a second line anyway — so the layout changed shape record to record.
   Three fixed lines (animal / clinic / vet) look the same for every case. */
.hero-sub {
    margin-top: 0.2rem;
    color: var(--text-muted);
    font-size: var(--text-md);
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.1rem;
}

.hero-sub-clinic {
    color: var(--text-secondary);
    font-weight: var(--weight-medium);
}

/* Its own line, so no separator is needed at all. */
.hero-sub-vet {
    font-size: var(--text-sm);
    color: var(--text-subtle);
}

/* Two cells, not four. The status left with the name, so what's left here is genuinely
   secondary — it earns the right edge without competing for it. */
/* Centred, not top-aligned: the confirmation chip carries padding the plain stats
   don't, so flex-start left them hanging off its top edge. */
/* margin-left, not justify-content: space-between on the row — the header has four
   children now, and space-between would spread the gaps between the mark, the number and
   the name instead of only pushing these two to the far edge. */
/* A hairline and a gutter. They stay right-aligned — that is where a reader looks for
   dates and money — but on a wide monitor they were two labels adrift in white space with
   nothing to belong to. The rule gives them an edge to sit against. */
.hero-meta {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.75rem 1.75rem;
    flex: 0 1 auto;
    margin-left: auto;
    padding-left: 1.75rem;
    border-left: 1px solid var(--border-default);
}


.hero-stat {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    min-width: 0;
}

.hero-stat-label {
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: var(--tracking-uppercase);
    color: var(--text-subtle);
    font-weight: var(--weight-bold);
}

.hero-stat-value {
    font-size: var(--text-md);
    font-weight: var(--weight-semi);
    color: var(--text-primary);
}

/* ===== Body: tabs + content ===== */
/* ===== Tab panels =====
   Each group is one tab panel filling the content area — no card frame, no
   accent stripe, no backdrop tint. One flat white surface across every tab, so
   elevation carries meaning instead of decoration:
     tinted fill  = an input you can type in
     hairline box = a record you can read
   Nothing else gets a surface of its own. */
.modal-group {
    padding: 1.75rem 2rem;
    background: var(--surface-base);
}

.modal-group-header {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-bottom: 0.85rem;
    padding-bottom: 0.65rem;
    border-bottom: 1px solid var(--border-subtle);
}

.modal-group-header .rzi {
    font-size: var(--text-md);
}

.modal-group-title {
    font-size: 0.7rem;
    font-weight: var(--weight-bold);
    color: var(--text-primary);
    text-transform: uppercase;
    letter-spacing: var(--tracking-uppercase);
}


/* Request record — quiet neutral surface, lock icon. */

.modal-group--readonly .modal-group-header .rzi {
    color: var(--text-muted);
}

/* Sections inside a group: no separate card chrome. Just header + grid. */
.modal-group .modal-section {
    background: transparent;
    border-left: none;
    border-radius: 0;
    padding: 0;
    margin-bottom: 1.25rem;
}

.modal-group .modal-section:last-child {
    margin-bottom: 0;
}

.modal-group--editable .modal-section:last-child {
    margin-bottom: 0;
}

/* Anchored sections — leave headroom so the hero doesn't cover them on jump. */
.modal-section {
    scroll-margin-top: 1rem;
    margin-bottom: 2rem;
}

.modal-section:last-child {
    margin-bottom: 0;
}

.section-header {
    margin-bottom: 0.9rem;
}

.section-header h3 {
    margin: 0;
    font-size: var(--text-sm);
    font-weight: var(--weight-bold);
    color: var(--text-primary);
    text-transform: uppercase;
    letter-spacing: var(--tracking-uppercase);
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
}

.section-header h3 .rzi {
    color: var(--liu-blue-dark);
    font-size: 1em;
}

.section-sub {
    margin: 0.25rem 0 0;
    color: var(--text-muted);
    font-size: var(--text-sm);
    text-transform: none;
    letter-spacing: 0;
    font-weight: var(--weight-regular);
    line-height: var(--leading-snug);
}

/* Subsection labels — clear "stops" between field groups so the dense
   Submission record stays scannable. Slightly more prominent than the
   uppercase metadata labels (--weight-bold + readable color), with a small
   accent dot on the left to act as a visual anchor as you scroll. */
/* Section stops in the long record. Deliberately a *different kind* of type to
   the row labels beneath them — larger, sentence case, dark — because when both
   were small uppercase greys the heading stopped outranking its own rows. */
.subsection-title {
    margin: 2.5rem 0 0.25rem;
    padding-bottom: 0.7rem;
    font-size: 0.95rem;
    font-weight: var(--weight-bold);
    text-transform: none;
    letter-spacing: 0.01em;
    color: var(--text-primary);
    border-bottom: 1px solid var(--border-default);
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.subsection-title .rzi {
    display: none;
}

.subsection-title:first-of-type {
    margin-top: 0;
}

/* ===== Grid (editable default: stacked label-above-control) =====
   Form controls need full width, so editable fields stay stacked. The read-only
   group below switches to horizontal label/value rows for scannability. */
.modal-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 1.25rem 2rem;
    padding: 0;
}

.modal-field,
.modal-field-full {
    display: flex;
    flex-direction: column;
}

.modal-field-full {
    grid-column: 1 / -1;
}

.modal-field label,
.modal-field-full label {
    font-size: var(--text-xs);
    font-weight: var(--weight-semi);
    text-transform: uppercase;
    letter-spacing: var(--tracking-uppercase);
    color: var(--text-subtle);
    margin-bottom: 0.3rem;
}

/* Read-only group: horizontal definition rows. Label sits left in a fixed
   column, value flows right. Cuts vertical space ~50% vs stacked, and the
   eye can scan straight down the label column. */
/* One label:value pair per row — label left, value right. */
.modal-group--readonly .modal-grid {
    grid-template-columns: 140px minmax(0, 1fr);
    column-gap: 2rem;
    row-gap: 0;
    align-items: baseline;
}

.modal-group--readonly .modal-field,
.modal-group--readonly .modal-field-full {
    /* Dissolve the field wrapper so its label + value become direct grid
       children — each occupies one row, label left, value right. */
    display: contents;
}

/* Same micro-label language as STATUS / NOTE / SUBMITTED elsewhere in the modal:
   uppercase, tracked, quiet. Roomier rows so the record breathes like the
   timeline does. */
.modal-group--readonly .modal-field label,
.modal-group--readonly .modal-field-full label {
    margin-bottom: 0;
    padding: 0.7rem 0;
    line-height: 1.5;
    font-size: var(--text-xs);
    font-weight: var(--weight-semi);
    text-transform: uppercase;
    letter-spacing: var(--tracking-uppercase);
    color: var(--text-subtle);
    border-bottom: 1px solid var(--border-subtle);
}

.field-hint {
    margin-left: 0.4rem;
    text-transform: none;
    letter-spacing: 0;
    color: var(--text-muted);
    font-weight: var(--weight-medium);
}

.modal-value {
    font-size: var(--text-md);
    color: var(--text-primary);
    word-break: break-word;
    line-height: var(--leading-snug);
}

/* Read-only record: plain readable value, faint row rule (label styled above). */
.modal-group--readonly .modal-value {
    padding: 0.7rem 0;
    font-size: 0.875rem;
    font-weight: 400;
    line-height: 1.6;
    letter-spacing: 0.01em;
    color: var(--text-primary);
    border-bottom: 1px solid var(--border-subtle);
}

.modal-value-text {
    font-size: var(--text-md);
    color: var(--text-primary);
    word-break: break-word;
    line-height: var(--leading-snug);
    white-space: pre-wrap;
    background: var(--surface-raised);
    padding: 0.6rem 0.75rem;
    border-radius: 4px;
    border: 1px solid var(--border-default);
}

.payment-amount {
    font-size: var(--text-lg);
    font-weight: var(--weight-semi);
    color: var(--status-success);
}

.tracking-link {
    overflow-wrap: anywhere;
    color: var(--liu-blue-dark);
    text-decoration: none;
    font-weight: var(--weight-medium);
}

.tracking-link:hover {
    text-decoration: underline;
}

/* ===== Attachments ===== */
/* ===== Attachments =====
   One row per file, all the same width. They used to be content-sized chips that wrapped,
   which in a column-width panel put each on its own line at its own length — a ragged
   stack of six different rectangles, where the eye reads a list of files as a list and
   expects a straight edge. Still not the lifted card with a 56px thumb that was built for
   a 900px modal; just a row.
   Capped rather than full-bleed: the record's Attachments block runs the width of the
   record, and a filename at one end of 60rem with its download button at the other is
   worse than a short row. In the Material panel the cap is wider than the column, so
   there it simply fills. */
.attachments-list {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0.4rem;
    max-width: 34rem;
    padding-left: 0.65rem;
}

.attachment-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    max-width: 100%;
    padding: 0.25rem 0.3rem;
    background: var(--surface-raised);
    border: 1px solid var(--border-subtle);
    border-radius: 6px;
    box-shadow: none;
}

.attachment-thumb {
    width: 26px;
    height: 26px;
    object-fit: cover;
    border-radius: 3px;
    border: 1px solid var(--border-default);
    background: var(--surface-base);
}

.attachment-thumb--icon {
    display: flex;
    align-items: center;
    justify-content: center;
}

.attachment-thumb--icon .rzi {
    font-size: 15px;
    color: var(--text-muted);
}

/* Name and size on one line — two stacked lines is what made the row tall. */
/* Takes the slack, so the size and the download button land on the row's right edge
   instead of trailing the filename at whatever length it happens to be. */
.attachment-info {
    display: flex;
    align-items: baseline;
    gap: 0.4rem;
    min-width: 0;
    flex: 1 1 auto;
}

/* No fixed cap now that the row has a width of its own — the ellipsis comes from the
   row running out, which is the same for every row. */
.attachment-name {
    font-size: 0.78rem;
    font-weight: var(--weight-medium);
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--text-primary);
}

/* Right-aligned against the download button rather than trailing the name, so the sizes
   line up down the column and can be compared at a glance. */
.attachment-size {
    flex: 0 0 auto;
    margin-left: auto;
    padding-left: 0.5rem;
    color: var(--text-subtle);
    font-size: 0.68rem;
}

.attachment-row .rz-button {
    min-width: 0;
    width: 1.5rem;
    height: 1.5rem;
    padding: 0;
    margin: 0;
}

.attachment-row .rz-button .rzi { font-size: 0.9rem; }


/* ===== Footer save bar ===== */
.modal-footer {
    position: sticky;
    bottom: 0;
    z-index: 5;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 1rem;
    padding: 0.75rem 1.75rem;
    background: var(--surface-base);
    border-top: 1px solid var(--border-default);
}

.modal-footer-hint {
    color: var(--status-danger);
    font-size: var(--text-sm);
    font-weight: var(--weight-medium);
}

/* Save in the detail modal wears the action tier, the same tinted fill the dialogs and
   the submission form use. It was a solid LIU-blue fill — the third treatment, and the
   one that made a modal's Save louder than the case decision behind it. */
.modal-footer .rz-button.rz-primary:not(:disabled):not(.rz-state-disabled) {
    background-color: var(--liu-blue-light) !important;
    border: 1px solid #bcdcf1 !important;
    color: #1f5f8b !important;
    box-shadow: none !important;
}

.modal-footer .rz-button.rz-primary:not(.autopsy-submit-btn):not(:disabled):not(.rz-state-disabled):hover {
    background-color: var(--liu-blue-light) !important;
    border-color: #bcdcf1 !important;
    color: #1f5f8b !important;
    filter: brightness(0.97);
}

/* ===== Badges (shared) =====
   Status badges (success/danger/warning/info) carry meaning — used for payment
   status, case status, etc. where the color genuinely communicates state.
   Yes/No fact badges are neutral — the field name carries the meaning; the
   chip just answers "is it set?" without coloring the chip good or bad. */
.badge-success,
.badge-danger,
.badge-warning,
.badge-info,
.badge-new,
.badge-default,
.badge-yes,
.badge-no {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.2rem 0.5rem;
    border-radius: 4px;
    font-size: var(--text-sm);
    font-weight: var(--weight-medium);
}

/* Colours live in app.css as .tone-* — see "Case status tones" there. BadgeClass returns
   the tone alongside the badge, so these carry shape only. */

/* Neutral Yes/No — same chip, icon subtly differentiated. No success-green or
   danger-red, because most Yes/No fields are facts, not judgments. */
.badge-yes,
.badge-no {
    background: var(--surface-raised);
    color: var(--text-primary);
    border: 1px solid var(--border-default);
}

.badge-yes .rzi { color: var(--status-success); }
.badge-no  .rzi { color: var(--text-subtle); }

.badge-success .rzi,
.badge-danger .rzi,
.badge-warning .rzi,
.badge-info .rzi,
.badge-default .rzi,
.badge-yes .rzi,
.badge-no .rzi {
    font-size: 1.05em;
}

/* One pill language across the modal: the hero's PAYMENT/CASE chips and the
   Submission record's status chips get the same small-caps treatment as the
   timeline's status pills, so a "state" looks the same wherever it appears.
   Scoped to the modal — the grid pages keep their own badge sizing. */
.case-page .badge-success,
.case-page .badge-danger,
.case-page .badge-warning,
.case-page .badge-info,
.case-page .badge-new,
.case-page .badge-default,
.case-page .badge-yes,
.case-page .badge-no {
    border-radius: 999px;
    padding: 0.12rem 0.5rem;
    font-size: 0.65rem;
    font-weight: var(--weight-bold);
    text-transform: uppercase;
    letter-spacing: 0.09em;
    white-space: nowrap;
}

/* Empty states — quiet and roomy, and always named ("no files", "no activity"),
   never a bare "No data". */
.case-page .empty {
    padding: 2.75rem 1rem;
    text-align: center;
    font-size: 0.875rem;
    letter-spacing: 0.01em;
    color: var(--text-subtle);
}

/* Animal pills — definitions live in admin.css; this modal inherits them. */

/* ===== Responsive collapse ===== */
@media (max-width: 760px) {
    .modal-tabs {
        overflow-x: auto;
        padding: 0 0.75rem;
    }
    .modal-tab {
        flex: 0 0 auto;
    }
    /* The name can wrap off its own line here; the pill follows it rather than
       squeezing beside it. The left indent stays, scaled down with the mark — dropping it
       would leave the silhouette sitting under the text rather than beside it. */
    .case-page { --case-sticky-height: 8.3rem; }
    .case-page.is-locked { --case-sticky-height: 11rem; }
    .modal-hero { padding: 1rem 1.25rem 1rem 5.75rem; }
    .hero-beast { left: 1rem; width: 3.75rem; height: 3.75rem; border-radius: 0.9rem; font-size: 1.9rem; }
    .hero-id { font-size: 1.65rem; }
    .hero-line h2 { white-space: normal; }
    .case-locked-bar { padding: 0.7rem 1.25rem; }
}

/* Transient save confirmation. Deliberately unscoped: this used to hang off
   `.modal-field label`, which the page no longer has, so it was rendering as bare
   grey text with none of these rules applying. */
.save-flash {
    display: inline-flex;
    align-items: center;
    gap: 0.2rem;
    padding: 0.1rem 0.45rem;
    border-radius: 999px;
    background: var(--status-success-bg);
    font-size: 0.65rem;
    font-weight: var(--weight-semi);
    letter-spacing: 0;
    text-transform: none;
    color: var(--status-success-text);
    /* Fades itself out just before the 2.5s timer removes it, so it leaves
       rather than blinking out mid-sentence. */
    animation: save-flash-in 0.18s ease-out both,
               save-flash-out 0.4s ease-in 2.1s both;
}

.save-flash .rzi,
.save-flash .rz-icon { font-size: 0.8rem; }

@keyframes save-flash-in {
    from { opacity: 0; transform: scale(0.92); }
    to   { opacity: 1; transform: none; }
}

@keyframes save-flash-out {
    from { opacity: 1; }
    to   { opacity: 0; }
}

/* ---- Case notes: composer ---- */

.composer-input {
    width: 100%;
    /* Four lines rather than two and a half. A note here is usually a finding, not a
       one-liner, and a field that starts too short says the opposite. Still resizable. */
    min-height: 116px;
    resize: vertical;
    padding: 0.65rem 0.8rem;
    font: inherit;
    font-size: 0.9rem;
    line-height: 1.45;
    color: var(--text-primary);
    /* Faintly sunken rather than white — a white field on a white card has no edge. */
    background: var(--surface-raised);
    border: 1px solid var(--border-default);
    border-radius: 8px;
    transition: background 0.12s ease, border-color 0.12s ease, box-shadow 0.12s ease;
}
.composer-input::placeholder { color: var(--text-muted); }
.composer-input:focus {
    outline: none;
    background: #fff;
    border-color: var(--liu-blue);
    box-shadow: 0 0 0 3px var(--liu-blue-light);
}

.composer-actions {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 0.75rem;
    margin-top: 0.5rem;
}
/* ---- Case notes: timeline ----
   One rail. Comments are cards on it; status changes and sends are quiet
   markers between them, so the writing dominates and the plumbing recedes. */
/* ===== Activity =====
   Grouped by actor: a 26px gutter carries the avatar and the connecting line, the
   text column runs flush left beside it. Identity is stated once per group, so the
   lines beneath it are just what happened. */
.case-timeline {
    list-style: none;
    margin: 1.25rem 0 0;
    padding: 0;
}

/* The gutter widened from 26px when the role moved onto the avatar: the badge hangs
   past the circle's bottom-right, and the column has to hold both. */
.tl-group {
    display: grid;
    grid-template-columns: 34px minmax(0, 1fr);
    gap: 0 0.7rem;
    margin-bottom: 1.15rem;
}

.tl-group:last-child { margin-bottom: 0; }

/* The line runs from under the avatar to the next group's avatar, so a long run
   of actions reads as one continuous visit. */
.tl-gutter { position: relative; }

/* Centred under the wider avatar, and starting below the badge that hangs past its
   bottom-right corner. */
.tl-line {
    position: absolute;
    left: 16px;
    top: 40px;
    bottom: -1.4rem;
    width: 1px;
    background: var(--border-default);
}

.tl-entries { min-width: 0; }

/* The name is a heading for the lines under it, so it needs the gap a heading
   gets — at 0.1rem it sat on top of its own first event. */
.tl-group-head {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    flex-wrap: wrap;
    margin-bottom: 0.4rem;
}

/* One action: the phrase, then the time. A grid rather than flex so the time holds
   its column no matter what padding the row's own surface adds.
   No icon column — a set of 0.85rem grey glyphs read as identical blobs at this
   size, cost a column, and duplicated what the coloured pills already say. */
/* Three columns: the glyph, the phrase, the time. The glyph gets a column of its own
   rather than sitting inline, so every phrase on the timeline starts at the same x —
   with icons inline, the lines with one were indented and the lines without weren't,
   and the left edge came out ragged. */
.tl-act {
    display: grid;
    grid-template-columns: 1.15rem minmax(0, 1fr) 4.5rem;
    gap: 0 0.55rem;
    align-items: baseline;
    padding: 0.4rem 0;
    font-size: 0.82rem;
    line-height: var(--leading-snug);
    color: var(--text-secondary);
}

/* The one judgment on the case. Every neighbouring line carries a coloured pill,
   so a decision in plain grey read as the least important thing here — it gets
   the same palette, applied to the words instead of a chip. */
/* Consequences of the action above — the status a case moved into, the site it
   came from, the note that came with it. Own line, so nothing runs together. */
.tl-detail {
    display: block;
    margin-top: 0.2rem;
    margin-left: 0.9rem;
    color: var(--text-muted);
}

/* List items one per line under the verb they belong to — filenames, or the fields an
   edit touched. Joined by commas they ran together into wrapped prose, and for
   filenames the delimiter occurred inside the names themselves. */
.tl-lines {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.1rem;
    margin-top: 0.2rem;
    margin-left: 0.9rem;
}

.tl-act-text {
    flex: 1 1 auto;
    min-width: 0;
    line-height: 1.45;
}

/* Notes are the reason this panel exists — the rest of the feed is the system
   reporting on itself, so it gets a surface the machine lines don't.
   Shaped like something spoken rather than flagged: white ground, hairline border,
   and one squared corner at the top-left pointing back at the author's name above it.
   The old treatment was a grey band with a blue rule down the side — it read as a
   callout demanding attention, it introduced the only blue on the page that wasn't a
   status pill, and it stretched the full column width for a three-word note.
   The surface wraps the words only: padding the whole row would shove its
   timestamp inward and break the column the other rows line up on. */
.tl-act--said {
    align-items: flex-start;
    margin: 0.45rem 0;
}

/* Flush with the bubble's top edge, not floating above it and not drifting to the
   middle of a note that might run five lines. The glyph marks where the note starts. */
.tl-act--said .tl-mark {
    margin-top: 0;
    align-self: start;
}

.tl-act--said .tl-act-text {
    display: inline-block;
    /* Hugs short notes instead of running to the timestamp column, and caps long ones
       at a readable measure. */
    width: fit-content;
    max-width: 44rem;
    padding: 0.55rem 0.8rem;
    background: var(--surface-base);
    border: 1px solid var(--border-default);
    border-radius: 3px 10px 10px 10px;
    box-shadow: 0 1px 2px rgba(16, 24, 40, 0.04);
}

/* Sits in the gutter column, not absolutely placed against the old row. */
.tl-avatar {
    flex: 0 0 auto;
    width: 26px;
    height: 26px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.7rem;
    font-weight: var(--weight-bold);
    letter-spacing: 0.04em;
    color: var(--text-secondary);
    box-shadow: 0 0 0 3px var(--surface, #fff);
}
/* Tinted to match the row's own LIU/DOCTOR pill exactly — same fill, same ink —
   so the circle and the pill read as one identity. Soft, not solid: the system
   markers on the same rail stay neutral, so this reads as "a person" without
   competing with the status pills. */
/* Avatar tints also come from .role-avatar (app.css), so a person's mark matches
   their tag. Legacy rows with no actor fall through to the system grey there. */

.tl-author { font-weight: 600; font-size: 0.9rem; letter-spacing: 0.01em; }
.tl-pill {
    font-size: 0.62rem;
    font-weight: var(--weight-bold);
    text-transform: uppercase;
    letter-spacing: 0.09em;
    padding: 0.12rem 0.5rem;
    border-radius: 999px;
    white-space: nowrap;
}
/* Colour comes from .role-tag now (app.css). What stays here is only the timeline's
   own spacing — the tints lived in three places and disagreed. */
/* Timestamp: its own fixed column on the right edge, outside the card, so every
   row's time lines up whether it's a comment card or a system marker. */
/* When something happened matters less than who did what — so it's plain
   lowercase text, not a tracked uppercase label competing with the action. */
.tl-time {
    text-align: right;
    font-size: 0.65rem;
    font-weight: var(--weight-regular);
    /* No lowercasing. It was here to quieten "AM"/"PM", which .NET renders in caps and
       which shouted at this size — but text-transform can't tell a meridiem from a month,
       so it took "Aug" down with it. The meridiem is lowercased in the component instead,
       where the two can be told apart. */
    letter-spacing: 0;
    color: var(--text-subtle);
    opacity: 0.75;
    white-space: nowrap;
    line-height: 1.35;
}

/* The clock half of a dated stamp. Its own line because "Jul 29, 2026 9:26 AM" is
   wider than the column and got clipped; each half fits on its own. */
.tl-time-2 { display: block; }
/* A comment is the words themselves — no card, since the group already frames
   who is speaking. Slightly darker than a system line so writing reads as writing. */
.tl-text {
    display: block;
    white-space: pre-wrap;
    overflow-wrap: break-word;
    font-size: 0.84rem;
    line-height: 1.55;
    color: var(--text-primary);
}


/* The status word is the only thing carrying colour on the row — a small caps
   pill, so it reads as a state at a glance instead of as part of the sentence. */
/* A role as a tinted circle instead of a worded pill — same language the dashboard
   feed uses, so a role looks identical wherever activity is shown. The word moves to
   the title attribute; the colour and glyph carry it at a glance. */
/* Every event that is a decision rather than a step gets a mark: approve, deny, hold,
   resume, lock, unlock. One vocabulary, so the eye can pick the turning points out of a
   long thread without reading it — and once each line carries its own glyph, none of them
   needs a coloured font or a rule across the entry to stand out. */
/* Now a grid cell, not an inline run. No ground of its own: fifteen tinted discs down
   the column read as fifteen alerts. The glyph alone is the index; colour is what marks
   the four that are exceptional. */
.tl-mark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.15rem;
    height: 1.15rem;
    border-radius: 50%;
    color: var(--text-subtle);
    vertical-align: middle;
    flex: 0 0 auto;
}

.tl-mark .rzi { font-size: 0.75rem; }

.tl-mark--yes {
    background: color-mix(in srgb, var(--status-success) 12%, #fff);
    color: var(--status-success);
}

.tl-mark--no {
    background: color-mix(in srgb, var(--status-danger) 12%, #fff);
    color: var(--status-danger);
}

.tl-mark--warn {
    background: color-mix(in srgb, var(--status-warning) 18%, #fff);
    color: var(--status-warning-text);
}

/* ---------- locked case ---------- */

/* Beside the status pill in the hero. Solid slate, not a tint: tinted, it sat at exactly
   the weight of the status pill next to it and read as a second status rather than as a
   wall. Still slate rather than a warning colour — a lock is the expected end state of a
   finished case, not something wrong with it. */
.case-locked {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    margin-left: 0.4rem;
    padding: 0.22rem 0.6rem;
    border-radius: 999px;
    background: #334155;
    color: #fff;
    font-size: 0.65rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.09em;
    white-space: nowrap;
    vertical-align: middle;
}

.case-locked .rzi { font-size: 0.85rem; }

/* The announcement, at page width directly under the header. The chip above says the
   word; this says the sentence, including who to go to — which the chip could only do in
   a title attribute nobody hovers. It is also the only place a clinic user is told any of
   this: the fuller explanation lives in the rail, which they never see. */
.case-locked-bar {
    display: flex;
    align-items: center;
    gap: 0.65rem;
    padding: 0.7rem 2rem;
    background: #334155;
    color: #e2e8f0;
    font-size: var(--text-md);
    border-bottom: 1px solid #1e293b;
}

.case-locked-bar .rzi { font-size: 1.05rem; color: #fff; flex: 0 0 auto; }
.case-locked-bar b { color: #fff; font-weight: var(--weight-bold); }

/* Takes the place of the next-step button in the Progress panel, so the space that
   normally says "here is what to do" explains why there is nothing to do instead. */
/* A lock is a boundary in the case's history, not another line in it, so it carries
   more weight than the quiet panels around it: a solid left edge and a ground that
   isn't the same raised grey every other block uses. Deliberately not amber — that is
   the hold's colour, and the two are different states. */
.lock-strip {
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
    padding: 0.75rem 0.85rem;
    border-radius: var(--r-card);
    background: var(--surface-sunken);
    border: 1px solid var(--border-strong);
    border-left: 3px solid var(--text-subtle);
    margin-bottom: 0.6rem;
}

.lock-strip > .rzi { font-size: 1.1rem; color: var(--text-subtle); flex: 0 0 auto; }

.lock-strip-title {
    font-size: 0.82rem;
    font-weight: var(--weight-bold);
    color: var(--text-secondary);
}

.lock-strip-why {
    font-size: 0.73rem;
    line-height: 1.5;
    color: var(--text-muted);
    margin-top: 2px;
}

.tl-role {
    width: 1.4rem;
    height: 1.4rem;
    flex: 0 0 auto;
    vertical-align: middle;
}

.tl-role .rzi { font-size: 0.85rem; }

.tl-kw {
    display: inline-flex;
    align-items: center;
    vertical-align: middle;
    margin: 0 0.1rem;
    padding: 0.12rem 0.5rem;
    border-radius: 999px;
    font-size: 0.65rem;
    font-weight: var(--weight-bold);
    text-transform: uppercase;
    letter-spacing: 0.09em;
    white-space: nowrap;
    background: var(--surface-sunken);
    color: var(--text-secondary);
}
.tl-kw--success {
    background: color-mix(in srgb, var(--status-success) 10%, #fff);
    color: var(--status-success);
}
.tl-kw--danger {
    background: color-mix(in srgb, var(--status-danger) 10%, #fff);
    color: var(--status-danger);
}
.tl-kw--warning {
    background: color-mix(in srgb, var(--status-warning) 16%, #fff);
    color: var(--status-warning-text);
}
.tl-kw--info {
    background: color-mix(in srgb, var(--status-info) 12%, #fff);
    color: var(--status-info-text);
}
/* No --muted variant: it only ever dressed names and emails, which are .tl-name now.
   A status pill always has a hue, so a colourless one would mean nothing. */

/* A proper noun — a person or a clinic. Deliberately NOT a pill: the set is open, and
   pill shape implies a vocabulary the reader could learn. It also never needed the
   uppercase/tracking/ellipsis workarounds a long site name forced when it was one.
   This is the ONLY bold on the timeline, and the rule is one line long: bold means an
   actor. Not a filename, not a field, not a status — a who. It was also carrying
   filenames for a while, and three bold names stacked under an upload outweighed every
   other line on the page. */
.tl-name {
    font-weight: var(--weight-semi);
    color: var(--text-primary);
}

/* A fixed-set attribute that isn't a status — a role, a billing flag. Pill shaped,
   because it IS a value from a known set, but always this one neutral treatment:
   colour inside a pill is reserved for case status, so a green "Non-profit" would
   read as an outcome alongside Approved and Completed. */
/* Was .tl-tag alone, named for the timeline — which meant a grid or a form that wanted
   the same chip had to either import a timeline class or draw its own near-copy. Same
   values, two names, one look: .fact-tag anywhere, .tl-tag where the timeline already
   says it. */
.tl-tag,
.fact-tag {
    display: inline-flex;
    align-items: center;
    vertical-align: middle;
    margin: 0 0.1rem;
    padding: 0.12rem 0.5rem;
    border-radius: 999px;
    font-size: 0.62rem;
    font-weight: var(--weight-bold);
    text-transform: uppercase;
    letter-spacing: 0.09em;
    white-space: nowrap;
    background: var(--surface-sunken);
    color: var(--text-subtle);
}

/* The assigner's note, quoted inline on an assignment row. */

/* Filenames run long and carry no spaces to break at, so left alone one can push the
   timeline's column wider than its pane. Breaks anywhere rather than overflowing. */
/* Data, not an actor: regular weight. Still distinguishable as a filename by sitting
   in its own indented list under the verb that added it. */
.tl-file {
    overflow-wrap: anywhere;
    color: var(--text-secondary);
}

/* A file still on the case downloads from the thread. Styled as a link rather than a
   button — it belongs to the sentence above it, and a row of grey buttons under every
   upload would out-weigh the entries themselves. The icon is what says it does
   something; the underline only appears when you go for it. */
button.tl-file-link {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0;
    background: none;
    border: 0;
    cursor: pointer;
    font: inherit;
    text-align: left;
    color: var(--liu-blue-dark);
}

button.tl-file-link:hover span,
button.tl-file-link:focus-visible span { text-decoration: underline; }
button.tl-file-link .rzi { font-size: 0.85rem; flex: 0 0 auto; }

/* A field an edit touched. Not .tl-name — that is for people and clinics, proper nouns
   with weight; these are the names of boxes on a form. */
.tl-changed { color: var(--text-muted); }

/* A note attached to an action — the reason for a hold, the word with an assignment.
   Deliberately NOT the bubble above: that is someone starting a conversation, this is
   an annotation on something they did, so it stays a quiet italic sub-line.
   Always worn with .tl-detail, which owns the indent. Setting margin-left here won on
   source order and pulled quoted notes out to the left of the sub-lines above them. */
.tl-note {
    color: var(--text-muted);
    font-style: italic;
}


/* ---- Live controls ----
   Status and the note composer are one control strip, not two cards. No frame
   of their own: the inputs are the only thing that carries a surface, and a
   single rule separates the whole strip from the timeline underneath. */
.case-controls {
    padding-bottom: 1.5rem;
    margin-bottom: 0.5rem;
    border-bottom: 1px solid var(--border-default);
}

.case-block + .case-block { margin-top: 1.25rem; }

.case-console {
    display: flex;
    align-items: flex-end;
    flex-wrap: wrap;
    gap: 1rem 1.5rem;
}

.case-console-field {
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
    min-width: 0;
}

.case-console-field > label,
.case-block-label {
    display: inline-flex;
    align-items: center;
    font-size: var(--text-xs);
    font-weight: var(--weight-semi);
    text-transform: uppercase;
    letter-spacing: var(--tracking-uppercase);
    color: var(--text-subtle);
}

/* A label sitting flush on the control it names reads as part of the control. */
.case-block-label { margin-bottom: 0.5rem; }

/* A label with its own add-files button, on one line — the shape .spec-gname gives
   Attachments over on the record. The button carries its own left gap. */
.case-block-head { display: flex; align-items: center; margin-bottom: 0.5rem; }
.case-block-head .case-block-label { margin-bottom: 0; }

/* And the list needs to come off the control that fills it, for the same reason. */
.case-material .attachments-list { margin-top: 0.9rem; }
/* Framed, and centred inside the frame. It was a sentence floating in a wide column with
   its heading hard left — the two did not read as belonging to each other. The dashed edge
   also says the panel is a place things go, which a line of grey text does not. */
/* Both file panels say "nothing here yet" the same way. Attachments used to say it as a
   line of grey text under its heading while Documentation said it in a framed box, so two
   panels doing the same job looked like two different kinds of thing. The dashed edge is
   also the part that reads as "somewhere files go", which loose text does not. */
.case-material .empty,
.spec-none {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 2rem 1rem;
    border: 1px dashed var(--border-strong);
    border-radius: 10px;
    background: var(--surface-base);
    text-align: center;
    line-height: var(--leading-loose);
    font-size: var(--text-sm);
    color: var(--text-muted);
}

/* Icon-led actions: the glyph carries the meaning, the word just confirms it.
   Regular weight throughout — Radzen's semibold default reads heavier than
   these deserve next to the quiet field labels. */
.case-block .rz-button,
.case-block .rz-button .rz-button-text,
.case-block .rz-button .rzi {
    font-weight: 400;
}
.case-block .rz-button .rzi { font-size: 1.25rem; }
.case-block .rz-button .rz-button-text { font-size: 0.85rem; }

/* Inputs are the only tinted surfaces in the pane — dropdown and note field
   match, and both go white on focus so the active one is obvious. */
.case-status-dd { width: 240px; }
.case-status-dd.rz-dropdown {
    background-color: var(--surface-raised);
    border-color: var(--border-default);
}
.case-status-dd.rz-dropdown:not(.rz-state-disabled):hover,
.case-status-dd.rz-dropdown.rz-state-focused {
    background-color: var(--surface-base);
}

/* Disabled should read as "waiting on you", not as a broken control. */
.case-block .rz-button[disabled],
.case-block .rz-button.rz-state-disabled {
    background-color: var(--surface-sunken) !important;
    border-color: var(--border-default) !important;
    color: var(--text-muted) !important;
    opacity: 1;
}
.case-block .rz-button[disabled] .rzi,
.case-block .rz-button.rz-state-disabled .rzi { color: var(--text-muted); }

.case-console-field--meta { padding-bottom: 0.4rem; }

.case-console-meta {
    font-size: var(--text-sm);
    font-weight: var(--weight-semi);
    color: var(--text-primary);
}

/* Send rides the far right of the status row. On its own line it left a dead
   band of whitespace; the gap across the row separates it well enough. */
.case-console > .rz-button { margin-left: auto; }

@media (max-width: 620px) {
    .case-console > .rz-button { margin-left: 0; width: 100%; }
    .case-status-dd { width: 100%; }
    .case-console-field { flex: 1 1 100%; }
}


/* ===== Editing the filed record =====
   A pencil beside the section it amends. Only visible to an admin, and quiet by
   default — correcting the record is a rare errand, not one of the page's actions. */
.spec-edit {
    margin-left: 0.4rem;
    padding: 0;
    background: none;
    border: 0;
    cursor: pointer;
    color: var(--text-subtle);
    opacity: 0;
    transition: opacity 0.15s ease, color 0.15s ease;
    vertical-align: middle;
}

.spec-edit .rzi { font-size: 0.85rem; }

/* Revealed on approach, and always present for keyboard users — an action that only
   exists on hover is an action a keyboard can't find. */
.spec-group:hover .spec-edit,
.spec-edit:focus-visible { opacity: 1; }
.spec-edit:hover { color: var(--liu-blue-dark); }

/* The section an edit touched. Neutral: an amendment is not a case status, and the
   coloured pills on this page are reserved for those. */
.tl-kw--muted-edit { background: var(--surface-sunken); color: var(--text-secondary); }


/* Add-files control. A native file input is unstyleable, so the label is the button and
   the input inside it is hidden — clicking the label still opens the picker, and the
   control keeps the same quiet outline every other secondary action on this page has. */
.spec-upload {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    margin-left: 0.6rem;
    padding: 0.2rem 0.6rem;
    border: 1px solid var(--border-default);
    border-radius: var(--r-button);
    cursor: pointer;
    font-size: var(--text-xs);
    font-weight: var(--weight-medium);
    text-transform: none;
    letter-spacing: 0;
    color: var(--text-muted);
    vertical-align: middle;
}

.spec-upload:hover { color: var(--text-primary); border-color: var(--border-strong); }
.spec-upload input[type="file"] { display: none; }
.spec-upload .rzi { font-size: 0.9rem; }
.spec-upload.is-busy { opacity: 0.6; cursor: default; }


/* ===== Editing your own note =====
   Quiet until you approach it, the same rule the record's edit pencil and the focus
   control follow. Only ever rendered on your own note, on an unlocked case — the
   component decides that, this only styles it.
   Top-right of the bubble, the corner the record cards put their control in — it used
   to trail the note text, and because the note is a block it wrapped onto a line of its
   own underneath. The bubble reserves that space permanently so a long first line can
   never run under the pencil. */
.tl-act--said .tl-act-text {
    position: relative;
    padding-right: 2.1rem;
}

.tl-edit-open {
    position: absolute;
    top: 0.3rem;
    right: 0.35rem;
    padding: 0.15rem;
    background: none;
    border: 0;
    border-radius: var(--r-input);
    cursor: pointer;
    color: var(--text-subtle);
    opacity: 0;
    transition: opacity 0.15s ease, color 0.15s ease;
    line-height: 1;
}

.tl-act:hover .tl-edit-open,
.tl-edit-open:focus-visible { opacity: 1; }
.tl-edit-open:hover { color: var(--liu-blue-dark); }
.tl-edit-open .rzi { font-size: 0.85rem; }

/* The editor replaces the bubble with a form. It fills the row rather than hugging its
   content — a textarea sized to fit is a textarea nobody can type in. */
.tl-edit {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    width: 100%;
    max-width: 44rem;
}

.tl-edit .composer-input {
    width: 100%;
    min-height: 6rem;
    resize: vertical;
}

.tl-edit-actions {
    display: flex;
    justify-content: flex-end;
    gap: 0.4rem;
}

/* Not a second timestamp — a note on the one above it. */
.tl-edited {
    font-style: italic;
    opacity: 0.85;
}

/* Downloading a file is a secondary action, so it wears the neutral member of the
   page's button family — the same transparent box, hairline border and 8px radius that
   .rail-action, .decision-hold and .case-tool wear. It used to be a solid blue Radzen
   button, which made every file row shout louder than the one primary action the view
   is allowed. */
.attachment-get {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    padding: 0.28rem 0.4rem;
    background: transparent;
    border: 1px solid var(--border-default);
    border-radius: 8px;
    cursor: pointer;
    color: var(--text-muted);
    line-height: 1;
    transition: color 0.15s ease, border-color 0.15s ease;
}

.attachment-get:hover,
.attachment-get:focus-visible {
    color: var(--text-primary);
    border-color: var(--border-strong);
}

.attachment-get .rzi { font-size: 0.95rem; }


/* ===== Role badge on the timeline avatar =====
   Pushed 6px along the diagonal rather than centred on the avatar's edge: at the
   conventional position half the disc sits over the initials, and two letters in a
   30px circle have none to spare. Out here only its inner corner clips the circle, so
   the initials read whole and the badge still reads as attached rather than floating.
   White ground and a grey glyph — the role is already carried by the avatar's tint, and
   a second hue here was what made the old header two coloured objects instead of one. */
/* 2.1rem here rather than the shared 1.9rem. Two initials in a 30px circle leave the
   badge nothing to clip into — at that size it took a letter with it whichever corner
   it sat in. */
.tl-avatar {
    position: relative;
    width: 2.1rem;
    height: 2.1rem;
}

.tl-role-badge {
    position: absolute;
    right: -6px;
    bottom: -6px;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 1.05rem;
    height: 1.05rem;
    border-radius: 50%;
    background: var(--surface-base);
    border: 1px solid var(--border-default);
    /* A ring of page colour, so the disc reads as sitting on top of the avatar rather
       than cut into it. */
    box-shadow: 0 0 0 1.5px var(--surface-base);
    color: var(--text-subtle);
}

.tl-role-badge .rzi { font-size: 0.68rem; }

/* The same disc, sitting in a sentence instead of on an avatar. Everything the avatar
   version needs to hang off a corner is undone; the look is untouched, which is the
   point — one badge, two placements. */
.tl-role-badge--inline {
    position: static;
    display: inline-flex;
    vertical-align: -0.2em;
    margin-left: 0.3rem;
    box-shadow: none;
}


/* ===== The rail's state block =====
   Where the case stands and what to do about it, as one object. It replaced a six-dot
   list that spent the rail's whole height saying what a stage name and a bar say in two
   lines — the journey is still there, as History below, which is what it always was. */
/* Takes the colour of the status it is announcing — an on-hold case's block is amber, a
   denied one's is red. It was always LIU blue, which meant the loudest element in the rail
   said the same thing whatever had happened to the case. */
.now-card {
    padding: 0.85rem 0.9rem 0.75rem;
    border-radius: 10px;
    background: var(--tone-bg);
    border: 1px solid var(--tone-edge);
}

.now-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    min-height: 1.2rem;
}

.now-k {
    font-size: 0.66rem;
    text-transform: uppercase;
    letter-spacing: var(--tracking-uppercase);
    color: var(--tone-ink);
    font-weight: var(--weight-bold);
    opacity: 0.8;
}

.now-v {
    margin: 0.15rem 0 0.1rem;
    font-size: 1.15rem;
    font-weight: var(--weight-bold);
    color: var(--tone-ink);
    line-height: var(--leading-tight);
}

.now-sub { font-size: 0.74rem; color: var(--tone-ink); opacity: 0.8; }

/* Six segments, one per stage. A bar answers "how far along" in a glance that six stacked
   dots never quite did — the dots said which stages exist, not how much is left. */
.now-meter { display: flex; gap: 3px; margin: 0.75rem 0 0.3rem; }

.now-meter i {
    flex: 1;
    height: 4px;
    border-radius: 2px;
    background: var(--tone-edge);
}

.now-meter i.is-on { background: var(--tone-ink); }

/* Small OR quiet, not both. At 0.62rem and 70% this was under every contrast floor going. */
.now-meter-lbl { font-size: 0.7rem; color: var(--tone-ink); opacity: 0.85; }

/* The escape hatch, in the corner of the block it changes — the corner the record's cards
   put their edit control in. Quiet until approached, so the action underneath stays the
   only thing that looks like one. */
.status-tweak {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.1rem 0.35rem;
    border: 0;
    border-radius: 5px;
    background: transparent;
    color: var(--tone-ink);
    opacity: 0.65;
    font: inherit;
    font-size: 0.6rem;
    font-weight: var(--weight-bold);
    text-transform: uppercase;
    letter-spacing: var(--tracking-uppercase);
    cursor: pointer;
    transition: opacity 0.15s ease, background 0.15s ease;
}

.status-tweak:hover,
.status-tweak:focus-visible { opacity: 1; background: rgba(255, 255, 255, 0.6); }

.status-tweak .rzi { font-size: 0.8rem; }

/* ===== History =====
   The journey, as a record rather than as the page's centrepiece. */
.hist { list-style: none; margin: 0; padding: 0; }

.hist-row {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    padding: 0.3rem 0;
    font-size: 0.78rem;
    color: var(--text-subtle);
}

.hist-row .rzi { font-size: 0.85rem; color: var(--border-strong); flex: 0 0 auto; }

.hist-row.is-done { color: var(--text-primary); }
.hist-row.is-done .rzi { color: var(--status-success); }

/* The stage mark is the same object the submission stepper and the thank-you page use:
   a disc of status-green mixed 14% into white with a green tick inside. A clinic sees it
   on the way in and LIU sees it on the case — one "this step is done" mark, not three
   drawn different ways. An unreached stage keeps the plain outline circle, so only the
   reached ones carry a disc. */
.hist-mark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 1.15rem;
    height: 1.15rem;
    border-radius: 50%;
}

.hist-row.is-done .hist-mark {
    background: color-mix(in srgb, var(--status-success) 14%, #fff);
}

/* Smaller inside the disc than the bare outline circle it replaces — the disc carries
   the weight, so the tick doesn't have to. */
.hist-row.is-done .hist-mark .rzi { font-size: 0.72rem; }

/* Where a case left the line. Same disc, same size, a cross instead of the tick — so the
   column still reads as one sequence and the one row that stopped it is the one that
   isn't green. It stands in the slot the case would have gone to next, which is why a
   denial sits where Approved does. */
.hist-row--exit { color: var(--text-primary); }
.hist-row--exit .hist-mark {
    background: color-mix(in srgb, var(--status-danger) 14%, #fff);
}
.hist-row--exit .hist-mark .rzi { font-size: 0.72rem; color: var(--status-danger); }

.hist-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Right-aligned so the dates line up down the column and can be read as a sequence. */
.hist-when {
    margin-left: auto;
    padding-left: 0.5rem;
    font-size: 0.72rem;
    color: var(--text-subtle);
    white-space: nowrap;
}


/* ===== A rail section's own control =====
   Heading left, the thing that changes it right. Progress already worked this way with its
   Change hatch; Assigned did not, so its Reassign sat full width under the assignee and
   read as the next step to take rather than as a way to alter the line above it.
   Quiet and inline, because it is an amendment, not the case's next move — that button is
   the solid one further up the rail, and there is only ever one of those. */
.rail-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    margin-bottom: 0.75rem;
}

.rail-head .rail-cap { margin-bottom: 0; }

/* Same box as .spec-upload's "Choose files": same padding, radius, type size and weight,
   so the two small outlined actions on this page are one control in two places rather
   than two near-misses sitting a few pixels apart. */
.rail-tweak {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    flex: 0 0 auto;
    padding: 0.2rem 0.6rem;
    border: 1px solid var(--border-default);
    border-radius: var(--r-button);
    background: var(--surface-base);
    color: var(--text-muted);
    font: inherit;
    font-size: var(--text-xs);
    font-weight: var(--weight-medium);
    cursor: pointer;
    transition: color 0.15s ease, border-color 0.15s ease;
}

.rail-tweak:hover:not([disabled]) {
    color: var(--text-primary);
    border-color: var(--border-strong);
}

.rail-tweak[disabled] { opacity: 0.55; cursor: default; }
.rail-tweak .rzi { font-size: 0.9rem; }

/* Attachments is the record's last block and the only thing in its row now that Record
   details is gone, so it runs the full width instead of holding a column with no
   neighbour. */
.spec-block { margin-top: 1.5rem; }
