/* Atlas Studio — the provider-portal surface, ported from Chris's demo
   (atlas_practitioner_recruiting-rob-exp.html, the ".side"/".s-*" block).
   Phase 5 #233: the demo is the reference for LOOK, our app for FUNCTION.

   ONE stylesheet, both access levels (Rob, 9 Sep). Stage 5 restyles the
   patient side onto these same rules rather than opening a second file.

   EVERYTHING IS PREFIXED pv-. The demo's own names collide with what is
   already loaded globally: portal.css owns .side (and app/globals.css pins
   it with !important), atlas.css owns .card, and .s-top/.s-card are too
   generic to leave bare in a global sheet. The prefix is the only deviation
   from the demo's CSS — values are its values.

   Tokens come from atlas.css :root, which the root layout loads first, so the
   sand, ink, serif and sans variables resolve here without being redeclared.
   The demo's own :root is the same palette with two extra pillar tints
   (--sienna-soft, --forest-tint) that atlas.css already defines too. */

/* ── Pillar accent ───────────────────────────────────────────────────────
   Chris's per-pillar studio pages each set ONE variable and let every piece of
   chrome read it — public/atlas-site/therapy/provider.html opens with
   `:root{ --accent:var(--steel); ... }` and the brand ring, active nav row,
   count pill, avatar and filled button are all var(--accent). Same idea here,
   scoped to the shell rather than :root because one app serves all four
   consoles.

   The mapping is atlas.css's, per brand guide v7, and it is what the data
   already carries: all 14 health provider rows are forest, all 22 therapy ones
   steel. The demo's `PILLAR_COLOR` has health and therapy the other way round,
   but that line lives once inside the EHR section, which is out of scope for
   this epic — do not copy it.

   Default is sienna: the shared clinician pages (patient chart, outcomes,
   settings) belong to no pillar and must not borrow one console's colour. */
/* --accent-wash is the accent at 12% on whatever it sits on, and it is NOT
   var(--*-tint). The tint tokens are lighter than the sidebar itself
   (--sienna-tint #f8f4f0 against a sand ground), so an active nav row painted
   with one disappears instead of standing out — hit once already this phase.
   Stated as rgba rather than color-mix so there is nothing to fall back from. */
.pvs-shell {
  --accent: var(--sienna);
  --accent-soft: var(--sienna-soft);
  --accent-wash: rgba(140, 82, 56, 0.12);
}
.pvs-shell[data-pillar="health"] {
  --accent: var(--forest);
  --accent-soft: var(--forest-soft);
  --accent-wash: rgba(44, 90, 74, 0.12);
}
.pvs-shell[data-pillar="therapy"] {
  --accent: var(--steel);
  --accent-soft: var(--steel-soft);
  --accent-wash: rgba(42, 80, 112, 0.12);
}
.pvs-shell[data-pillar="wellness"] {
  --accent: var(--sienna);
  --accent-soft: var(--sienna-soft);
  --accent-wash: rgba(140, 82, 56, 0.12);
}
.pvs-shell[data-pillar="living"] {
  --accent: var(--sand-pillar);
  --accent-soft: var(--sand-pillar-soft);
  --accent-wash: rgba(107, 94, 74, 0.12);
}
/* The pillar-less Atlas rail over the shared clinician screens (#233 stage 2,
   the 9/11 gap box). Not a fifth pillar — ink, so the active row reads as
   "selected" without claiming one of the four.

   It needs its own block rather than falling through to .pvs-shell above,
   whose default is SIENNA. That default is right for a console being QA'd
   before its pillar resolves, and wrong here: it painted the shared rail in
   WELLNESS's colour, so /clinician/patients looked like the wellness console. */
.pvs-shell[data-pillar="shared"] {
  --accent: var(--ink-700);
  --accent-soft: var(--ink-500);
  --accent-wash: rgba(60, 74, 86, 0.10);
}

/* How much vertical chrome sits above a page. The clinician screens were
   written against a 58px top bar and hardcoded `calc(100vh - 58px)` in six
   places; the new shell has a sidebar and no top bar, which left the inbox
   58px short and a dead band under it. One variable instead: 58px is still
   the default, so the top-bar frame and every /admin page are unchanged, and
   the sidebar shell zeroes it for what it wraps. */
:root { --console-chrome: 58px; }
.pvs-main { --console-chrome: 0px; }

/* The demo sets `font-weight:300` on its body, so its whole interface is Jost
   Light. Ours ran at 400 and read heavier everywhere — measured on the active
   nav row, every other property matched and only the weight differed.
   Anything that sets its own weight (the uppercase micro-labels at 500, bold
   names) is unaffected. */
.atlas-console, .pvs { font-weight: 300; }

/* ── Sidebar ─────────────────────────────────────────────────────────────
   Chris's aside: warm sand-50, ink text, a sienna bar down the active row.
   This replaced a navy block that was a placeholder — ProviderSidebar said
   so in its own comment ("the restyle onto Chris's palette is stage 5").
   Sticky rather than the demo's grid cell, because our console scrolls as
   one page and the demo's is a fixed-height frame. */
/* FIXED, not sticky. One mechanism for all three surfaces.
   
   It was sticky, which works right up until an ancestor has a constrained
   height — and the pillar apps load app/portal/_shared/pillar-app.css, which
   carries `html,body{height:100%}`. The provider console does not load that
   sheet, so the same rail stuck in one place and drifted in the other. Rob,
   19 Sep: "The left rail should always stick to the left side viewport. It
   does in the provider console, it does in the portal... and it should in the
   pillar itself."
   
   Fixed is what the portal rail already used (globals.css pins .portal .side),
   so this makes all three the same mechanism rather than two that agree by
   luck. It also cannot be broken by whatever a pillar's own stylesheets do to
   its ancestors, which is the failure that got us here. */
.pvs {
  position: fixed; left: 0; top: 0;
  flex: 0 0 240px; width: 240px; height: 100dvh;
  display: flex; flex-direction: column;
  padding: 28px 0; box-sizing: border-box;
  background: var(--sand-50); border-right: 1px solid var(--sand-300);
  font-family: var(--sans); overflow-y: auto;
}
.pvs-brand { display: flex; align-items: center; gap: 11px; padding: 0 26px 26px; }
.pvs-ring {
  width: 34px; height: 34px; flex: 0 0 34px;
  border: 1px solid var(--accent); border-radius: 50%;
  display: grid; place-items: center;
  font-family: var(--serif); font-size: 19px; color: var(--accent);
}
.pvs-brand-txt { display: flex; flex-direction: column; min-width: 0; }
.pvs-w { font-family: var(--display); font-size: 14px; letter-spacing: 0.26em; color: var(--ink-900); }
.pvs-s {
  font-family: var(--sans); font-size: 8.5px; letter-spacing: 0.24em;
  text-transform: uppercase; color: var(--accent); margin-top: 3px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pvs-lbl {
  font-family: var(--sans); font-size: 9px; letter-spacing: 0.22em;
  text-transform: uppercase; color: var(--ink-400); padding: 18px 26px 8px;
}
.pvs-nav { display: flex; flex-direction: column; }
.pvs-item {
  display: flex; align-items: center; gap: 13px; padding: 12px 26px;
  font-family: var(--sans); font-size: 13.5px; color: var(--ink-700);
  /* The demo's rows inherit line-height 1.75 from its body and stand 48.5px
     tall. Ours inherited a tighter one and came out 44px — 4.5px per row, 27px
     across the six Practice entries, which read as a cramped rail. */
  line-height: 1.75;
  text-decoration: none; border-left: 2px solid transparent; transition: all .2s;
}
.pvs-ni { font-size: 14px; width: 18px; flex: 0 0 18px; text-align: center; color: var(--ink-400); }
.pvs-item:hover { background: var(--sand-100); }
/* The demo's own value, not atlas.css's --sienna-tint. That token is
   #f8f4f0 — LIGHTER than the sand-50 rail it sits on, so the active row
   washed out instead of highlighting. The demo uses a 10% sienna wash, which
   darkens whatever is beneath it. */
.pvs-item.active { color: var(--ink-900); border-left-color: var(--accent); background: var(--accent-wash); }
.pvs-item.active .pvs-ni { color: var(--accent); }

/* Parked entries render greyed and do not navigate. Same rules as the live
   rows minus the hover, so they read as "later" rather than broken. */
.pvs-item.parked { color: var(--ink-300); cursor: default; }
.pvs-item.parked:hover { background: none; }
.pvs-soon {
  margin-left: auto; font-size: 7.5px; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--ink-400);
  border: 1px solid var(--sand-300); border-radius: 999px; padding: 2px 6px;
}

.pvs-me {
  margin-top: auto; padding: 18px 26px 0; border-top: 1px solid var(--sand-300);
  display: flex; align-items: center; gap: 12px;
  /* It is a link to account settings now, so it needs to look like one on
     hover — but not like a nav row, which would compete with the nav above. */
  text-decoration: none; color: inherit; padding-bottom: 12px;
  transition: background .18s;
}
/* Sign-out as its own line under the identity row, matching the member
   sidebar. The identity block reads as a label, so signing out used to be
   reachable only by guessing it was a link, or by finding it on Profile &
   brand. It is a visible control now without becoming a nav entry. */
