/* Admin dashboard — the operations overview.
 *
 * Every class here is `dash-` prefixed on purpose. The one real styling bug this app
 * has shipped was a bare `.role-tag` declared in two stylesheets, where load order
 * decided the winner and quietly greyed out every role pill in the product. A page
 * stylesheet gets its own namespace so it can never do that to a shared component.
 *
 * Shared components are used as they are, not restyled: .role-tag, .role-avatar,
 * .tl-kw and .case-tool all come from app.css and request-detail.css so a status or
 * a person looks identical here and on the case page. */

/* ---------- page ----------
 *
 * An ordinary scrolling page. It used to claim the viewport the way .case-page does —
 * 100vh minus the chrome, with the long lists scrolling inside their own cards — so the
 * whole dashboard sat on one screen. Everything below that fixed height existed to serve
 * it: cards that fought over leftover space, a max-height backstop, internal scrollers.
 * With the height gone none of it has anything to divide, so it went too rather than
 * staying as rules that quietly do nothing.
 *
 * No padding of its own now either: the shell's .admin-content supplies it like every
 * other admin page. That exception only existed because a self-sizing page can't also
 * accept padding from its container without overflowing. */
/* The page owns the viewport again — header and footer subtracted, nothing spilling past
   it. It had been left to scroll as one document, which is fine until one list is long:
   Requires Attention ran hundreds of rows down the page and left the right-hand column
   stranded beside a screen of white. Bounding the page puts the overflow back inside the
   card it belongs to, where a scrollbar says how much is left. */
.dash-page {
    display: flex;
    flex-direction: column;
    gap: 0.85rem;
    /* Minus the shell's own inset as well as the bars: .admin-content pads every side and
       adds a bottom margin, so a page that only subtracted the header and footer came out
       4.5rem too tall and put a scrollbar on the document — which is the one thing a
       one-screenful page must not do. The full-bleed pages dodge this by having the shell
       zero its padding for them; the dashboard wants the padding, so it pays for it here. */
    height: calc(100vh - var(--admin-header-height) - var(--app-footer-height)
                 - (var(--admin-content-inset) * 3));
    min-height: 0;
}

.dash-page > .dash-topbar,
.dash-page > .dash-card--pipe { flex: 0 0 auto; }

/* The shared toolbar carries its own bottom margin; the page's gap replaces it. */
.dash-topbar { margin: 0; }

/* Everything under the pipeline. Fills the leftover height exactly — min-height:0 is
   what lets a grid/flex child be SHORTER than its content so the scroll containers
   inside it have something to scroll against. */
/* Stretch, now that the page is bounded again: both columns run to the same floor, and the
   slack inside each one goes to its scrolling list rather than being left under it. Ragged
   bottoms were the visible symptom of a page that had stopped owning its own height. */
/* Four cards, placed by name. Two arrangements share this grid and differ only in the
   template below, which is what stops one of them quietly falling behind the other. */
.dash-body {
    display: grid;
    grid-template-columns: 1.15fr 0.85fr;
    /* Two rows, not three: an admin has no Site Caseload card, so a row kept for it would
       be a gap. The band template below adds it back for the tier that does. */
    grid-template-rows: auto minmax(0, 1fr);
    grid-template-areas:
        "att doc"
        "att feed";
    gap: 0.85rem;
    align-items: stretch;
    flex: 1 1 auto;
    min-height: 0;
}

.dash-at--att  { grid-area: att; }
.dash-at--doc  { grid-area: doc; }
.dash-at--site { grid-area: site; }
.dash-at--feed { grid-area: feed; }

/* The band. The two caseload lists are short by nature — a handful of doctors, a handful of
   sites — so they sit side by side in a row sized to them, and the two lists that are long
   by nature get a full-height column each below.
   It costs the worklist height, which is the trade: three cards in one narrow column meant
   every one of them was cut off, and this cuts one of them a little instead. */