.pvs-signout {
  display: block; padding: 0 26px 18px; text-align: center;
  font-family: var(--sans); font-size: 9.5px; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--ink-500);
  background: none; border: 0; width: 100%; cursor: pointer;
  transition: color .18s;
}
.pvs-signout:hover { color: var(--ink-900); }
a.pvs-me:hover { background: rgba(140,82,56,0.06); }
.pvs-av {
  width: 40px; height: 40px; flex: 0 0 40px; border-radius: 50%;
  background: var(--accent); color: #fff; display: grid; place-items: center;
  font-family: var(--serif); font-size: 17px;
}
.pvs-me-txt { min-width: 0; }
.pvs-nm { font-family: var(--serif); font-size: 15px; color: var(--ink-900);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pvs-rl { font-family: var(--sans); font-size: 10px; color: var(--ink-500);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* The sidebar replaces the top bar, so the console needs a row layout. */
/* Full-bleed on purpose. Chris's demo caps .studio at 1280px and centres it,
   rail and all, but the demo is a slide in a pitch deck at a fixed size -- on
   a real 1600px-plus monitor that leaves the whole console floating in the
   middle with dead background either side. Tried it, rejected it.

   What the pages DID need was one left edge. They each carried their own
   measure: /admin/accounts, /admin/patients and /admin/clinicians capped at
   1080 and centred, /admin/audit-log at 1240, /clinician/settings at 880
   left-aligned, /clinician at 1480 centred, the rest not at all -- four
   different left edges at a 1600px viewport, so moving from Settings to
   Accounts shifted the page 104px sideways. Those caps are gone and every
   page now starts at the shell's own 36px, matching .pv-view. */
/* THE RAIL'S COLUMN IS PAINTED BY THE SHELL, not by the rail.
   
   Rob, 19 Sep: "visually this rail should continue up and under the notice.
   The content can stay put but the rail should visually go to the top."
   
   The rail is `position: fixed; top: 0` and still does not start at the top,
   on desktop and on mobile alike. Something is giving it a containing block —
   atlas.css already documents that exact trap for a backdrop-filter elsewhere
   in the tree — and I could not find which element without devtools, having
   already guessed wrong at layout three times today.
   
   So this stops guessing and paints the stripe on the shell, which is a
   faux-column: the sand, the hairline and the page ground are one gradient on
   an element that definitely reaches the top. The rail draws its own
   background on top of it, so the two agree wherever they overlap and the
   column is continuous wherever they do not.
   
   Not a workaround for a bug I understand — a way of not needing to. If the
   containing block is ever found, this can stay or go and nothing moves. */
.pvs-shell {
  /* flow-root, not block: a block box with no top border or padding lets its
     first child's top margin collapse out through it, which would push the
     whole console down. Cheap insurance, and it changes nothing else.
     
     NOT the cause of the strip that was at the top of the window — that was
     .rail-bar rendering unstyled because I had deleted its rules. Kept because
     it is right, not because it fixed anything. */
  display: flow-root; min-height: 100dvh;
  background: linear-gradient(
    to right,
    var(--sand-50) 0 240px,
    var(--sand-300) 240px 241px,
    var(--sand-100) 241px
  );
}
/* The rail is out of flow now, so the page body carries its own offset rather
   than sitting beside it in a flex row. Same shape as .portal main.main. */
/* The page column. NOT a flex column: the notice is out of flow (see
   .pv-notice below), so nothing has to share the space with it.
   
   It was briefly flex, to stop a banner pushing a 100dvh child off the
   bottom. That fixed the overflow and left the thing Rob actually asked for
   undone — the screens still started BELOW the notice instead of running up
   behind it. Out of flow solves both at once. */
.pvs-main {
  /* Same reason as the shell: a page whose first block carries a top margin
     would push the column down rather than indent its own content. */
  display: flow-root;
  position: relative;
  margin-left: 240px; min-width: 0; background: var(--sand-100);
  min-height: 100dvh;
}
/* And belt: nothing at the top of a console page brings its own gap. */
.pvs-main > *:first-child { margin-top: 0; }

/* ── The rail on small screens: a drawer ─────────────────────────────────
   RESTORED. A previous edit of mine replaced a span of this file that ran
   from the .pvs-main block down to the page-header comment, and this whole
   section sat inside it — so .rail-bar lost `display: none` and rendered
   UNSTYLED at every width: a bare row at the top of the shell holding the
   hamburger's three bars. That is the thin dark strip across the top of the
   window, and it was my own delete rather than anything in the layout.
   
   The lesson is in how it was made: a replace anchored on two comments,
   without checking what lay between them.
   
   Off-canvas to the LEFT, the side the rail occupies on desktop. Toggled by
   components/RailToggle.tsx, which puts `rail-open` on <body>. */
.rail-bar { display: none; }
.rail-scrim { display: none; }

@media (max-width: 900px) {
  .pvs-main { margin-left: 0; min-height: 0; }

  /* The bar the content starts beneath. Sticky rather than fixed so it needs
     no matching body padding to drift out of step with. */
  .rail-bar {
    position: sticky; top: 0; z-index: 60;
    display: flex; align-items: center; gap: 12px;
    height: 52px; padding: 0 14px;
    background: var(--sand-50); border-bottom: 1px solid var(--sand-300);
  }
  .rail-bar-brand {
    display: flex; align-items: center; gap: 9px;
    font-family: var(--display); font-size: 12px; letter-spacing: 0.22em;
    text-transform: uppercase; color: var(--ink-900);
  }
  .rail-bar-ring {
    width: 26px; height: 26px; flex: 0 0 26px;
    border: 1px solid var(--accent); border-radius: 50%;
    display: grid; place-items: center;
    font-family: var(--serif); font-size: 15px; letter-spacing: 0; color: var(--accent);
  }

  .rail-burger {
    width: 38px; height: 38px; flex: 0 0 38px;
    display: grid; align-content: center; justify-items: stretch; gap: 5px;
    padding: 0 9px; background: none; border: 0; cursor: pointer;
  }
  .rail-burger span {
    display: block; height: 1.5px; background: var(--ink-900); border-radius: 2px;
    transition: transform 260ms cubic-bezier(.2,.8,.2,1), opacity 160ms;
  }
  /* Three bars become an X while the drawer is open — the control says what
     pressing it will do rather than what it did. */
  body.rail-open .rail-burger span:nth-child(1) { transform: translateY(6.5px) rotate(45deg); }
  body.rail-open .rail-burger span:nth-child(2) { opacity: 0; }
  body.rail-open .rail-burger span:nth-child(3) { transform: translateY(-6.5px) rotate(-45deg); }

  .pvs {
    /* Pinned at both ends, sized at neither: `height: 100dvh` measures the
       VISIBLE viewport, which on iOS is not the screen. Safe areas are
       PADDING so the sand runs edge to edge behind the status bar and home
       indicator while the content clears both. */
    position: fixed; top: 0; left: 0; bottom: 0; height: auto;
    padding-top: env(safe-area-inset-top, 0px);
    padding-bottom: env(safe-area-inset-bottom, 0px);
    width: min(300px, 84vw); z-index: 80;
    transform: translateX(-100%);
    transition: transform 320ms cubic-bezier(.2, .8, .2, 1);
    box-shadow: 0 0 60px -12px rgba(27, 42, 56, 0.45);
    border-right: 1px solid var(--sand-300);
    overflow-y: auto;
  }
  body.rail-open .pvs { transform: translateX(0); }
  .pvs { padding-left: 0; padding-right: 0; }
  .pvs .pvs-brand { padding-top: 24px; }

  .rail-scrim {
    display: block; position: fixed; inset: 0; z-index: 70;
    background: rgba(27, 42, 56, 0.5);
    animation: rail-fade 200ms ease;
  }
  @keyframes rail-fade { from { opacity: 0; } to { opacity: 1; } }

  /* No column to paint — the rail is a drawer over the page, not beside it. */
  .pvs-shell { background: var(--sand-100); }
}

/* A drawer open over the page must not let the page behind it scroll. */
body.rail-open { overflow: hidden; }
@media (min-width: 901px) { body.rail-open { overflow: auto; } }

/* ── Page header (the demo's .s-top) ─────────────────────────────────────
   Every console screen opens with the same bar: a small uppercase crumb
   saying where you are, a serif title, and the screen's own actions on the
   right. It replaces the eyebrow/h1/sub block each page used to inline. */
.pv-top {
  display: flex; align-items: center; justify-content: space-between; gap: 20px;
  flex-wrap: wrap; border-bottom: 1px solid var(--sand-300);
  padding: 24px 36px;
}
.pv-crumb {
  font-family: var(--sans); font-size: 10.5px; letter-spacing: 0.2em;
  text-transform: uppercase; color: var(--ink-500);
}
.pv-title {
  font-family: var(--serif); font-weight: 300; font-size: 27px;
  color: var(--ink-900); margin: 6px 0 0;
}
.pv-title em { font-style: italic; }
.pv-sub { font-family: var(--sans); font-size: 13px; color: var(--ink-500); margin: 6px 0 0; }
.pv-actions { display: flex; gap: 10px; flex-wrap: wrap; }

.pv-btn {
  font-family: var(--sans); font-size: 10.5px; letter-spacing: 0.14em;
  text-transform: uppercase; padding: 10px 16px;
  border: 1px solid var(--ink-900); border-radius: 999px;
  background: transparent; color: var(--ink-900);
  cursor: pointer; text-decoration: none; display: inline-block; transition: all .2s;
}
.pv-btn:hover { background: var(--ink-900); color: var(--sand-50); }
.pv-btn.filled { background: var(--accent); border-color: var(--accent); color: #fff; }
.pv-btn.filled:hover { background: var(--ink-900); border-color: var(--ink-900); }

/* ── Page body ─────────────────────────────────────────────────────────── */
.pv-view { padding: 28px 36px 48px; }
.pv-grid { display: grid; gap: 16px; align-items: start; }
/* Opt-in equal heights for a row where the cards are meant to line up — the
   demo's Content row does it with justify-content:center on the shorter card.
   Not the default: the dashboard's rows deliberately let cards end where their
   content ends. */
.pv-grid.pv-stretch { align-items: stretch; }
.pv-grid.pv-stretch > .pv-card { display: flex; flex-direction: column; justify-content: center; }
.pv-card { background: var(--sand-50); border: 1px solid var(--sand-300); border-radius: 16px; padding: 22px; }
.pv-card-h {
  font-family: var(--sans); font-size: 10.5px; letter-spacing: 0.2em;
  text-transform: uppercase; color: var(--ink-500); margin: 0 0 14px; font-weight: 500;
}
.pv-card-h em { font-style: normal; color: var(--accent); }
/* A card heading with something on its right (a "view all" link, a button). */
.pv-card-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 14px; }
.pv-card-head .pv-card-h { margin: 0; }
.pv-more {
  font-family: var(--sans); font-size: 10px; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--accent); text-decoration: none; white-space: nowrap;
}
.pv-more:hover { color: var(--ink-900); }

.pv-kpis { grid-template-columns: repeat(4, 1fr); }
.pv-kpi { background: var(--sand-50); border: 1px solid var(--sand-300); border-radius: 16px;
  padding: 20px 22px; display: block; text-decoration: none; color: inherit; }
a.pv-kpi { transition: background .2s, border-color .2s; }
a.pv-kpi:hover { background: var(--sand-100); border-color: var(--sand-400); }
.pv-kpi .lbl { font-family: var(--sans); font-size: 9.5px; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--ink-500); }
.pv-kpi .val { font-family: var(--serif); font-weight: 500; font-size: 38px;
  color: var(--ink-900); line-height: 1; margin: 11px 0 5px; }
.pv-kpi .cap { font-family: var(--sans); font-size: 11px; color: var(--ink-500); }
.pv-kpi .delta { font-family: var(--sans); font-size: 11px; color: var(--forest); }
.pv-kpi .delta.warm { color: var(--sienna); }

/* A list row inside a card — the demo's .today-row. Hairline between rows,
   none after the last, so a card of rows has no trailing rule. */
.pv-row { display: flex; align-items: center; gap: 14px; padding: 12px 0; border-bottom: 1px solid var(--sand-300); }
.pv-row:last-child { border-bottom: 0; }
/* The time column. 78px fits "8:00 AM CDT" and nothing longer, and FacilityTime
   appends "(9:00 PM your time)" for any reader outside Central — so the bracket
   ran straight out of the box and printed on top of the session title. Invisible
   from Chicago, which is why it stood this long.
   min-width keeps the column lined up when every row is short; the text wraps
   onto a second line rather than widening the column and squeezing the title. */
.pv-row .tm { font-family: var(--sans); font-size: 12px; color: var(--ink-900);
  font-weight: 500; min-width: 78px; max-width: 150px; flex-shrink: 0; line-height: 1.35; }
.pv-row .tm .ft-local { display: block; color: var(--ink-500); }
.pv-row .ti { flex: 1; font-family: var(--serif); font-size: 16px; color: var(--ink-900); min-width: 0; }
.pv-row .tsub { font-family: var(--sans); font-size: 12px; color: var(--ink-500); }
a.pv-row { text-decoration: none; }
a.pv-row:hover { background: var(--sand-100); }

/* Status pills. The demo carries one per meaning rather than a colour prop,
   so the palette can't drift per screen. */
.pv-pill { font-family: var(--sans); font-size: 8.5px; letter-spacing: 0.1em;
  text-transform: uppercase; padding: 4px 9px; border-radius: 999px; white-space: nowrap; }
.pv-pill.done { background: var(--forest-soft); color: var(--forest); }
.pv-pill.open { background: var(--sienna-soft); color: var(--sienna); }
.pv-pill.wait { background: var(--steel-soft); color: var(--steel); }
.pv-pill.live { background: #f3e0db; color: #b3402f; }
.pv-pill.due { background: rgba(184,145,42,0.18); color: var(--gold); }

/* Empty states. Every console screen has them and they were each inlined. */
.pv-empty { font-family: var(--sans); font-size: 13px; color: var(--ink-500);
  padding: 22px 0; text-align: center; }

/* Bar chart, the demo's `.reach-vis` — one bar per bucket, height as a
   percentage of the tallest. The baseline rule matters: a week with nobody in
   it draws a zero-height bar, and without an axis to sit on that reads as a
   missing bar rather than an empty one. */
.pv-bars { --pv-bars-h: 70px;
  display: flex; align-items: flex-end; gap: 6px; height: var(--pv-bars-h);
  /* Headroom for the hover label, which sits above whichever bar is under the
     pointer — without it the tallest bar's label lands on the card heading. */
  margin-top: 40px;
  border-bottom: 1px solid var(--sand-300); }
.pv-bars i { position: relative; flex: 1; background: var(--accent-soft);
  border-radius: 4px 4px 0 0; }
.pv-bars i.on { background: var(--accent); }
/* A bucket with nothing in it. Drawn as a sliver in the divider colour rather
   than omitted, so the axis still reads as eight weeks — an invisible bar
   looks like missing data, not like a quiet week. */
.pv-bars i.nil { height: 3px !important; background: var(--sand-300); }

/* Hover readout. Our own rather than the browser's `title`, for two reasons:
   a native tooltip needs a second of stillness and is drawn by the OS, so it
   can neither be styled to match nor verified from a test; and an empty week
   is a 3px sliver you cannot realistically point at. ::before is a transparent
   hit area spanning the bar's full column, so every week is hoverable
   including the empty ones. */
.pv-bars i::before { content: ""; position: absolute; left: 0; right: 0;
  bottom: 0; height: var(--pv-bars-h); }
.pv-bars i::after {
  content: attr(data-lbl); position: absolute; bottom: 100%; left: 50%;
  transform: translateX(-50%); margin-bottom: 8px; white-space: nowrap;
  font-family: var(--sans); font-size: 10px; letter-spacing: 0.06em;
  background: var(--ink-900); color: var(--sand-50);
  padding: 5px 9px; border-radius: 4px;
  opacity: 0; pointer-events: none; transition: opacity 120ms;
}
.pv-bars i:hover { z-index: 2; }
.pv-bars i:hover::after { opacity: 1; }
/* The end bars' labels would centre themselves off the edge of the card, so
   they anchor to their own side instead. */
.pv-bars i:first-child::after { left: 0; transform: none; }
.pv-bars i:last-child::after { left: auto; right: 0; transform: none; }
.pv-bars-cap { margin-top: 14px; font-family: var(--serif); font-size: 15px;
  color: var(--ink-700); }
.pv-bars-cap b { color: var(--accent); }

/* ── Tables ──────────────────────────────────────────────────────────────
   Scoped to .pv-table, not bare table/thead/td as the demo has it — this
   sheet is global and would otherwise restyle every table in the app. */
.pv-table { width: 100%; border-collapse: collapse; }
.pv-table thead th {
  font-family: var(--sans); font-size: 9.5px; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--ink-500); text-align: left;
  padding: 0 14px 13px; font-weight: 500; border-bottom: 1px solid var(--sand-300);
}
.pv-table tbody td {
  padding: 15px 14px; border-bottom: 1px solid var(--sand-300);
  font-family: var(--sans); font-size: 13px; color: var(--ink-700); vertical-align: middle;
}
.pv-table tbody tr:last-child td { border-bottom: 0; }
.pv-table tbody tr { transition: background .2s; }
.pv-table tbody tr:hover { background: var(--sand-100); }
.pv-table .nm { font-family: var(--serif); font-size: 15px; color: var(--ink-900); }
.pv-av {
  width: 34px; height: 34px; border-radius: 50%; display: inline-grid; place-items: center;
  font-family: var(--serif); font-size: 14px; color: #fff; background: var(--sand-pillar);
}
.pv-cell-name { display: flex; align-items: center; gap: 12px; }