.dash-body--band {
    grid-template-columns: 1fr 1fr;
    grid-template-rows: auto minmax(0, 1fr);
    grid-template-areas:
        "doc  site"
        "att  feed";
}

/* Both caseload cards take the height their list actually needs — an `auto` row, so five
   doctors get five rows rather than whatever was left over. That was the original
   complaint: the card was sized by the space remaining instead of by its contents.

   Capped, in both arrangements, because "as tall as it needs" is only sensible while the
   list is short. Fifteen doctors would push the worklist and the feed off the screen, which
   is the exact problem being fixed. Past the cap the list scrolls inside its own card, like
   every other list here.

   In the band the two share a row, so the taller sets the height and the shorter stretches
   to meet it — level bottoms rather than a ragged step. */
.dash-body > .dash-at--doc,
.dash-body > .dash-at--site { max-height: 17rem; }

/* Every card in the grid sizes to its cell and scrolls inside it, so the row template is the
   only thing deciding heights. --fill and --natural still describe intent; the grid
   enforces it. */
.dash-body > .dash-card { min-height: 0; }

/* ---------- card + caption ---------- */

.dash-card {
    background: #fff;
    border: 1px solid var(--border-default);
    border-radius: var(--r-card);
    padding: 1.1rem 1.2rem;
}

.dash-card--fill,
.dash-card--natural {
    display: flex;
    flex-direction: column;
    min-height: 0;
}

/* Left over from when the right-hand cards were a flex column. They are grid items now and
   the row template decides their height, so the flex basis only ever mattered as a way for a
   card to shrink below its content — which is precisely what made Doctor Caseload show two
   rows of five. Kept only as intent: --fill means "take the leftover row", --natural means
   "size to your list". The grid enforces both. */
.dash-card--fill,
.dash-card--natural { flex: 0 1 auto; }

/* The list scrolls, not the page. min-height:0 is what lets a flex child be SHORTER than
   its content — without it the card grows to fit and nothing ever overflows. */
.dash-scroll {
    min-width: 0;
    min-height: 0;
    overflow-y: auto;
    /* Room for the thumb so the last column of text isn't under it. */
    padding-right: 0.2rem;
}

.dash-cap {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    margin: 0 0 0.85rem;
    font-size: var(--text-xs);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: var(--tracking-uppercase);
    color: var(--text-subtle);
}

.dash-empty {
    display: flex;
    align-items: center;
    gap: 0.7rem;
    padding: 1.1rem 0.2rem;
    color: var(--text-muted);
    font-size: 0.82rem;
}

.dash-empty .rzi { font-size: 1.4rem; color: var(--border-strong); }
.dash-empty b { color: var(--text-secondary); font-size: 0.86rem; }
.dash-empty div div { margin-top: 1px; }

/* ---------- arrivals ----------
 *
 * A live screen that changes silently is worse than one you refresh yourself: you can't
 * tell whether you're reading news or the same page you already read. A new row slides
 * in under a wash of LIU blue that clears itself.
 *
 * No fill-mode on purpose — the animation ends exactly at the row's normal appearance,
 * so the marker class can stay on the element until the next load without leaving it
 * looking different, and nothing has to be cleaned up on a timer.
 *
 * Once only: a CSS animation replays when the element is recreated, and @key means an
 * existing row is reused rather than rebuilt, so a poll tick that changes nothing
 * doesn't set every row flashing again. */
@keyframes dash-arrive {
    from { opacity: 0; transform: translateY(-8px); background-color: var(--liu-blue-light); }
    55%  { opacity: 1; transform: none;            background-color: var(--liu-blue-light); }
    to   { opacity: 1; transform: none;            background-color: transparent; }
}