@media (max-width: 900px) {
  .pv-top { padding: 20px 20px; }
  .pv-view { padding: 20px 20px 40px; }
  .pv-kpis { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 560px) {
  .pv-kpis { grid-template-columns: 1fr; }
}

/* ── Patient / member lists ──────────────────────────────────────────────
   The roster every console shows under Patients (Members, for the
   non-clinical pillars). These rules were copy-pasted in three places —
   app/clinician/patients/page.tsx, its Health twin, and simple-list-styles.ts
   — with two different .pl-row shapes between them. One copy now, with the
   rich grid as a `.pl-list.rich` modifier rather than a second definition of
   the same class.

   Demo look: a sand-50 row on the sand-100 page, hairline sand border, the
   name in serif. No lift-on-hover — the demo never moves a row, it tints it
   (its table rows go to sand-100 on hover), and the old translateY made the
   list twitch under the cursor. */
.pl-list { display: flex; flex-direction: column; gap: 8px; }
.pl-row {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 16px 20px; background: var(--sand-50);
  border: 1px solid var(--sand-300); border-radius: 11px;
  text-decoration: none; color: inherit; transition: background .2s, border-color .2s;
}
a.pl-row:hover { background: var(--sand-100); border-color: var(--sand-400); }
.pl-name { font-family: var(--serif); font-size: 16px; color: var(--ink-900); }
.pl-meta { font-family: var(--sans); font-size: 11.5px; color: var(--ink-500); margin-top: 3px; }
.pl-chev { color: var(--ink-400); font-size: 22px; line-height: 1; }

/* The rich variant: avatar, name, and five stat columns. Same surface, a
   grid instead of the simple row's flex. */
.pl-list.rich .pl-row {
  display: grid; grid-template-columns: 44px 1fr repeat(5, 88px) 16px;
  gap: 16px; align-items: center;
}
.pl-ava {
  width: 44px; height: 44px; border-radius: 50%; position: relative;
  background: var(--sand-pillar); color: #fff; display: grid; place-items: center;
  font-family: var(--serif); font-size: 17px;
}
.pl-ava-dot {
  position: absolute; top: 0; right: 0; width: 10px; height: 10px;
  border-radius: 50%; background: var(--sienna); border: 2px solid var(--sand-50);
}
.pl-row.flagged { border-left: 2px solid var(--sienna); }
.pl-stat-v { font-family: var(--serif); font-size: 22px; color: var(--ink-900); font-weight: 300; line-height: 1; }
.pl-stat-v.warn { color: var(--sienna); }
.pl-stat-k {
  margin-top: 4px; font-family: var(--sans); font-size: 9px; letter-spacing: 0.18em;
  text-transform: uppercase; color: var(--ink-500);
}

/* Search + filter controls above a list. */
.pl-controls { display: flex; gap: 16px; align-items: center; flex-wrap: wrap; margin-bottom: 20px; }
.pl-search {
  flex: 1 1 260px; padding: 10px 14px; font-family: var(--sans); font-size: 13px;
  color: var(--ink-900); background: var(--sand-50);
  border: 1px solid var(--sand-300); border-radius: 999px; outline: none;
}
.pl-search:focus { border-color: var(--accent); }
.pl-filters { display: flex; gap: 8px; flex-wrap: wrap; }
.pl-filter-btn {
  font-family: var(--sans); font-size: 10px; letter-spacing: 0.14em; text-transform: uppercase;
  padding: 8px 14px; border: 1px solid var(--sand-300); border-radius: 999px;
  background: transparent; color: var(--ink-500); cursor: pointer; transition: all .2s;
}
.pl-filter-btn:hover { border-color: var(--ink-400); color: var(--ink-900); }
.pl-filter-btn.active { background: var(--accent); border-color: var(--accent); color: #fff; }

@media (max-width: 820px) {
  /* Row 1: avatar + name. Row 2: all five stats in a single inline row
     (each an equal fifth) instead of a 2x2 stack. */
  .pl-list.rich .pl-row {
    grid-template-columns: repeat(5, 1fr);
    grid-template-rows: auto auto;
    column-gap: 8px; row-gap: 14px;
  }
  .pl-ava { grid-column: 1 / 2; grid-row: 1; }
  .pl-name-col { grid-column: 2 / -1; grid-row: 1; }
  .pl-stat { grid-row: 2; text-align: center; }
  .pl-chev { display: none; }
}

/* ── Upload zone, present but DEACTIVATED ────────────────────────────────
   The demo's `.upload-zone`, shipped the way Charts / Earnings / Services are:
   the shape of the portal is right, and nothing pretends to work. No pointer
   cursor and no hover state, because both would promise a click that does
   nothing — the wearables-panel failure mode the spec names. */
.pv-upload {
  border: 1.5px dashed var(--sand-400); border-radius: 14px; padding: 34px;
  text-align: center; background: var(--sand-50); opacity: 0.72;
}
.pv-upload .ic { font-size: 28px; color: var(--accent); }
.pv-upload .t {
  font-family: var(--serif); font-size: 18px; color: var(--ink-900);
  margin-top: 10px; display: flex; align-items: center;
  justify-content: center; gap: 10px;
}
.pv-upload .su { font-family: var(--sans); font-size: 12px; color: var(--ink-500); margin-top: 5px; }
/* Same badge the parked nav entries carry, so "not yet" reads the same way
   wherever it appears in the console. */
.pv-soon {
  font-family: var(--sans); font-size: 7.5px; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--ink-400);
  border: 1px solid var(--sand-300); border-radius: 999px; padding: 2px 6px;
}

/* "Library at a glance" — the demo's inline stat row, three numbers side by
   side inside one card rather than three separate tiles. */
.pv-glance { display: flex; gap: 28px; flex-wrap: wrap; }
.pv-glance .v { font-family: var(--serif); font-size: 32px; color: var(--ink-900); line-height: 1.1; }
.pv-glance .k { font-family: var(--sans); font-size: 11px; color: var(--ink-500); margin-top: 2px; }

/* ── Profile & brand ─────────────────────────────────────────────────────
   The demo's Profile view: form on the left, live member-card preview on the
   right. Its editor grid is 1fr 340px. */
/* TWO COLUMNS, back to the demo's shape (Rob, 18 Sep: "it's not even close").
   It was stacked — Paolo's 2026-09-10 call, on the reasoning that side by side
   squeezed the form and left the preview narrow. That reasoning held at the
   width the shell had then. It does not hold now: on a full-width console the
   form ran to 1700px, so "Display name" was a text box the width of a desk,
   and the preview below it stretched to the same width — a phone bezel drawn
   around a letterbox, which is the single thing a phone frame must never be.
   A member card is a CARD. It has a width, and past it the preview stops
   being a preview.

   360px for the preview column, from the demo's 340px plus our thicker bezel.
   Stacked again below 1100px, where two columns genuinely would squeeze. */
/* CAPPED, and this is the half the two-column fix missed. Splitting the row
   stopped the preview stretching, but the FORM column was still 1fr of
   whatever the console is — 1300px of text input on a wide monitor, so
   "Display name" remained a box the width of a desk and the page read as one
   long ribbon.

   ~1020 = a 620px form, the 32px gutter and the 360px preview. 620 is a
   comfortable measure for a one-line field and a five-row About box; the
   demo's own editor sits at roughly the same total. Left-aligned rather than
   centred, because the page heading above it is, and a body that centres
   under a heading that doesn't looks misaligned rather than composed.

   Capped HERE rather than on StudioView: the tables and the week calendar
   want every pixel the console has, and this is the one screen made of
   short fields. */
.pv-profile { display: grid; grid-template-columns: minmax(0, 1fr) 360px; gap: 32px; align-items: start; max-width: 1020px; }
/* The preview follows you down a long form. The About box alone can run past
   the fold, and a live preview you have to scroll back up to is not live. */
.pv-profile .pv-preview-wrap { position: sticky; top: 24px; }
@media (max-width: 1100px) {
  .pv-profile { grid-template-columns: minmax(0, 1fr); }
  .pv-profile .pv-preview-wrap { position: static; margin-top: 24px; max-width: 380px; }
}
.pv-field { display: block; margin-bottom: 14px; }
.pv-field > span {
  display: block; font-family: var(--sans); font-size: 9.5px;
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--ink-500);
  margin-bottom: 6px;
}
.pv-field .pv-input { width: 100%; }
.pv-field textarea.pv-input { resize: vertical; line-height: 1.55; }

.pv-tags { display: flex; flex-wrap: wrap; gap: 8px; }
.pv-tag {
  font-family: var(--sans); font-size: 11.5px; padding: 6px 12px;
  border-radius: 999px; border: 1px solid var(--sand-300);
  background: var(--sand-50); color: var(--ink-700); cursor: pointer;
  transition: all .18s;
}
.pv-tag:hover { border-color: var(--accent); }
.pv-tag.on { background: var(--accent); border-color: var(--accent); color: #fff; }
/* The primary modality — on the card, but Atlas's call, so it reads as fixed
   rather than as a toggle that refuses to move. */
.pv-tag:disabled { cursor: default; opacity: 0.85; }
.pv-tag:disabled:hover { border-color: var(--accent); }

.pv-preview-lbl {
  font-family: var(--sans); font-size: 9.5px; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--ink-400); margin-bottom: 10px;
  text-align: center;
}
.pv-preview {
  border: 1px solid var(--sand-300); border-radius: 18px; overflow: hidden;
  background: var(--sand-50); box-shadow: 0 24px 50px -30px rgba(27,42,56,.35);
}
/* The dark border is what separates the member's view from the console's own
   cards — without it this reads as one more panel of the editor rather than a
   picture of somewhere else.

   It lives in a 360px column now, so the bezel wraps something phone-shaped
   again. While this was full width the same nine pixels of ink drew a frame
   1700px across and 400 tall, which read as a fault rather than as a device. */
.pv-phone {
  border: 9px solid var(--ink-900); border-radius: 30px;
  box-shadow: 0 30px 60px -30px rgba(27,42,56,.45);
}
.pv-phone .pv-preview-top { border-radius: 21px 21px 0 0; height: 190px; }
.pv-phone .pv-preview-body { padding: 22px 26px 24px; }
.pv-preview-top {
  height: 150px; display: grid; place-items: center;
  font-family: var(--serif); font-size: 44px; color: #fff;
}
.pv-preview-body { padding: 18px 20px 20px; }
.pv-preview-body .mods {
  display: flex; flex-wrap: wrap; gap: 6px; font-family: var(--sans);
  font-size: 8.5px; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--ink-500); margin-bottom: 8px;
}
/* The separator between modalities, dimmer than the words it joins so the
   list reads as one phrase rather than as three things of equal weight. */
.pv-preview-body .mods i { font-style: normal; opacity: 0.5; margin-right: 6px; }
.pv-preview-body .nm { font-family: var(--serif); font-size: 23px; color: var(--ink-900); }
.pv-preview-body .cred { font-family: var(--sans); font-size: 11.5px; color: var(--ink-500); margin-top: 3px; }
.pv-preview-body .tag {
  font-family: var(--serif); font-style: italic; font-size: 15px;
  color: var(--ink-700); line-height: 1.5; margin: 12px 0 0;
}
/* Three equal cells with rules between them — the member profile's own
   .pf-stats, so the preview is the card, not an impression of it. */
.pv-preview-body .stats {
  display: flex; margin-top: 16px; padding-top: 14px;
  border-top: 1px solid var(--sand-300); text-align: center;
}
.pv-preview-body .stats > div { flex: 1; border-right: 1px solid var(--sand-300); }
.pv-preview-body .stats > div:last-child { border-right: 0; }
.pv-preview-body .stats b { display: block; font-family: var(--serif); font-size: 19px; font-weight: 400; color: var(--ink-900); line-height: 1.3; }
.pv-preview-body .stats span { font-family: var(--sans); font-size: 8.5px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-500); }



/* Member reflections — the demo's serif italic pull-quote with its attribution
   line, and a hairline between entries rather than a card each. */
.pv-refl {
  font-family: var(--serif); font-style: italic; font-size: 16px;
  color: var(--ink-700); line-height: 1.5; margin: 0;
}
.pv-refl-by { font-family: var(--sans); font-size: 11px; color: var(--ink-500); margin-top: 8px; }
.pv-refl-rule { height: 1px; background: var(--sand-300); margin: 16px 0; }

/* Vertical rhythm between stacked grids, the demo's 16px. */
.pv-mt { margin-top: 16px; }