.dash-act.is-new,
.dash-feed-row.is-new {
    animation: dash-arrive 1.1s ease-out;
    border-radius: 5px;
    /* The wash needs room to be a highlight rather than a stripe. Rows carry no
       horizontal padding, so the coloured band was butting straight against the status
       rail on one side and the button on the other, which reads as broken spacing
       rather than emphasis. The negative margin cancels the padding exactly, so the
       row's contents don't move a pixel when the class goes on or comes off. */
    padding-left: 0.5rem;
    padding-right: 0.5rem;
    margin-left: -0.5rem;
    margin-right: -0.5rem;
}

/* The pill is a 12% tint, which all but disappears once it is sitting on the wash.
   Invert it to solid for exactly as long as the wash is up — the timing and the 55%
   hold match dash-arrive, so the two fade out together instead of the badge staying
   loud after the highlight has gone. */
.dash-act.is-new .dash-new {
    animation: dash-new-in 0.4s ease-out, dash-new-lift 1.1s ease-out;
}

@keyframes dash-new-lift {
    from, 55% { background: var(--liu-blue-dark); color: #fff; }
    to        { background: color-mix(in srgb, var(--liu-blue-dark) 12%, #fff);
                color: var(--liu-blue-dark); }
}

/* Motion is decoration here — the row is legible either way, so anyone who has asked
   the OS for less of it gets the highlight without the movement. */
@media (prefers-reduced-motion: reduce) {
    .dash-act.is-new,
    .dash-feed-row.is-new { animation: none; background-color: var(--liu-blue-light); }
}

/* ---------- pipeline ---------- */

.dash-pipe {
    display: flex;
    align-items: stretch;
    gap: 6px;
}

/* A button, not a div: each stage is a way into the filtered list, so it has to be
   reachable by keyboard and announce itself as an action. */
.dash-stage {
    flex: 1 1 0%;
    min-width: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-start;
    gap: 2px;
    background: var(--surface-raised);
    border: 1px solid var(--border-default);
    border-radius: var(--r-card);
    padding: 0.7rem 0.5rem;
    font: inherit;
    color: inherit;
    cursor: pointer;
    text-align: center;
    transition: border-color 0.12s ease, background 0.12s ease, transform 0.12s ease;
}

/* Hover lifts the wash off rather than adding to it, so the hovered tile is the
   lightest thing in the row and can't read as a state change. Beats the tone rules
   below on specificity (pseudo-class), so it doesn't need repeating per tone. */
.dash-stage:hover {
    border-color: var(--liu-blue-dark);
    background: #fff;
    transform: translateY(-1px);
}

.dash-stage:focus-visible {
    outline: 2px solid var(--liu-blue-dark);
    outline-offset: 2px;
}

.dash-stage-n { font-size: 1.55rem; font-weight: 700; line-height: 1.1; }

.dash-stage-l {
    font-size: 0.6rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: var(--tracking-uppercase);
    color: var(--text-subtle);
    line-height: 1.25;
}

/* Each stage wears its own status colour, at the same weight the status pills use
   elsewhere — a wash for the tile and full strength on the number, so the row reads
   as one line of related things rather than six unrelated boxes. Tint percentages
   match .tl-kw so a status is literally the same colour on every screen.
   Submitted keeps the neutral ground: it's the front of the queue, not a verdict. */
.dash-stage--neutral { background: var(--surface-raised); }
.dash-stage--neutral .dash-stage-n { color: var(--text-secondary); }

.dash-stage--warning {
    background: color-mix(in srgb, var(--status-warning) 16%, #fff);
    border-color: color-mix(in srgb, var(--status-warning) 34%, #fff);
}
.dash-stage--warning .dash-stage-n { color: var(--status-warning-text); }
.dash-stage--warning .dash-stage-l { color: color-mix(in srgb, var(--status-warning-text) 72%, #fff); }

.dash-stage--success {
    background: color-mix(in srgb, var(--status-success) 10%, #fff);
    border-color: color-mix(in srgb, var(--status-success) 28%, #fff);
}
.dash-stage--success .dash-stage-n { color: var(--status-success); }
.dash-stage--success .dash-stage-l { color: color-mix(in srgb, var(--status-success-text) 70%, #fff); }

.dash-stage--info {
    background: color-mix(in srgb, var(--status-info) 12%, #fff);
    border-color: color-mix(in srgb, var(--status-info) 30%, #fff);
}
.dash-stage--info .dash-stage-n { color: var(--status-info-text); }
.dash-stage--info .dash-stage-l { color: color-mix(in srgb, var(--status-info-text) 72%, #fff); }

.dash-stage--danger {
    background: color-mix(in srgb, var(--status-danger) 10%, #fff);
    border-color: color-mix(in srgb, var(--status-danger) 28%, #fff);
}
.dash-stage--danger .dash-stage-n { color: var(--status-danger); }

.dash-pipe-foot {
    display: flex;
    flex-wrap: wrap;
    gap: 1.4rem;
    margin-top: 0.9rem;
    padding-top: 0.8rem;
    border-top: 1px solid var(--border-subtle);
    font-size: 0.78rem;
    color: var(--text-muted);
}

/* Buttons, same as the tiles above them — a hold and a denial are queues too. */
.dash-foot-item {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.2rem 0.45rem;
    margin: -0.2rem 0;
    background: none;
    border: 0;
    border-radius: var(--r-button);
    font: inherit;
    color: inherit;
    cursor: pointer;
}

.dash-foot-item:hover { background: var(--surface-raised); color: var(--text-secondary); }
.dash-foot-item:focus-visible { outline: 2px solid var(--liu-blue-dark); outline-offset: 1px; }

/* ---------- needs-you queue ---------- */

.dash-act {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.6rem 0;
    border-bottom: 1px solid var(--border-subtle);
}

.dash-act:last-of-type { border-bottom: 0; }

.dash-why { flex: 1 1 auto; min-width: 0; }
.dash-why b { font-size: 0.84rem; color: var(--text-primary, var(--text-secondary)); }


.dash-age {
    font-size: 0.72rem;
    color: var(--text-subtle);
    white-space: nowrap;
    flex: 0 0 auto;
}

/* Arrived while you were looking at the page.
 *
 * A quiet pill beside the case name. No dot: a marker that pulses beside a dozen others
 * stops being a signal, and the word already says everything the dot was hinting at.
 *
 * Blue rather than the red it was sketched in: red is load-bearing in this app — denied,
 * payment failed, on hold — and a perfectly healthy new request wearing it reads as
 * something wrong. One token to change if you want the red anyway.
 */
.dash-new {
    display: inline-block;
    margin-left: 0.4rem;
    padding: 0.1rem 0.4rem;
    border-radius: 999px;
    background: color-mix(in srgb, var(--liu-blue-dark) 12%, #fff);
    color: var(--liu-blue-dark);
    font-size: 0.58rem;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.09em;
    vertical-align: middle;
    animation: dash-new-in 0.4s ease-out;
}

@keyframes dash-new-in {
    from { opacity: 0; }
    to   { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
    .dash-new { animation: none; }
}


/* ---------- workload ---------- */

/* A row is a link into that reviewer's queue, so it's a button — reset to look like
   the row it replaced, and given a hover that reads as "this opens something". */
.dash-load {
    display: grid;
    grid-template-columns: minmax(0, 1.15fr) 1fr 2rem;
    align-items: center;
    gap: 0.6rem;
    width: 100%;
    /* Slimmer than it was. Two of these lists now share the right column with the feed, and a
       row built around a 1.9rem disc with 0.4rem of padding either side was spending most of
       its height on the disc. The name, the bar and the number are all shorter than that — so
       the row is sized to them and the mark shrinks to match. */
    padding: 0.2rem 0.4rem;
    margin: 0;
    background: none;
    border: 0;
    border-radius: var(--r-button);
    font: inherit;
    color: inherit;
    text-align: left;
    cursor: pointer;
}

.dash-load:hover { background: var(--surface-raised); }
.dash-load:hover .dash-load-name { color: var(--liu-blue-dark); }

.dash-load:focus-visible {
    outline: 2px solid var(--liu-blue-dark);
    outline-offset: -2px;
}

.dash-load-who {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    min-width: 0;
}

/* Fee-exempt, said in the same colour at a slightly different strength — 14% against 22%,
   with the ink left alone.
   Hue is spoken for twice over: colour inside a pill means case status, and a role avatar's
   hue means which role. A non-profit clinic is still a clinic, so a second hue would read as
   a different kind of thing rather than the same kind on different terms. Lightness is the
   one channel carrying no meaning yet.
   Deliberately faint. This card answers "which sites send us the most work" — billing isn't
   part of that question, and it is already stated in words on the sites grid and the site's
   own page. So the marker is the quietest thing that still exists: there when you compare
   two rows, ignorable when you aren't looking for it. The tooltip carries the word.
   Scoped to this card: .role-avatar--clinic is shared with clinic PEOPLE in the feeds and
   timelines, and none of this is about them. */
.dash-at--site .role-avatar--clinic {
    background: color-mix(in srgb, var(--role-clinic) 14%, #fff);
}

.dash-at--site .site-mark--exempt {
    background: color-mix(in srgb, var(--role-clinic) 22%, #fff);
}

/* The mark is an accent on a name here, not the row's subject, so it doesn't get to set the
   row's height. Everywhere else it identifies someone on its own and keeps its full size. */
.dash-load-who .role-avatar {
    width: 1.45rem;
    height: 1.45rem;
    font-size: 0.58rem;
}

.dash-load-name {
    font-size: 0.8rem;
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.dash-load-n {
    font-size: 0.78rem;
    font-weight: 700;
    color: var(--text-secondary);
    text-align: right;
}

.dash-bar {
    height: 6px;
    border-radius: 999px;
    background: var(--surface-sunken);
    overflow: hidden;
}

.dash-bar i {
    display: block;
    height: 100%;
    border-radius: 999px;
    background: var(--role-doctor);
}

/* The unassigned pile is a warning, not a person's queue — it reads amber so it
   can't be mistaken for someone pulling their weight. */
.dash-bar i.is-unassigned { background: var(--status-warning); }

.dash-load.is-unassigned { margin-top: 0.3rem; padding-top: 0.55rem; border-top: 1px solid var(--border-subtle); }

/* ---------- feed ---------- */

.dash-feed { display: flex; flex-direction: column; }

.dash-feed-row {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.42rem 0;
    border-bottom: 1px solid var(--border-subtle);
    font-size: 0.79rem;
}

.dash-feed-row:last-child { border-bottom: 0; }

/* .role-avatar is sized for a name badge; the feed wants it smaller and carrying a
   glyph rather than initials. */
.dash-feed-role {
    width: 1.5rem;
    height: 1.5rem;
    flex: 0 0 auto;
}

.dash-feed-role .rzi { font-size: 0.9rem; }

.dash-feed-text {
    flex: 1 1 auto;
    min-width: 0;
    color: var(--text-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.dash-feed-text b { color: var(--text-secondary); font-weight: 600; }

/* Razor drops the whitespace around an @if, so a line assembled out of conditional pieces
   came out as "editedLIU Clinicontact". Rather than sprinkle &nbsp; through the markup —
   invisible to whoever edits it next, and easy to lose — the row spaces its own parts.
   Inline-flex with a gap, so it still sits on one line and still truncates. */
.dash-feed-text--gapped {
    display: inline-flex;
    align-items: baseline;
    flex-wrap: nowrap;
    gap: 0 0.28rem;
}

/* A button rather than an anchor so it routes through Blazor like every other
   case link on the page, but it has to read as a link. */
.dash-feed-link {
    background: none;
    border: 0;
    padding: 0;
    font: inherit;
    font-weight: 600;
    color: var(--liu-blue-dark);
    cursor: pointer;
}

.dash-feed-link:hover { text-decoration: underline; }

.dash-feed-when {
    font-size: 0.7rem;
    color: var(--text-subtle);
    white-space: nowrap;
    flex: 0 0 auto;
}

/* ---------- narrow ---------- */

/* Below this the two columns stop fitting side by side, so the page gives up owning
   the viewport and scrolls normally rather than cramming three cards into a phone. */
@media (max-width: 1100px) {
    .dash-body { grid-template-columns: 1fr; }
}

@media (max-width: 820px) {
    /* Six stages side by side stop being readable long before they stop fitting;
       wrapping to two rows keeps each number legible. */
    .dash-pipe { flex-wrap: wrap; }
    .dash-stage { flex: 1 1 30%; }
}



/* A site's mark in the caseload list is a glyph, not initials — every site would otherwise
   be one or two letters of its own name, which is a worse label than the name beside it. */
.dash-load-who .role-avatar .rzi { font-size: 0.8rem; }

/* A qualifier on a feed line — which role someone landed in, which fields an edit moved.
   Quiet, because the sentence already said the thing that happened. */
.dash-feed-sub { color: var(--text-subtle); }



/* ===== What kind of thing the row is =====
   Each row wears the mark its own page leads with, so the list uses the app's existing
   vocabulary rather than inventing a third one: the species glyph in the species' ink for a
   case, the tinted rounded square for a site. Two marks that already mean these two things
   everywhere else.
 *
 * What it replaced: a disc tinted by CASE STAGE. Two problems, both about spending the loud
 * channel on the wrong variable. The stage is written in words on the same line, so the
 * colour was a second and weaker copy of it — and because the list is sorted by wait, not by
 * stage, an amber row sitting under a blue one read as an ordering bug. Meanwhile the thing
 * that genuinely differs, case versus site, was a clipboard and a building at 0.85rem, where
 * both are just a small dark shape.
 *
 * Both marks share a footprint AND a ground, so the titles line up down the column and the
 * column reads as one kind of thing. Every other mark in these cards sits on a ground — the
 * feed's avatars, the caseload's discs, the site's own square — so a bare glyph among them
 * looked like a mark that hadn't finished loading.
 *
 * Rounded square, not the circle the old stage disc used: in this app a circle means a PERSON
 * (that is what the avatars beside these very cards are), and neither a case nor a site is
 * one. Circles for people, rounded squares for things. */
.dash-mark-case,
.dash-mark-site {
    flex: 0 0 auto;
    display: inline-grid;
    place-items: center;
    width: 1.5rem;
    height: 1.5rem;
    border-radius: 7px;
}

/* A neutral ground with the species' ink on the glyph. The case hero tints its ground per
   species; repeating that here would run a column of pale blue, gold and lilac discs down the
   card — which is the exact look the stage tones were removed for, at the same weight. So the
   ground says "a mark sits here", the glyph shape and its ink say which species, and the only
   COLOURED ground left in the list is the site's. */
.dash-mark-case {
    background: var(--surface-sunken);
    font-size: 0.9rem;
    line-height: 1;
}

/* The site mark at row scale, keeping its fill: it is the one mark whose colour carries a
   distinction — "this row is a site, not a case" — and it appears on a handful of rows. */
.dash-mark-site.site-mark {
    width: 1.5rem;
    height: 1.5rem;
}

.dash-mark-site.site-mark .rzi { font-size: 0.9rem; }

/* Why the row is here, after the name. Quieter than the name, because the name is what you
   scan for and this is what you read once you have stopped on it. The weight difference is
   the whole separation — a dash between them was punctuation doing a job the colour already
   does, and it read as a hyphen in the name at a glance. */
.dash-reason {
    margin-left: 0.4rem;
    color: var(--text-subtle);
}