/* ── Nav count badge ─────────────────────────────────────────────────────
   The demo's `.spill` — a small filled pill on a nav entry carrying the
   number waiting behind it ("Members 6", "Assessments 2"). Only rendered
   when there IS something waiting; a zero badge is noise. */
.pvs-spill {
  margin-left: auto; font-family: var(--sans); font-size: 10px;
  background: var(--accent); color: #fff; border-radius: 999px; padding: 2px 7px;
  line-height: 1.5;
}
.pvs-item.parked .pvs-spill { background: var(--sand-400); }

/* ── Week calendar ───────────────────────────────────────────────────────
   The demo's .cal, for the Schedule tab. Ours was already this shape — a
   time gutter, day columns, an event block per cell — so these are its
   values, not a new layout. Seven day columns rather than the demo's six:
   the demo's week is Mon–Sat, ours runs the full week. */
/* Week nav in the calendar card's heading row, and the sentence that closes
   the card the way the demo's does. */
.pv-weeknav { display: inline-flex; align-items: center; gap: 8px; }
.pv-cal-cap { font-family: var(--sans); font-size: 12.5px; color: var(--ink-500);
  margin: 16px 0 0; }

.pv-cal-wrap { border: 1px solid var(--sand-300); border-radius: 14px; overflow: hidden; background: var(--sand-50); }
.pv-cal-scroll { overflow-x: auto; }

/* NO BOOKABLE HOURS YET — the week is dimmed and the one useful action sits in
   the middle of it.

   The grid stays RENDERED rather than being replaced by an empty state,
   because it is not empty: blocked time and appointments with people who are
   not Atlas patients both live on it before any availability exists, and
   swapping it for a placeholder would hide a provider's real week behind a
   set-up prompt. Dimmed says "this cannot be booked into", which is the true
   statement; "there is nothing here" is not.

   position: relative on the wrap and the overlay pinned to it, so the prompt
   sits over the grid without the grid having to make room — the wrap already
   clips with overflow: hidden, so nothing escapes the rounded corners. */
.pv-cal-wrap { position: relative; }
.pv-cal-wrap.is-unset .pv-cal-scroll {
  /* Legible, not erased. Enough contrast loss to read as inactive while the
     shape of the week stays visible behind the prompt. */
  opacity: 0.38;
  filter: saturate(0.6);
  /* Not clickable while it cannot be booked into: clicking an empty cell
     normally opens the appointment form prefilled with that hour, and offering
     that from behind a scrim is a second, hidden way past the prompt. */
  pointer-events: none;
  user-select: none;
}
.pv-cal-unset {
  position: absolute; inset: 0; z-index: 2;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 12px; padding: 24px; text-align: center;
  /* A wash rather than a solid fill, so the dimmed grid still reads through
     it as the thing being talked about. */
  background: linear-gradient(rgba(244, 241, 234, 0.72), rgba(244, 241, 234, 0.72));
}
.pv-cal-unset p {
  margin: 0; max-width: 34ch;
  font-family: var(--serif); font-size: 15.5px; line-height: 1.5; color: var(--ink-900);
}
/* The grid is 900px wide and scrolls on a phone; the prompt must not. It is
   pinned to the WRAP, which is the viewport-width element, so it stays centred
   on screen rather than centred on a grid that is mostly off to the right. */
@media (max-width: 760px) {
  .pv-cal-unset { padding: 18px; }
  .pv-cal-unset p { font-size: 14.5px; }
}
.pv-cal { display: grid; grid-template-columns: 64px repeat(7, minmax(120px, 1fr)); min-width: 900px; }
.pv-cal .ch {
  padding: 13px 6px; text-align: center; font-family: var(--sans); font-size: 10.5px;
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-500);
  background: var(--sand-100);
  border-bottom: 1px solid var(--sand-300); border-left: 1px solid var(--sand-300);
}
.pv-cal .ch:first-child { border-left: 0; }
.pv-cal .ch b { display: block; font-family: var(--serif); font-size: 17px; color: var(--ink-900); margin-top: 3px; font-weight: 400; }
.pv-cal .hr {
  padding: 13px 6px; font-family: var(--sans); font-size: 10.5px; color: var(--ink-400);
  text-align: right; border-bottom: 1px solid var(--sand-300);
}
.pv-cal .cell {
  border-left: 1px solid var(--sand-300); border-bottom: 1px solid var(--sand-300);
  padding: 4px; min-height: 52px;
}
/* An hour outside the provider's published availability. Quiet grey, no
   pattern: most of a week is outside hours, so this is the background the
   working day sits on rather than a thing to notice. The point is that 9am
   and 7am stop looking identical.

   Still clickable — a provider booking into their own evening is a real thing
   to do, and only patients are held to published hours. This says "not
   normally", not "not allowed". */
.pv-cal .cell.outside { background-color: rgba(27, 42, 56, 0.035); }

/* "Earlier" / "Later" — the rest of the day, one click away at each end.
   Spans every column so it reads as a seam in the grid rather than a button
   that happens to sit near it, and sits exactly where the hidden hours would
   appear. Short and quiet by design: this is a way to reach 8pm, not an
   invitation to look at 8pm all day. */
.pv-cal .pv-cal-expand {
  grid-column: 1 / -1;
  display: block; width: 100%;
  padding: 7px 10px;
  font-family: var(--sans); font-size: 10.5px; letter-spacing: 0.06em;
  color: var(--ink-400); text-align: center;
  background: var(--sand-100);
  border: 0; border-bottom: 1px solid var(--sand-300);
  cursor: pointer;
}
.pv-cal .pv-cal-expand:hover { color: var(--ink-700, #444); background: var(--sand-200, #efeade); }

/* An hour the provider has blocked — diagonal hatching over a faded alert
   wash. Stripes are the convention the field already uses for unavailable
   time, so this reads without a legend even though there is one.

   The hatch is drawn with a repeating gradient rather than an image: one
   declaration, scales at any zoom, and no request.

   45deg and low contrast on purpose. It has to be unmistakable at a glance
   across a whole week and quiet enough to sit under an appointment chip that
   lands in the same cell — a blocked hour that already has something in it is
   a real state, not an impossible one. */
.pv-cal .cell.blocked {
  background-color: var(--alert-soft);
  background-image: repeating-linear-gradient(
    45deg,
    transparent 0 6px,
    rgba(179, 64, 47, 0.14) 6px 12px
  );
  cursor: default;
}

.pv-cal .ev {
  border-radius: 7px; padding: 6px 8px; font-family: var(--sans); font-size: 10px;
  line-height: 1.3; margin-bottom: 4px; cursor: pointer;
  background: var(--steel-soft); color: #234b66; border-left: 2px solid var(--steel);
}
.pv-cal .ev:last-child { margin-bottom: 0; }
.pv-cal .ev b { display: block; font-weight: 600; }
/* NEW SINCE THIS PROVIDER LAST OPENED THE TAB — the thing the rail's Schedule
   pill is counting. Without it the pill says "3" and the calendar looks
   identical, so they clear the badge and then have to hunt, possibly across
   weeks.
   
   A dot beside the name rather than a fourth chip colour. The three colours
   here already MEAN something — a one-to-one, a programme session, another
   pillar's block — and recolouring a chip for "recent" would overwrite what it
   is with when it arrived. The dot sits on top of all three and says only the
   one thing. It disappears on the next visit, because by then it isn't news. */
.pv-cal .ev-new-dot {
  display: inline-block; width: 5px; height: 5px; border-radius: 50%;
  background: var(--sienna, #8c5238); margin-left: 5px; vertical-align: middle;
}
.pv-cal .ev.ev-new { box-shadow: inset 0 0 0 1px var(--sienna, #8c5238); }
/* A programme session, as against a one-to-one. The demo's "live class". */
.pv-cal .ev.live { background: #f3e0db; color: #9a3526; border-left-color: #b3402f; }
/* ANOTHER PILLAR'S BOOKING (#253). The block is present because the slot is
   genuinely gone, and unreadable because the record is not this console's.
   Sand rather than a fourth accent: the two colours above MEAN something —
   a one-to-one and a programme session — and a third hue would read as a third
   kind of appointment rather than as the absence of information. Dashed left
   edge and no cursor say the same thing again for anyone who cannot separate
   the tones. */
.pv-cal .ev.busy {
  background: var(--sand-100); color: var(--ink-500);
  border-left: 2px dashed var(--sand-400); cursor: default;
}
.pv-cal .ev.busy b { color: var(--ink-500); font-weight: 500; }

.pv-legend .sw .sw-busy { background: var(--sand-100); border: 1px dashed var(--sand-400); }
/* The same hatch as the grid, shrunk to 12px — the stripe pitch is halved so a
   swatch that small still reads as stripes rather than as a smudge. */
.pv-legend .sw .sw-outside { background: rgba(27, 42, 56, 0.06); border: 1px solid var(--sand-300); }
.pv-legend .sw .sw-blocked {
  background-color: var(--alert-soft);
  background-image: repeating-linear-gradient(
    45deg,
    transparent 0 3px,
    rgba(179, 64, 47, 0.22) 3px 6px
  );
  border: 1px solid var(--alert-edge);
}
.pv-legend { display: flex; gap: 20px; margin-top: 14px; font-family: var(--sans);
  font-size: 11px; color: var(--ink-500); flex-wrap: wrap; }
.pv-legend .sw { display: flex; align-items: center; gap: 7px; }
.pv-legend .sw i { width: 12px; height: 12px; border-radius: 3px; display: inline-block; }

/* ── Extras the demo's roster and dashboard use ──────────────────────── */
/* A card whose content runs to its own edges — a table, mainly. */
.pv-card.flush { padding: 0; overflow: hidden; }
.pv-pill.inperson { background: var(--steel-soft); color: var(--steel); }
.pv-card-h .r {
  float: right; font-family: var(--sans); font-size: 9px; letter-spacing: 0.1em;
  text-transform: none; color: var(--ink-400);
}

/* Form controls, so a native select/time input stops looking like a browser
   default next to the studio's own surfaces. */
.pv-input {
  padding: 9px 13px; border: 1px solid var(--sand-300); border-radius: 11px;
  background: var(--sand-50); color: var(--ink-900);
  font-family: var(--sans); font-size: 13px; outline: none;
}
.pv-input:focus { border-color: var(--accent); }
/* Locked, not broken. A disabled field here is the RESTING state of Profile &
   brand, not an error, so it keeps full contrast on the text — greying the
   value out would make a profile that is simply not being edited look
   unavailable. Only the frame recedes. */
.pv-input:disabled {
  background: transparent; border-color: var(--sand-200, #efeade);
  color: var(--ink-900); cursor: default; opacity: 1;
  -webkit-text-fill-color: var(--ink-900);
}
.pv-btn:disabled { opacity: 0.45; cursor: default; }
.pv-btn:disabled:hover { background: transparent; color: var(--ink-900); }

/* Roster avatar tones — the demo's flag-sienna/forest/steel/sand. */
.pv-av.t-sienna { background: var(--sienna); }
.pv-av.t-forest { background: var(--forest); }
.pv-av.t-steel  { background: var(--steel); }
.pv-av.t-sand   { background: var(--sand-pillar); }

/* A member's name as a link into their page. Underlines on hover only, so a
   roster reads as names rather than a column of blue. Still used by the
   therapy/wellness cell layouts that are not whole-row targets. */
.pv-namelink { color: inherit; text-decoration: none; }
.pv-namelink:hover { color: var(--accent); text-decoration: underline; }

/* WHOLE-ROW TARGETS IN A TABLE ROSTER.
   Health's roster is a list of cards and each card IS the link — you click
   anywhere on it and a chevron says so. The table rosters were not: only the
   name was clickable, you had to find it with the pointer, and it underlined
   on hover, so the same action looked like two different things depending on
   which pillar you were in.
   A <tr> cannot be wrapped in an <a>, so the name's link stretches over the
   row instead: the row is the positioning context and the link's ::after
   covers it. Everything else in the row still paints above that layer, so
   text stays selectable and the chevron stays visible; nothing else in these
   rows is interactive, so nothing is swallowed.
   `.opens` is what carries the affordance. A row without it — a follower, whom
   the chart gate would refuse — keeps the plain cursor and no hover, because
   pretending it is clickable is worse than it plainly not being. */
.pv-trow.opens { cursor: pointer; }
.pv-trow.opens:hover { background: var(--sand-100); }
.pv-trow-link { color: inherit; text-decoration: none; position: static; }
.pv-trow.opens:hover .pv-trow-link { color: var(--accent); }
/* The positioning context is the ROW, not the cell — anchoring to the cell
   would stretch the link over the name column only, which is the bug this is
   fixing. position: relative on a <tr> is honoured by every browser we
   support; the mobile fallback below stops it mattering where it is not. */
.pv-trow.opens { position: relative; }
.pv-trow-link::after { content: ""; position: absolute; inset: 0; }
/* The chevron sits in its own column so it lands on the row's right edge
   whatever the columns before it are doing. Same glyph and colour as
   .pl-chev, a size down — a table row is shorter than a card. */
.pv-trow-chev {
  width: 1px; padding-right: 18px; text-align: right;
  color: var(--ink-400); font-size: 18px; line-height: 1;
}
@media (max-width: 900px) { .pv-trow-chev { display: none; } }

/* A native <select> keeps the OS's own box and arrow, which reads as a browser
   default sitting on a designed page. Strip the chrome and draw the caret. */
select.pv-input {
  appearance: none; -webkit-appearance: none;
  padding-right: 34px; cursor: pointer;
  background-image: linear-gradient(45deg, transparent 50%, var(--ink-500) 50%),
                    linear-gradient(135deg, var(--ink-500) 50%, transparent 50%);
  background-position: calc(100% - 18px) center, calc(100% - 13px) center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}
select.pv-input:disabled { opacity: .55; cursor: default; }

/* The right-hand detail on a session row — the demo's "Room 1" / "48 joined". */
.pv-rowend { font-family: var(--sans); font-size: 11px; color: var(--ink-500);
  width: 78px; text-align: right; flex-shrink: 0; }

/* Bare form controls on the provider surface. Only ones with no class of
   their own: several inputs and textareas set a border and a radius inline
   but no background, so they rendered the browser's white on a sand page.
   An inline background still wins, and anything with a class keeps its own
   rules — this only fills the gap. */
.atlas-console input:not([class]):not([type="checkbox"]):not([type="radio"]),
.atlas-console textarea:not([class]),
.atlas-console select:not([class]) {
  background: var(--sand-50);
  color: var(--ink-900);
}

/* Same for a bare <button>. Several were rendering the OS default grey slab
   next to designed controls. Anything that sets its own background inline —
   the text-only Edit/Delete actions, the filled pills — keeps it, because an
   inline style wins over this. */
.atlas-console button:not([class]) {
  font-family: var(--sans);
  font-size: 10.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 9px 16px;
  border: 1px solid var(--ink-900);
  border-radius: 999px;
  background: transparent;
  color: var(--ink-900);
  cursor: pointer;
  transition: background .2s, color .2s;
}
.atlas-console button:not([class]):hover:not(:disabled) {
  background: var(--ink-900);
  color: var(--sand-50);
}
.atlas-console button:not([class]):disabled { opacity: .45; cursor: default; }

/* ═══ Assessments — queue beside the intake ══════════════════════════════
   Chris's clinician screen: the list of who is waiting on the left, the one
   you picked on the right. Replaces an accordion of every intake stacked down
   the page, where opening the third meant scrolling past the first two.
   ------------------------------------------------------------------- */
.as-split { display: grid; grid-template-columns: 268px minmax(0, 1fr); gap: 16px; align-items: start; }
/* Under 900 the two columns become one and the queue moves on top, which is
   also where the provider's thumb is on a tablet. */
@media (max-width: 900px) { .as-split { grid-template-columns: minmax(0, 1fr); } }

/* The queue is a card of its own, like the intake beside it — two panels on
   the page rather than a floating list next to a boxed one. */
.as-queue { position: sticky; top: 12px; border: 1px solid var(--sand-300); border-radius: 10px; background: var(--sand-50); padding: 14px 4px 6px; }
@media (max-width: 900px) { .as-queue { position: static; } }
.as-queue h5 { font-family: var(--sans); font-size: 11px; letter-spacing: .16em; text-transform: uppercase; color: var(--ink-500); margin: 0 0 8px; padding: 0 12px; }
/* Rows are separated by a hairline rather than each carrying its own box —
   nested borders read as a list of buttons, not as one list. */
.as-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; width: 100%; text-align: left; padding: 11px 12px; border: 0; border-top: 1px solid var(--sand-200, #e0d9cf); background: none; cursor: pointer; }
.as-queue h5 + .as-row { border-top: 0; }
.as-row:hover { background: var(--sand-100); }
/* The one being read is marked by a filled ground and an accent edge down its
   leading side, so it stands out without competing with the intake. */
.as-row.on { background: var(--sand-100); box-shadow: inset 3px 0 0 var(--forest); }
.as-row-name { display: block; font-family: var(--serif); font-size: 15px; color: var(--ink-900); line-height: 1.25; }
.as-row-when { display: block; font-family: var(--sans); font-size: 11px; color: var(--ink-500); margin-top: 2px; }
.as-chip { flex: 0 0 auto; font-family: var(--sans); font-size: 10px; letter-spacing: .06em; padding: 3px 8px; border-radius: 999px; white-space: nowrap; }
.as-chip.pending { background: rgba(184,145,42,0.14); color: #8a6d1f; }
.as-chip.done { background: rgba(44,90,74,0.12); color: var(--forest); }
.as-empty { font-family: var(--sans); font-size: 13px; color: var(--ink-500); padding: 10px 2px; }

/* The intake scrolls INSIDE its own panel, not down the page. A sixteen-marker
   lab panel plus twenty screens of answers is a very long document, and with
   the page scrolling instead, the queue scrolled away with it and the Approve
   button sat a thousand pixels below the fold. */
/* Same 10px corner as the queue beside it. cardSurface hard-codes a 4px
   radius inline, and an inline style beats a class, so the radius is set here
   AND the inline one is dropped at the call site — two panels on one screen
   with different corners is the kind of mismatch you see before you can name
   it. overflow:hidden so the scrolling contents are clipped by the corner
   rather than squaring it off again. */
.as-detail { display: flex; flex-direction: column; max-height: calc(100vh - 150px); border-radius: 10px; overflow: hidden; }
.as-detail-scroll { overflow-y: auto; padding: 22px 24px; }
@media (max-width: 900px) { .as-detail { max-height: none; } .as-detail-scroll { overflow-y: visible; } }

/* The intake itself. */
.as-head { display: flex; align-items: flex-start; gap: 14px; padding-bottom: 16px; border-bottom: 1px solid var(--sand-300); margin-bottom: 18px; }
.as-av { flex: 0 0 auto; width: 44px; height: 44px; border-radius: 50%; background: var(--forest); color: var(--sand-50); display: flex; align-items: center; justify-content: center; font-family: var(--sans); font-size: 13px; letter-spacing: .06em; }
.as-kick { font-family: var(--sans); font-size: 10px; letter-spacing: .18em; text-transform: uppercase; color: var(--ink-500); margin-bottom: 3px; }
.as-name { font-family: var(--serif); font-weight: 400; font-size: 26px; line-height: 1.12; color: var(--ink-900); margin: 0; }
.as-meta { font-family: var(--sans); font-size: 12px; color: var(--ink-500); margin-top: 5px; line-height: 1.45; }

.as-block { margin-bottom: 22px; }
.as-block h5 { font-family: var(--sans); font-size: 11px; letter-spacing: .16em; text-transform: uppercase; color: var(--ink-500); margin: 0 0 10px; }
.as-sec { border-top: 1px solid var(--sand-300); padding-top: 13px; margin-bottom: 14px; }
.as-sec:first-of-type { border-top: 0; padding-top: 0; }
.as-sec-kick { font-family: var(--sans); font-size: 10px; letter-spacing: .16em; text-transform: uppercase; color: var(--ink-500); margin-bottom: 2px; }
.as-sec-title { font-family: var(--serif); font-size: 16px; color: var(--ink-900); margin-bottom: 9px; }
/* Label left, answer right — the provider's eye runs down what was said, not
   down the prompts they already know. */
.as-line { display: flex; align-items: baseline; gap: 18px; padding: 4px 0; }
.as-line-k { flex: 0 0 auto; font-family: var(--sans); font-size: 13px; color: var(--ink-500); }
.as-line-v { flex: 1 1 auto; text-align: right; font-family: var(--sans); font-size: 13px; color: var(--ink-900); line-height: 1.45; white-space: pre-wrap; }

/* ── The Schedule tab's editing panel ──────────────────────────────────── */
/*
   A sheet beside the calendar rather than a dialog over it. Three forms used
   to stack above the grid as their own cards, each with its own close in its
   own place and no rule about how many could be open. This is one surface with
   one close, and the calendar stays readable underneath.

   The scrim is deliberately faint. A modal's scrim says "deal with me first";
   this one only says "the panel is where you are", and the week behind it is
   the context the form is being filled in against.
*/
.pv-sheet-scrim {
  position: fixed; inset: 0; z-index: 90;
  background: rgba(27, 42, 56, 0.18);
  animation: pv-sheet-fade 160ms ease;
}

.pv-sheet {
  position: fixed; top: 0; right: 0; bottom: 0; z-index: 91;
  width: min(460px, 100vw);
  display: flex; flex-direction: column;
  background: var(--sand-50, #f5f1e7);
  border-left: 1px solid var(--sand-300);
  box-shadow: -18px 0 46px rgba(27, 42, 56, 0.13);
  animation: pv-sheet-in 200ms cubic-bezier(0.22, 0.8, 0.3, 1);
}

/* Full width on a phone, and from the bottom — a 460px panel on a 390px screen
   is a modal wearing a panel's clothes. */
@media (max-width: 560px) {
  .pv-sheet {
    top: auto; left: 0; width: 100vw; max-height: 88vh;
    border-left: 0; border-top: 1px solid var(--sand-300); border-radius: 12px 12px 0 0;
    animation: pv-sheet-up 220ms cubic-bezier(0.22, 0.8, 0.3, 1);
  }
}

.pv-sheet-head {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 16px 18px; border-bottom: 1px solid var(--sand-300); flex: none;
}
.pv-sheet-tabs { display: inline-flex; gap: 6px; flex-wrap: wrap; }
/* Stands in for the tab bar when the panel is READING rather than offering a
   choice. Sized to the tabs it replaces so the header doesn't change height. */
.pv-sheet-title {
  font-family: var(--sans); font-size: 10.5px; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--ink-500); padding: 7px 0;
}
.pv-sheet-tab {
  font-family: var(--sans); font-size: 10.5px; letter-spacing: 0.12em;
  text-transform: uppercase; cursor: pointer;
  padding: 7px 13px; border-radius: 999px;
  border: 1px solid var(--sand-300); background: transparent; color: var(--ink-500);
  transition: all .18s;
}
.pv-sheet-tab:hover { border-color: var(--ink-900); color: var(--ink-900); }
.pv-sheet-tab.on { background: var(--ink-900); border-color: var(--ink-900); color: var(--sand-50); }

.pv-sheet-x {
  font-size: 15px; line-height: 1; cursor: pointer; flex: none;
  width: 30px; height: 30px; border-radius: 999px;
  border: 1px solid transparent; background: transparent; color: var(--ink-500);
  transition: all .18s;
}
.pv-sheet-x:hover { border-color: var(--sand-400); color: var(--ink-900); }

/* Scrolls on its own so a long form never pushes the close button off screen —
   the close has to stay reachable from anywhere in the form. */
.pv-sheet-body { padding: 18px; overflow-y: auto; flex: 1 1 auto; }

/* Inside the panel a card is the panel, so the chrome comes off: no second
   border, no second background, no margin fighting the body's padding. */
.pv-sheet-body .pv-card {
  background: transparent; border: 0; padding: 0; margin: 0 0 18px; box-shadow: none;
}
.pv-sheet-body .pv-card:last-child { margin-bottom: 0; }

@keyframes pv-sheet-fade { from { opacity: 0 } to { opacity: 1 } }
@keyframes pv-sheet-in { from { transform: translateX(28px); opacity: 0 } to { transform: none; opacity: 1 } }
@keyframes pv-sheet-up { from { transform: translateY(28px); opacity: 0 } to { transform: none; opacity: 1 } }

/* An appointment with somebody who is not an Atlas patient — a provider's
   existing week, brought across. Drawn as a chip because a person is coming;
   muted because there is no record behind it to open. */
.pv-cal .ev.ext {
  background: var(--sand-100); border-left: 2px solid var(--ink-500);
  color: var(--ink-900); cursor: default;
}

/* The member message pane used to be defined here, in its own invented
   vocabulary (msg-*), next to a real inbox that already had one. Deleted —
   see components/messages/styles.ts, which both sides now render. */

/* The unfinished-intake marker: "!", not a count. Sized as a round badge
   rather than a pill, since it holds one narrow character. */
.pvs-dot {
  min-width: 18px; padding: 2px 0; text-align: center;
  font-size: 11px; line-height: 1.3; font-weight: 500;
}

/* ── The Atlas home rail ─────────────────────────────────────────────────
   One sidebar convention, not two. /portal carried its own rail from
   portal.css — different type, different spacing, different active state —
   while the provider console and the pillar apps shared this sheet's. Moving
   between them read as moving between two products.

   The portal's aside keeps its `side` class (globals.css pins it FIXED to the
   viewport with !important, and main.main's left offset is measured from
   that), and gains `pvs` for the look. So: same rail, portal's positioning.
   Specificity has to beat `.portal .side` (0,2,0), hence the doubled-up
   selectors here rather than plain `.pvs`. */
.portal .side.pvs {
  padding: 28px 0;
  background: var(--sand-50);
  border-right: 1px solid var(--sand-300);
  font-family: var(--sans); font-weight: 300;
  display: flex; flex-direction: column;
}
.portal .side.pvs .side-nav { gap: 0; }
.portal .side.pvs .pvs-item {
  display: flex; align-items: center; gap: 13px;
  padding: 12px 26px; border-left: 2px solid transparent;
  font-size: 13.5px; color: var(--ink-700); text-decoration: none;
  border-radius: 0; background: none;
}
.portal .side.pvs .pvs-item:hover { background: var(--sand-100); color: var(--ink-900); }
.portal .side.pvs .pvs-item.active {
  color: var(--ink-900); background: var(--accent-wash);
  border-left-color: var(--accent);
}
/* The old rail marked the active row with a dot pseudo-element. The bar down
   the left edge is this sheet's marker; two markers on one row is one too
   many. */
.portal .side.pvs .pvs-item.active::before { content: none; }
.portal .side.pvs .pvs-lbl {
  font-size: 9px; letter-spacing: 0.22em; text-transform: uppercase;
  color: var(--ink-400); padding: 18px 26px 8px; margin: 0;
}
.portal .side.pvs .pvs-brand { margin-bottom: 0; }
.portal .side.pvs .side-foot { margin-top: auto; padding: 0; border: 0; }
.portal .side.pvs .pvs-me { margin: 0; padding: 18px 26px 0; border-top: 1px solid var(--sand-300); }
.portal .side.pvs .pvs-spill { background: var(--accent); }

/* The portal rail belongs to no single pillar, so it takes the neutral accent
   the shared clinician rail uses rather than borrowing one pillar's colour. */
.portal { --accent: var(--ink-700); --accent-wash: rgba(60, 74, 86, 0.10); }

/* ── Ghost chrome ────────────────────────────────────────────────────────
   The pillar app's .app wrapper still reserves space for furniture that no
   longer exists:

     _app-desktop.css   padding-top: 64px    — the fixed top nav bar
     _app-desktop.css   padding-bottom: 56px — the bottom tab bar
     pillar-app.css     padding-bottom: 96px — the same bar, taller
     _app-mobile.css    padding-bottom: 88px — the same bar, plus home indicator

   All four were room for the header and the bottom tab bar. Those are gone;
   the rail replaced them. The padding stayed, so every screen in the pillar
   started 64px down and carried ~96px of dead space underneath — and because
   .app ALSO carries min-height:100vh, a screen that is itself viewport-height
   (the inbox) made the page taller than the window and scrolled for no reason.

   Rob, 19 Sep: "one is positioned differently the patient one is still wrong
   and scrolls weird."

   Scoped to .pvs-main, so the rule only applies where the new shell is the
   chrome. Specificity (0,2,0) beats every declaration above regardless of
   load order, which is why this is one block rather than four edits in four
   sheets that each have their own reasons to exist. */
.pvs-main .app {
  min-height: 0;
  padding-top: 0;
  padding-bottom: 0;
}

/* The inbox fills the window. The PADDING IS ON ITS COLUMNS, not on the shell:
   pad the shell and the whole thing starts below the notice, which is the
   thing being fixed. Pad the columns and each one's background — the list's
   sand-50, the chat's ground — runs to the top with its content below the
   notice. */
/* Nothing to make room for: the notice is a fixed toast now and takes part in
   no layout at all. */
.pvs-main .ix-shell { height: 100dvh; }

/* ── The capped page ─────────────────────────────────────────────────────
   _app-desktop.css caps and centres the whole document:

     body { max-width: 1440px !important; margin: 0 auto !important; }

   That sheet is loaded by the pillar layouts and by nothing else — the
   provider console never sees it. So the same shell rendered in a pillar sat
   inside a 1440px column centred in the viewport: the rail pinned to the true
   left edge (it is fixed), while .pvs-main's 240px offset was measured inside
   the centred body. The result is a band of dead space between the rail and
   the content, and content that stops short of the right edge.

   Rob, 19 Sep: "the content sticks to the left nav... In the other (the
   patient) it is incorrect because it does not stick to the nav."

   The cap exists for the pillar screens that are still laid out as a centred
   document. It must not apply when the rail is the chrome, so it is undone
   exactly there — :has(.pvs-shell) is true only on a page this shell wraps.
   !important because the rule it overrides carries one, and the higher
   specificity is what decides between them. */
body:has(.pvs-shell) {
  max-width: none !important;
  width: auto !important;
  margin: 0 !important;
}



/* ── Small screens, page furniture ───────────────────────────────────────
   At the END of the sheet on purpose. These override .pv-top, .pv-view and
   the two banners, all of which are declared further up with the same
   specificity — so written above them, the base rules win at every width and
   a phone keeps a desktop's 36px gutters. Order is the only thing separating
   these, which is exactly why they are last. */
@media (max-width: 900px) {
  .pv-top { padding: 18px; }
  .pv-view { padding: 18px; }
  /* The inbox measures itself against the viewport; on a phone the sticky bar
     is above it and 100dvh would push its composer off the bottom. */
  .pvs-main .ix-shell { height: calc(100dvh - 52px); }
  /* Two panes do not fit. The list is the picker; the chat covers it once a
     thread is open, which is what ix-chat-back is for. */
  .ix-shell { grid-template-columns: 1fr; }
}

/* ── The toast ───────────────────────────────────────────────────────────
   Top right, over everything, part of no layout. Four attempts at keeping
   this in the flow all failed the same way — a block in flow takes space and
   the only question is what it pushes. Fixed has no height to measure and
   nothing underneath it to move. */
.pv-toast {
  position: fixed; top: 18px; right: 18px;
  width: min(400px, calc(100vw - 36px));
  /* UNDER EVERY DRAWER, over the page.

     It was 300 — above the schedule panel (91), its scrim (90), the mobile
     rail drawer (80) and that drawer's scrim (70). So a panel sliding out
     from the right passed BENEATH the one thing pinned to the right, and the
     dot stayed live over the top of it: clickable, hoverable, and collecting
     clicks meant for the panel underneath. Rob, 19 Sep: "the ! should be
     under the drawer that slides out not on top of it. It causes weird
     behaviour when its on top."

     65 puts it above the sticky topbar (60) and below all four of those, so
     anything that opens deliberately covers it — including the scrim, which
     is what makes a click near the dot close the panel rather than fight it.

     It still gets out of the way on its own: opening the schedule panel fires
     the collapse in lib/notice-collapse.ts, so what ends up under the scrim is
     the dot rather than the whole card. */
  z-index: 65;
}
.pv-toast-card {
  /* Extra room on the right for the ✕, which is absolutely positioned in this
     corner and would otherwise have the title running under it. */
  border-radius: 14px; padding: 15px 34px 15px 18px;
  /* OPAQUE, and it has to be.
     --sienna-tint is rgba(140,82,56,0.10). As a background on its own that is
     a 10%-opacity pane with a blur behind it — a piece of glass, not a card.
     Whatever sits underneath shows through, and on /clinician/health/settings
     what sits underneath is the location field: focusing it fires
     `.set-input:focus { border-color: var(--ink-400) }` and that darkened
     vertical border read as a faint line INSIDE the toast (Rob, 19 Sep).
     A notice that renders the page's focus rings is not a surface.
     Compositing the same tint onto the canvas gives the identical colour it
     had while floating over sand, with nothing able to bleed through. The
     backdrop-filter goes with it: it now blurs something no one can see. */
  background: linear-gradient(var(--sienna-tint), var(--sienna-tint)), var(--sand-50);
  border: 1px solid var(--sienna-soft);
  box-shadow: 0 24px 50px -28px rgba(27, 42, 56, 0.4);
}
.pv-toast.tone-ask .pv-toast-card {
  background:
    linear-gradient(var(--accent-wash, rgba(44, 90, 74, 0.12)), var(--accent-wash, rgba(44, 90, 74, 0.12))),
    var(--sand-50);
  border-color: var(--accent-soft, var(--forest-soft));
}
.pv-toast-k {
  font-family: var(--sans); font-size: 9.5px; letter-spacing: 0.18em;
  text-transform: uppercase; color: var(--sienna); margin-bottom: 5px;
}
.pv-toast.tone-ask .pv-toast-k { color: var(--accent, var(--forest)); }
.pv-toast p {
  margin: 0; font-family: var(--serif); font-size: 15px;
  line-height: 1.45; color: var(--ink-900);
}
.pv-toast ul { margin: 7px 0 0; padding-left: 18px; }
.pv-toast li {
  font-family: var(--serif); font-size: 14.5px; line-height: 1.6; color: var(--ink-900);
}
.pv-toast li a { color: var(--ink-900); text-decoration: underline; text-underline-offset: 3px; }
.pv-toast li a:hover { color: var(--sienna); }
/* THE DOT IS EVERY SCREEN'S AFFORDANCE NOW, not just the phone's.
   It used to be display:none above 900px, on the reasoning that a desktop has
   room to leave the card open. Room was never the complaint — the card sits
   over the top-right of the page and a provider filling in the very fields it
   is asking about cannot move it (Rob, 19 Sep: "we can close it, it stays as a
   '!' action item in the top right until resolved. No true dismissal").
   So: same control, same two states, every width.

   THREE states, not two. data-open="auto" is the untouched default and means
   different things by width — open on a desktop, a dot on a phone — which is
   why it cannot be a boolean decided at render without guessing the viewport
   during SSR. Once the provider touches the control it becomes "1" or "0" and
   means the same thing everywhere, because by then they have said so. */
/* ONE BUTTON, TWO SHAPES, and it never takes space from the card.

   It was briefly a 34px circle stacked ABOVE the card, which pushed the whole
   notice down the page — Rob, 19 Sep: "that X really needs to be smaller and
   in the corner of the toast. It actually moved the toast down quite a ways
   and I liked its positioning before."

   So it is absolutely positioned against .pv-toast, which is the fixed box the
   card fills. Out of flow means the card sits exactly where it always sat, and
   top-right of this box IS top-right of the card. When the card is hidden the
   box has no height, and the same anchor leaves the dot in the corner the
   notice used to occupy — which is where a "!" belongs anyway. */
/* OPEN — a dismiss on somebody else's card: small, quiet, no fill, no shadow.
   The card is the thing being read; the way out of it is not. */
.pv-toast {
  --dot-inset: 9px;
  --dot-size: 22px;
  --dot-font: 12px;
  --dot-bg: transparent;
  --dot-fg: var(--ink-400);
  --dot-shadow: none;
}
/* COLLAPSED — the button IS the notice now, so it takes the card's weight:
   full size, filled, shadowed, in the corner the card used to occupy. Set as
   variables rather than as a second block of declarations because two states
   flip it and one of them lives inside a media query, where a duplicated
   block would be a copy waiting to drift. */
.pv-toast[data-open="0"] {
  --dot-inset: 0px;
  --dot-size: 34px;
  --dot-font: 17px;
  --dot-bg: var(--sienna);
  --dot-fg: var(--sand-50);
  --dot-shadow: 0 10px 22px -10px rgba(27, 42, 56, 0.6);
}
.pv-toast.tone-ask[data-open="0"] { --dot-bg: var(--accent, var(--forest)); }

.pv-toast-dot {
  position: absolute; top: var(--dot-inset); right: var(--dot-inset); z-index: 1;
  display: grid; place-items: center;
  width: var(--dot-size); height: var(--dot-size);
  border-radius: 50%; border: 0; cursor: pointer; padding: 0;
  background: var(--dot-bg); color: var(--dot-fg);
  box-shadow: var(--dot-shadow);
  font-family: var(--sans); font-size: var(--dot-font); line-height: 1;
}
/* The button is the hover target, not the glyph inside it. */
.pv-toast-dot:hover { color: var(--ink-900); }
.pv-toast[data-open="0"] .pv-toast-dot:hover { color: var(--sand-50); filter: brightness(0.94); }

.pv-toast[data-open="0"] .pv-toast-card { display: none; }

/* The glyph follows the card, so it is always the action available: an ✕ while
   the card is showing, a ! while it is not. Both are rendered and CSS picks,
   because "auto" has no single answer to give React. */
.pv-toast .pv-toast-bang { display: none; }
.pv-toast .pv-toast-x { display: block; }
.pv-toast[data-open="0"] .pv-toast-bang { display: block; }
.pv-toast[data-open="0"] .pv-toast-x { display: none; }

@media (max-width: 900px) {
  .pv-toast {
    top: calc(10px + env(safe-area-inset-top, 0px));
    right: 10px;
    /* A definite width in both states, rather than `auto` when collapsed. The
       button is out of flow now, so an auto-width box would measure zero and
       the card would have no width to inherit the moment it opened. */
    width: min(360px, calc(100vw - 20px));
  }
  /* Untouched on a phone is the dot, which is where this pattern started —
       same three variables the explicit collapsed state sets. */
  .pv-toast[data-open="auto"] {
    --dot-inset: 0px;
    --dot-size: 34px;
    --dot-font: 17px;
    --dot-bg: var(--sienna);
    --dot-fg: var(--sand-50);
    --dot-shadow: 0 10px 22px -10px rgba(27, 42, 56, 0.6);
  }
  .pv-toast.tone-ask[data-open="auto"] { --dot-bg: var(--accent, var(--forest)); }
  .pv-toast[data-open="auto"] .pv-toast-card { display: none; }
  .pv-toast[data-open="auto"] .pv-toast-bang { display: block; }
  .pv-toast[data-open="auto"] .pv-toast-x { display: none; }
}
