/* ==========================================================================
   SpeakRoutine application stylesheet
   Design tokens and component shapes carried over from the approved mock-up
   (../webapp.html v2.6) so the built system matches what HPKK signed off.

   Accessibility (layer L1 of RESEARCH.md) is driven by the CSS custom
   properties --pc-font-*, --pc-touch and --pc-gap, which AccessibilityService
   writes onto <html> per user. Never hard-code a font size or a button height
   in a component: read the variable, and the patient's setting follows.
   ========================================================================== */

:root {
  /* ---- brand ------------------------------------------------------------
     One hue with a light tint for selected states, and a dark shade for text
     on white. Every value below was checked against #FFFFFF and against the
     page ground: the ink colours clear 4.5:1 and the button fills clear 4.5:1
     with white text, which is what L1 asks for.

     Navy carries the institution and gold carries the warmth. This is a UKM
     hospital's deliverable and the people who have to accept it are clinicians,
     so the brand hue is the one that reads as the university rather than as a
     consumer app. Warmth is the accent's job, not the brand's.

     Three hues were built and compared before this one was kept. Teal tried to
     be credible and friendly in a single colour and landed short of both.
     Forest green is the product's own metaphor, but green already meant
     "finished" on the heatmap and "success" on a status, and the brand blurred
     into both. Maroon separated best across the page, and then a primary button
     in it read as a destructive one, because red is spoken for by errors. Navy
     is the only one of the four with no collision to manage.

     --accent is the second hue, and it exists because a one-colour page has no
     way to say "this bit matters more" except by making things bigger. It is
     spent sparingly: the kicker above each section, the step numerals, one tile
     in three.

     There is no --secondary. It was a plum left over from the wireframe, then
     briefly the accent's dark tone, and its only reader was .wa-btn.s, which
     now takes the brand tint directly. A token with one caller and no meaning
     of its own is a redirection, not an abstraction. */
  --primary: #1D4E89;
  --primary-dark: #12305A;
  --primary-tint: #E8EEF7;
  --accent: #A16207;
  --accent-dark: #7C4A05;
  --accent-tint: #FBF2DF;

  /* The ground of a full-bleed dark band, and the ink that survives on it. The
     hero and the week band are painted edge to edge rather than floated as
     cards, and a band that deep needs its own token: reaching for
     --primary-dark there tied the darkest step of the brand ramp to a page
     surface, and the two move for different reasons. */
  --ink-band: #0F2747;
  --on-band: #EEF2F8;
  --on-band-dim: #BCC8DA;
  /* The accent and the growth green, lightened to clear 7:1 on that band. The
     page versions are ink for white and go muddy on navy. */
  --accent-on-band: #E9B558;
  --grow-on-band: #7BC98B;

  /* ---- semantic ---------------------------------------------------------
     The old set was the 2014 Material palette (#4CAF50, #FF9800, #E53935).
     Those are display colours: as text on white none of them reaches 4.5:1,
     so each pairs a readable ink with a pale ground. */
  --success: #15803D;
  --success-bg: #ECFDF3;
  --warning: #9A5B00;
  --warning-bg: #FFF8EB;
  --danger: #B42318;
  --danger-bg: #FEF3F2;

  /* Edge of a semantic panel. All three sit at L* 80, so an error box and an
     info box are outlined with the same weight of line in different hues.
     They were #F5B5B0 and #B8DCEB, two one-off tints, while the success alert
     borrowed full-strength --success and so was outlined far harder than the
     other two. */
  --success-border: #A9D0AF;
  --danger-border: #F9B7A8;
  --primary-border: #B6C9E2;

  /* Growth ramp for the plant motivator and the adherence heatmap. The steps
     are spaced evenly in CIE L* (92, 77, 62, 47) on a fixed hue, so the ramp
     reads as one colour getting stronger rather than three unrelated greens.
     Four steps cannot be 3:1 apart from each other: that needs 27:1 end to
     end and white to black is 21:1. So colour is never the only encoding on
     a heatmap cell: each one carries an aria-label naming the day and the
     session count, prints its day letter underneath, and keeps a title for
     the hover. The title alone used to be the whole of it, which reached
     neither a screen reader reliably nor a printout at all. Replaces --l5, which was the wireframe's
     layer-colour key and had no business setting a fill in the product. */
  --success-soft: #96CA9F;
  --success-mid: #5BA56B;

  /* Step zero of that ramp. It is not --border: a heatmap cell for a day with
     no session is the lightest step of a scale, while --border is the edge of
     a control. They happen to be the same grey in this theme, and borrowing
     one for the other inverted the whole ramp in high-contrast mode, where
     --border goes black and 'no sessions' came out as the strongest cell. */
  --heat-empty: #E5E2DB;

  /* ---- neutrals ---------------------------------------------------------
     --border-strong is for the edge of anything a person types into or presses.
     WCAG 1.4.11 wants 3:1 there and the old --border managed 1.47, so form
     fields had effectively no visible boundary for a patient with low vision.
     --border stays light because a divider between two panels is decoration and
     has no contrast requirement. */
  --bg: #F6F5F2;
  --surface: #FFFFFF;
  --text: #16191D;
  --text-muted: #565B63;
  --border: #E5E2DB;
  --border-strong: #85817A;

  /* Focus gets its own colour, and it has to be a hue nothing else on the page
     owns. It used to borrow --warning; it then collided with --accent, which is
     amber for the same reason warning is. It has been through violet, which
     sat too close to navy on a form field, and then magenta, which cleared
     every contrast check and still read as maroon: on the one control a
     patient looks at while typing, a hue nobody associates with "this field
     is active" is a question rather than an answer.

     Azure is what that convention actually is, and it is far enough from the
     brand's muted navy to be told apart. It clears 3:1 on all three grounds a
     ring is ever drawn against: white, the page ground, and the dark band. */
  --focus: #1D6FE0;

  /* Ink on a filled control. --surface is the ground under text; this is text
     over a fill, and the two are only the same colour by coincidence. */
  --on-fill: #FFFFFF;

  /* ---- type scale -------------------------------------------------------
     Three of the steps ARE the accessibility contract and are read straight
     from it: --fs-base is body, --fs-md is instruction, --fs-xl is heading.
     Everything else is derived from those, so a patient raising their text
     size moves the whole scale rather than three of its steps.

     --fs-lg sits at the midpoint of instruction and heading rather than at a
     multiple of body. Taken off body it collided: on the 'large' setting the
     old --fs-lg came out at 24.15px against --fs-md at 24px, two steps 0.15px
     apart, and mixing two roots meant the ordering was never guaranteed
     across all three tiers. A midpoint is between its neighbours by
     construction, whatever the three input sizes are.

     The two small steps are floored in px. Proportional alone, --fs-xs came
     out at 12.96px on the standard setting, and it carries form labels, KPI
     labels, pills and chart labels: a 13px label on a screen whose stated
     floor is 18px body. The floor binds on the standard setting and lets go
     once the patient raises their text, which is why the two steps converge
     at the bottom and open up again above it.

     Resulting scale, standard / large / xlarge:
       xs   16    16.4  19.5      lg   22    26    31
       sm   17    18.7  22.3      xl   24    28    34
       base 18    21    25        2xl  33.6  39.2  47.6
       md   20    24    28        3xl  45.6  53.2  64.6  */
  --fs-xs: max(16px, calc(var(--pc-font-body) * 0.78));
  --fs-sm: max(17px, calc(var(--pc-font-body) * 0.89));
  --fs-base: var(--pc-font-body);
  --fs-md: var(--pc-font-instr);
  --fs-lg: calc((var(--pc-font-instr) + var(--pc-font-heading)) / 2);
  --fs-xl: var(--pc-font-heading);
  --fs-2xl: calc(var(--pc-font-heading) * 1.4);
  --fs-3xl: calc(var(--pc-font-heading) * 1.9);
  /* Display step, for the hero headline and the ghost numerals on the flow.
     Derived like the rest rather than set in px or in clamp(): a viewport-based
     size would ignore the patient's own heading setting, which is the one thing
     the whole scale exists to respect. 57.6 / 67.2 / 81.6px. */
  --fs-4xl: calc(var(--pc-font-heading) * 2.4);

  /* Four weights. 800 was doing the work of a heading typeface and just read
     as blunt at every size. */
  --fw-normal: 400;
  --fw-medium: 500;
  --fw-semibold: 600;
  --fw-bold: 700;

  /* ---- elevation ---------------------------------------------------------
     Material 3's elevation levels 1, 2 and 3, which are a pair of shadows
     each: a tight key shadow and a wide ambient one. The single soft blur this
     file used before is what made every card look like it was printed on the
     page rather than sitting above it.

     M3 specifies them in black. They are cast in the brand's darkest navy
     instead, because a neutral black shadow on a warm ground reads as grey
     dirt under the card, and every surface here sits on a warm ground. */
  --shadow-sm: 0 1px 2px rgba(11, 26, 48, .24), 0 1px 3px 1px rgba(11, 26, 48, .10);
  --shadow-md: 0 1px 2px rgba(11, 26, 48, .24), 0 2px 6px 2px rgba(11, 26, 48, .10);
  --shadow-lg: 0 1px 3px rgba(11, 26, 48, .22), 0 4px 8px 3px rgba(11, 26, 48, .12);

  /* ---- state layers ------------------------------------------------------
     M3 draws interaction as a translucent overlay of the ink colour over the
     control, at a fixed opacity per state, rather than as a second hard-coded
     fill per component. One rule then covers hover on a white tile, a tinted
     chip and a filled button, and a component that changes colour keeps its
     states for free. */
  --state-hover: .08;
  --state-focus: .10;
  --state-press: .12;

  /* ---- motion ------------------------------------------------------------
     M3's standard easing and two of its duration tokens. The curve starts fast
     and settles slowly, which is what keeps a hover lift from reading as a
     slide. Every use is behind prefers-reduced-motion. */
  --ease-standard: cubic-bezier(.2, 0, 0, 1);
  --dur-short: 200ms;
  --dur-medium: 400ms;

  /* ---- radius ------------------------------------------------------------
     Six different values were in use (4, 6, 8, 10, 12, 20px). Three is enough. */
  --radius-sm: 8px;
  --radius-md: 12px;
  /* Material 3's shape scale: 4, 8, 12, 16, 28, full. The two larger steps are
     for the landing. The rest of the app keeps small and medium, because a
     dashboard panel at extra-large reads as a toy, while a marketing tile at
     medium reads as a 2015 Bootstrap card, which was the note on this page. */
  --radius-lg: 16px;
  --radius-xl: 28px;
  --radius-pill: 999px;

  /* Overwritten per user by AccessibilityService::cssVariables(). The values
     here are the L1 floor, used for signed-out pages. */
  --pc-font-body: 18px;
  --pc-font-heading: 24px;
  --pc-font-instr: 20px;
  --pc-touch: 60px;
  --pc-gap: 12px;
}

/* High-contrast mode (S1 toggle): darker ink, brighter ground, stronger edges. */
html.pc-contrast {
  --bg: #FFFFFF;
  --surface: #FFFFFF;
  --text: #000000;
  --text-muted: #2B2B2B;
  --border: #000000;
  --border-strong: #000000;
  --primary: #103B6B;
  --primary-dark: #071F3C;
  --primary-tint: #FFFFFF;
  /* Amber survives here, darkened. Dropping it to black would take the one
     signal that tells a step number from a heading apart at a glance. */
  --accent: #603A00;
  --accent-dark: #422800;
  --accent-tint: #FFFFFF;
  --success: #0B5D2B;
  --success-bg: #FFFFFF;
  --warning: #6B3F00;
  --warning-bg: #FFFFFF;
  --danger: #8B1A12;
  --danger-bg: #FFFFFF;
  /* Every panel ground is white here, so the outline is the only thing left
     holding a semantic box together; it goes to the same black as --border. */
  --success-border: #000000;
  --danger-border: #000000;
  --primary-border: #000000;
  /* The ramp re-spaces itself around the darker high-contrast --success:
     L* 96, 75, 55, 34, steps of ~21 against ~15 in the default theme. */
  --success-soft: #9BC4A1;
  --success-mid: #569063;
  --heat-empty: #FFFFFF;
  /* A full-bleed dark band cannot survive here: the whole mode is black ink on
     white. The band inverts and is held by a rule instead. */
  --ink-band: #FFFFFF;
  --on-band: #000000;
  --on-band-dim: #2B2B2B;
  --accent-on-band: #6B3F00;
  --grow-on-band: #0B5D2B;
  --shadow-sm: none;
  --shadow-md: none;
  --shadow-lg: none;
}
html.pc-contrast .wcard,
html.pc-contrast .kpi { border-width: 2px; border-color: var(--border); box-shadow: none; }
html.pc-contrast .wa-input:focus { box-shadow: none; }
/* An empty cell is white here, so without an outline it would not read as a
   cell at all. Inset rather than border, so the grid geometry does not move. */
html.pc-contrast .heat i { box-shadow: inset 0 0 0 1px var(--border); }

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  font-size: var(--fs-base);
  line-height: 1.55;

  /* A column body keeps the funder footer at the foot of the viewport on the
     standalone pages (home, login), which have no app shell to push it down.
     Inside the shell .wa-content already does that job. */
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}
body > .pc-shell { flex: 1; }

h1, h2, h3 { margin: 0 0 10px; line-height: 1.3; }
h1 { font-size: var(--fs-xl); }
h2 { font-size: var(--fs-lg); }
/* h3 was --fs-lg as well, so a card title and a section title came out the
   same size and the hierarchy stopped after h1. */
h3 { font-size: var(--fs-md); }

a { color: var(--primary); }

/* ---------------------------------------------------------------- shell -- */

.pc-shell { display: flex; min-height: 100vh; }

/* Sticky and full height, with its own scrollbar.
   It used to scroll away with the page, so on a long screen like R1 the only
   way to reach any other screen was to scroll back to the top first. That is a
   real cost for someone whose hands shake: every extra scroll is another chance
   to overshoot. min-width rather than a fixed width, because at the xlarge text
   tier the labels wrap and a pinned 220px would cut them. */
.wa-sidebar {
  width: 15em;
  min-width: 220px;
  max-width: 300px;
  background: var(--surface);
  border-right: 1px solid var(--border);
  padding: 16px 0 10px;
  flex-shrink: 0;
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
}

/* Brand, and under it the role of the account currently open. Families share
   one tablet here: a daughter signs in to record for her father, then he signs
   in himself later. Which account is open decides what the next tap does, so it
   is not a detail to be tucked away in a corner of the top bar. */
.wa-sidebar .wa-brand {
  display: block;
  padding: 0 18px 14px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 10px;
  min-height: 0;
}
.wa-sidebar .wa-brand:hover { background: none; }

.wa-brand-role {
  display: block;
  margin-top: 5px;
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  color: var(--text-muted);
}

/* ---- brand lockup -------------------------------------------------------

   Mark plus live text, replacing the wordmark SVG. See pc_brand(). The name is
   one colour in one weight rather than two colours: the old logo split the two
   halves of the name across two blues, which at 42px on a white bar read as
   two separate words. */
.pc-brand {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  color: var(--primary-dark);
}
/* Selector carries .pc-brand so it outranks the base .pc-icon size, which is
   set in em and would otherwise shrink the mark to the height of the text it
   sits beside. The mark is artwork here, not an icon in a label. */
.pc-brand .pc-brand-mark { width: 2.1em; height: 2.1em; color: var(--primary); flex: 0 0 auto; }
.pc-brand-name {
  font-size: var(--fs-lg);
  font-weight: var(--fw-bold);
  letter-spacing: -0.4px;
  line-height: 1;
}
/* On a dark ground the whole lockup inverts, mark included. */
.pc-brand.on-fill, .pc-brand.on-fill .pc-brand-mark { color: var(--on-fill); }

/* Two smaller sizes of the same lockup. The mark is set in em throughout, so
   changing the name's size carries the artwork with it and the two can never
   drift apart. */
.pc-brand-sm .pc-brand-name { font-size: var(--fs-base); }

.wa-sidebar a {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 18px;
  color: var(--text-muted);
  text-decoration: none;
  font-weight: var(--fw-semibold);
  min-height: var(--pc-touch);
  font-size: var(--fs-sm);
}
.wa-sidebar a:hover { background: var(--bg); color: var(--text); }

/* The row you are already on needs the least emphasis on the screen: you can
   see where you are. A soft tint with an edge marker says it without turning
   the sidebar into a dark block on every screen. The marker is drawn inside the
   row rather than as a border, so the label does not shift by 4px when a row
   becomes active. */
.wa-sidebar nav a.active {
  color: var(--primary-dark);
  background: var(--primary-tint);
  position: relative;
}
.wa-sidebar nav a.active::before {
  content: "";
  position: absolute;
  left: 0;
  top: 6px;
  bottom: 6px;
  width: 4px;
  border-radius: 0 4px 4px 0;
  background: var(--primary);
}
.wa-sidebar .nav-label { flex: 1 1 auto; }

/* Counters, and only where the number falls back to zero on its own: a review
   queue and an unanswered inbox. Absent at zero rather than showing "0",
   because a badge that is always lit is furniture, and furniture is not read.
   min-width keeps a single digit from rendering as a thin sliver. */
.nav-badge {
  flex: 0 0 auto;
  min-width: 1.9em;
  padding: 2px 7px;
  border-radius: var(--radius-pill);
  background: var(--danger);
  color: var(--on-fill);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  text-align: center;
  line-height: 1.5;
}

/* Identity and the way out, together at the foot. margin-top:auto pins them to
   the bottom on a short menu and lets them sit right under the last link on a
   long one, rather than floating in a gap. */
.wa-sidebar-foot {
  margin-top: auto;
  padding: 10px 8px 0;
  border-top: 1px solid var(--border);
}
.wa-sidebar .wa-who {
  gap: 9px;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  color: var(--text);
  min-height: 0;
}
.wa-who-name {
  flex: 1 1 auto;
  min-width: 0;
  font-size: var(--fs-sm);
  line-height: 1.3;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.wa-who-name span {
  display: block;
  font-size: var(--fs-xs);
  font-weight: var(--fw-regular, 400);
  color: var(--text-muted);
}
.wa-sidebar .wa-who .pc-icon { flex: 0 0 auto; width: 1.15em; height: 1.15em; color: var(--text-muted); }
.wa-sidebar .wa-who:hover .pc-icon { color: var(--primary); }
/* Matches an active menu row, so "where am I" reads the same wherever you are. */
.wa-sidebar .wa-who.active {
  background: var(--primary-tint);
  color: var(--primary-dark);
  font-weight: var(--fw-semibold);
}
.wa-sidebar .wa-who.active .pc-icon,
.wa-sidebar .wa-who.active .wa-who-name span { color: var(--primary-dark); }

.wa-sidebar .wa-signout {
  gap: 9px;
  margin-top: 4px;
  padding: 10px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  color: var(--text);
  font-size: var(--fs-sm);
}
.wa-sidebar .wa-signout:hover { background: var(--bg); border-color: var(--primary); color: var(--primary-dark); }

/* Screen-reader-only text. The badge says "5"; this is what the 5 counts. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}
/* Nav icons carry more weight than inline ones, so they are sized above the
   1.15em default. They still scale with the label rather than being pinned to
   a pixel size. */
.wa-sidebar a .pc-icon { width: 1.4em; height: 1.4em; }
.wa-sidebar .nav-close .pc-icon { width: 1.2em; height: 1.2em; }

.wa-main { flex: 1; display: flex; flex-direction: column; min-width: 0; }

/* Navigation only. The page title used to sit here in a white strip, small
   and bold, which opened every screen the same anonymous way; it is a page
   header in the content now. What is left is the menu button on a handset
   and, signed out, the way in. */
.wa-topbar {
  background: var(--surface);
  padding: 12px 22px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  border-bottom: 1px solid var(--border);
  flex-wrap: wrap;
}
.wa-topbar .user { display: flex; align-items: center; gap: 8px; color: var(--text-muted); font-size: var(--fs-sm); }
/* Holding only the phone menu button, so above that breakpoint it is an
   empty white band and goes. */
@media (min-width: 901px) {
  .wa-topbar.is-nav-only { display: none; }
}

.wa-content { padding: 28px 30px 8px; flex: 1; }
@media (max-width: 900px) {
  .wa-content { padding: 20px 18px 8px; }
}

/* ---- page header --------------------------------------------------------

   The opening of every signed-in screen: the title at display size on the
   page ground, an optional line saying what the screen is for, and a slot on
   the right for the one action the screen is about.

   Before this, the title was 18px semibold inside a white bar and the screen
   started underneath it with no hierarchy at all, so a dashboard, a table and
   a form all began identically. The actions that belong to a screen had
   nowhere to go either, and ended up wedged into the heading row of whichever
   card happened to be first. */
.pc-page {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 16px 24px;
  flex-wrap: wrap;
  margin: 0 0 22px;
}
.pc-page h1 {
  margin: 0;
  font-size: var(--fs-2xl);
  font-weight: var(--fw-bold);
  line-height: 1.15;
  letter-spacing: -.02em;
}
.pc-page-text p {
  margin: 6px 0 0;
  font-size: var(--fs-base);
  color: var(--text-muted);
  line-height: 1.5;
  max-width: 62ch;
}
.pc-page-actions { display: flex; gap: 12px; flex-wrap: wrap; }
.pc-page-actions .wa-btn { margin-top: 0; width: auto; }

/* Funder attribution. Required by the ISIF Asia grant on every page, so it is
   deliberately outside .pc-secondary (simple mode must not hide it) and stays
   visible in print. */
.pc-funding {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16px;
  flex-wrap: wrap;
  margin-top: auto;
  padding: 18px 22px 20px;
  color: var(--text-muted);

  /* No white band here: the footer sits on the page ground with a single
     hairline above it. A --surface panel competed with the white cards and
     read as a stray stripe wherever the page was short. */
  border-top: 1px solid var(--border);
}
/* 80px is about the floor for this stacked lockup: below it "FOUNDATION" and
   the isif asia sub-mark blur into grey and the credit stops being readable. */
.pc-funding-logo { height: 80px; width: auto; flex: none; }
.pc-funding-text { margin: 0; font-size: var(--fs-xs); line-height: 1.5; text-align: left; flex: 0 1 44ch; }

html.pc-contrast .pc-funding { color: var(--text); border-top-width: 2px; }

/* ---------------------------------------------------------- landing (/) -- */

/* This is the one screen a patient meets *before* they can reach S1
   Accessibility Settings, so it has to clear the L1 floor from RESEARCH.md on
   its own: body >= 18px, instructions >= 20px, primary button >= 64px tall,
   >= 12px between anything tappable. Nothing here uses .small, because 14px
   grey on a page aimed at 65+ patients with visual impairment was the main
   fault.

   Shape, elevation, state and motion all come from Material 3, defined once in
   :root. The component library itself is not used: this project has no npm and
   no bundler, @material/web ships as ES modules that need building, and
   icon_helper.php already records why nothing here loads from a CDN. Taking
   the system rather than the components also keeps the L1 contract intact,
   which a third-party button with its own type scale would not.

   The page is laid out as full-bleed bands, not as one column of cards. Read
   back, every section of the old version was the same shape: a small label with
   a rule, then a row of equal white rectangles. That sameness is what reads as
   dated. Nothing was wrong with any one band; the problem was that the ninth
   looked exactly like the first. */

.pc-lp { background: var(--bg); }

/* A band is full width; only its contents are held to a column. */
.pc-wrap {
  width: 100%;
  max-width: 1140px;
  margin: 0 auto;
  padding: 0 24px;
}
.pc-band { padding: 72px 0; }
.pc-band.is-tinted {
  /* M3's surface-container idea: a second, slightly tinted ground that marks a
     band off without giving it a border. */
  background: var(--primary-tint);
}
@media (max-width: 700px) {
  .pc-band { padding: 48px 0; }
  .pc-wrap { padding: 0 18px; }
}

/* ---- section openers ----------------------------------------------------

   A small accent kicker over a display heading, replacing the label-with-a-rule
   that opened all four of the old bands identically. The kicker is the only
   uppercase text on the page and it carries tracking, so it reads as a label
   at a glance without being small enough to strain. */
.pc-kicker {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 12px;
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--accent-dark);
}
.pc-kicker .pc-icon { width: 1.1em; height: 1.1em; }
.pc-kicker.on-band { color: var(--accent-on-band); }

.pc-display {
  margin: 0 0 34px;
  max-width: 20ch;
  font-size: var(--fs-2xl);
  font-weight: var(--fw-bold);
  line-height: 1.15;
  letter-spacing: -.02em;
  color: var(--text);
}
.pc-display.on-band { color: var(--on-band); }

/* ---- top bar ------------------------------------------------------------

   Sticky, and translucent over whatever is behind it. It carries no control
   at all. A staff sign-in sat here first, and it and the hero button were two
   doors onto one form, which the visitor read as a choice they had to get
   right; a compact Sign in revealed on scroll replaced it and kept the count
   at one on screen, but not at one on the page. The bar says whose site this
   is, and nothing else. */
.pc-lpbar {
  position: sticky;
  top: 0;
  z-index: 20;
  background: rgba(255, 255, 255, .88);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--border);
}
.pc-lpbar-in {
  width: 100%;
  max-width: 1140px;
  margin: 0 auto;
  padding: 10px 24px;
  display: flex;
  align-items: center;
  gap: 16px;
}
.pc-lpbar-brand { display: inline-flex; align-items: center; text-decoration: none; }
/* The organisation, not a control. It answers "whose site is this", which is
   the question that decides whether an older visitor trusts the page enough to
   type a password into it.

   The mark is capped by height and takes its width from its own ratio, and
   the width/height attributes on the tag reserve the right box before the
   file arrives, so the bar does not jump as the page settles.

   margin-left: auto puts the pair hard against the right end of the column.

   The rest of this rule is about a collision that is easy to miss until you
   zoom in. UKM's artwork is not a crest, it is a full lockup: the crest plus
   four lines of small serif type setting the university's name twice, once in
   Malay and once in English. At 44px those four lines are about six pixels
   tall each, which is not reading, it is texture. Beside them sat the
   hospital's name at 17px in muted grey, lighter than the mush next to it, so
   the one line a visitor actually needs was the quieter of the two.

   The line beside it reads "Hospital Pakar Kanak-Kanak (HPKK)" and stops
   there. It used to end ", UKM", which put the university's name twice in one
   lockup: once in our sans and once, twice over, inside their artwork.

   Three changes, no cropping. Their logo is theirs and redrawing it to a
   crest is not ours to do. The name goes up to body size, to the full ink and
   to semibold so it leads; the mark comes down to 36px so it reads as a mark
   rather than as a paragraph; and a hairline sits between them so the eye
   parses two things instead of one smudge. */
.pc-lpbar-org {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 14px;
  font-size: var(--fs-base);
  font-weight: var(--fw-semibold);
  color: var(--text);
}
.pc-lpbar-org img {
  height: 36px;
  width: auto;
  display: block;
  padding-right: 14px;
  border-right: 1px solid var(--border);
}
/* On a phone the words go and the mark stays. Dropping the pair entirely was
   the easy call and the wrong one: the name of the hospital is the trust
   signal on this bar, and the mark carries it in a fifth of the width. */
@media (max-width: 620px) {
  .pc-lpbar-orgname { display: none; }
  /* With the name gone there is nothing to divide from. */
  .pc-lpbar-org img { height: 34px; padding-right: 0; border-right: 0; }
  .pc-lpbar-in { padding: 8px 18px; gap: 12px; }
}

.pc-lp-flash { max-width: 1140px; margin: 16px auto 0; padding: 0 24px; }

/* ---- hero ---------------------------------------------------------------

   Edge to edge and dark, rather than a rounded card floating in the middle of
   a pale page. A hero that is inset on all four sides has the same silhouette
   as the cards below it, which is most of why the old page read as one long
   list of panels. */
.pc-hero {
  position: relative;
  overflow: hidden;
  background:
    radial-gradient(900px 520px at 78% 8%, rgba(120, 170, 240, .20), transparent 62%),
    radial-gradient(700px 460px at 8% 96%, rgba(161, 98, 7, .16), transparent 60%),
    var(--ink-band);
  color: var(--on-band);
  /* Room at the foot for the figures card to straddle the edge. */
  padding: 64px 0 108px;
}
.pc-hero-in {
  width: 100%;
  max-width: 1140px;
  margin: 0 auto;
  padding: 0 24px;
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, .85fr);
  gap: 40px;
  align-items: center;
}
.pc-hero h1 {
  margin: 0;
  font-size: var(--fs-4xl);
  font-weight: var(--fw-bold);
  line-height: 1.05;
  letter-spacing: -.03em;
  color: var(--on-band);
}
.pc-hero-eyebrow, .pc-hero .pc-kicker { margin-bottom: 18px; }
.pc-hero-lede {
  margin: 22px 0 0;
  font-size: var(--fs-md);
  line-height: 1.55;
  max-width: 46ch;
  /* Not white: a long paragraph at full strength on a dark ground buzzes. This
     step still clears 8.8:1. */
  color: var(--on-band-dim);
}
.pc-hero-act { display: grid; gap: 16px; margin-top: 32px; max-width: 430px; }
.wa-btn.pc-btn-hero {
  min-height: 64px;
  padding: 14px 34px;
  font-size: var(--fs-lg);
  margin: 0;
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--primary-dark);
  box-shadow: var(--shadow-lg);
  transition: transform var(--dur-short) var(--ease-standard),
              box-shadow var(--dur-short) var(--ease-standard);
}
.wa-btn.pc-btn-hero:hover { background: var(--primary-tint); color: var(--primary-dark); }
.wa-btn.pc-btn-hero .pc-icon { width: 1em; height: 1em; }
.pc-hero-help {
  margin: 0;
  font-size: var(--fs-sm);
  line-height: 1.55;
  color: var(--on-band-dim);
}
.pc-hero-art { justify-self: center; width: 100%; max-width: 340px; color: var(--on-band); }
/* The plant is the one part of this drawing that exists elsewhere in the
   product, and it is green there. --grow-on-band is the growth green already
   lightened for this ground; it clears 7.5:1 on it. */
.pc-hero-plant { color: var(--grow-on-band); }
.pc-hero-art svg { display: block; width: 100%; height: auto; }

@media (max-width: 900px) {
  .pc-hero { padding: 44px 0 96px; }
  .pc-hero-in { grid-template-columns: minmax(0, 1fr); }
  /* Decoration goes before content does: kept, it pushes the button under the
     fold on a phone. */
  .pc-hero-art { display: none; }
}

/* ---- the three figures --------------------------------------------------

   Straddling the foot of the hero, half on the dark and half on the page. The
   overlap is the point: it ties the two bands into one opening instead of
   stacking a second panel under the first. */
.pc-figs-wrap {
  width: 100%;
  max-width: 1140px;
  margin: -60px auto 0;
  padding: 0 24px;
  position: relative;
  z-index: 2;
}
.pc-figs {
  list-style: none;
  margin: 0;
  padding: 26px 20px;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 16px;
  background: var(--surface);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-lg);
}
.pc-figs li {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  text-align: center;
  padding: 0 14px;
}
.pc-figs li + li { border-left: 1px solid var(--border); }
.pc-figs b {
  font-size: var(--fs-3xl);
  font-weight: var(--fw-bold);
  line-height: 1;
  letter-spacing: -.03em;
  color: var(--primary);
}
/* All three in the brand colour. The zero was set in the accent to mark it as
   the most useful figure on the page, and a single brown numeral between two
   navy ones just read as a mistake. What makes it land is its label, not a
   second hue. */
.pc-figs span { font-size: var(--fs-base); color: var(--text-muted); line-height: 1.4; }

@media (max-width: 760px) {
  .pc-figs { grid-template-columns: minmax(0, 1fr); padding: 18px; }
  .pc-figs li { flex-direction: row; align-items: baseline; gap: 12px; text-align: left; padding: 12px 6px; }
  .pc-figs li + li { border-left: 0; border-top: 1px solid var(--border); }
  .pc-figs b { min-width: 1.6em; }
}

/* ---- bento --------------------------------------------------------------

   Deliberately not three equal tiles. The recording is the part of the routine
   the therapist reviews and the only one that leaves the house, so it takes the
   large cell and the other two stack beside it. Equal weight is the honest
   layout of a wireframe, not of a page that knows what matters. */
.pc-bento {
  display: grid;
  grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
  grid-auto-rows: 1fr;
  gap: 18px;
}
.pc-tile {
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
  transition: transform var(--dur-short) var(--ease-standard),
              box-shadow var(--dur-short) var(--ease-standard);
}
.pc-tile.is-lead { grid-row: span 2; }
/* The well holds the hue; its children inherit it through currentColor, which
   is the whole reason one drawing can serve three colours. */
.pc-tile-art {
  background: var(--primary-tint);
  color: var(--primary);
  padding: 26px 28px;
}
.pc-tile.is-lead .pc-tile-art { flex: 1 1 auto; display: grid; align-items: center; padding: 34px; }
.pc-tile.is-accent .pc-tile-art { background: var(--accent-tint); color: var(--accent-dark); }
.pc-tile.is-grow .pc-tile-art { background: var(--success-bg); color: var(--success); }
.pc-tile-art svg { display: block; width: 100%; height: auto; }
.pc-tile-text { padding: 22px 26px 26px; }
.pc-tile h3 { margin: 0 0 8px; font-size: var(--fs-md); }
.pc-tile.is-lead h3 { font-size: var(--fs-lg); }
.pc-tile p { margin: 0; font-size: var(--fs-base); color: var(--text-muted); line-height: 1.55; }

@media (max-width: 860px) {
  .pc-bento { grid-template-columns: minmax(0, 1fr); grid-auto-rows: auto; }
  .pc-tile.is-lead { grid-row: auto; }
}

/* ---- the steps ---------------------------------------------------------

   A stepper: filled nodes on a track, label and supporting text beneath. The
   previous attempt set a huge ghost numeral in the accent at a third opacity
   over a fading rule, which gave the band two washed-out elements and no
   solid one, and a faded amber on a tinted ground goes muddy rather than
   quiet.

   The connector is an offset on the step itself, not a rule behind the whole
   row: behind the row it has to be positioned against a guessed node height,
   and it slips the moment a label wraps to two lines on one step and one on
   the next. Drawing it on the node instead does not work either, and that is
   worth recording, because it looks like it should: a percentage width on a
   pseudo-element of the node resolves against the node's 52px, not against
   the column, so the line comes out a fixed stub however wide the step is.
   The offsets below are in px off the step box for that reason, and they are
   exact because the node is the one thing here with a fixed size. */
.pc-steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0 20px;
  counter-reset: none;
}
.pc-steps li { position: relative; }
.pc-step-node {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 52px;
  height: 52px;
  margin: 0 0 18px;
  border-radius: 50%;
  background: var(--primary);
  color: var(--on-fill);
  font-size: var(--fs-md);
  font-weight: var(--fw-bold);
  line-height: 1;
  /* M3 elevation 1, so the node reads as sitting on the track rather than
     being a hole cut in it. */
  box-shadow: var(--shadow-sm);
}
/* The track: from 14px past this node to 14px short of the next. The column
   gap is 20px, so the far end reaches 6px past this step's own right edge. */
.pc-steps li:not(:last-child)::after {
  content: "";
  position: absolute;
  left: 66px;
  right: -6px;
  top: 26px;
  height: 3px;
  border-radius: 2px;
  background: var(--primary-border);
}
.pc-steps h3 { margin: 0 0 8px; font-size: var(--fs-md); }
.pc-steps p {
  margin: 0;
  padding-right: 18px;
  font-size: var(--fs-base);
  color: var(--text-muted);
  line-height: 1.55;
}

/* Four across is a track; two across is two tracks with a gap down the middle,
   which reads as eight steps in no order. So it goes straight from four to
   one, and the track turns to run down the left of the column. */
@media (max-width: 940px) {
  .pc-steps { grid-template-columns: minmax(0, 1fr); gap: 0; }
  .pc-steps li {
    display: grid;
    grid-template-columns: 52px minmax(0, 1fr);
    gap: 0 20px;
    padding-bottom: 26px;
  }
  .pc-steps li:last-child { padding-bottom: 0; }
  .pc-step-node { grid-row: span 2; margin: 0; }
  .pc-steps li:not(:last-child)::after {
    left: 25px;
    right: auto;
    top: 66px;
    bottom: 12px;
    width: 3px;
    height: auto;
  }
  .pc-steps h3 { align-self: center; margin: 0; }
  .pc-steps p { padding: 8px 0 0; }
}

/* ---- the week -----------------------------------------------------------

   The second dark band, and the only one that shows the product rather than
   describing it. Putting it on the dark ground is what stops the lower half of
   the page being one long pale run, and the growth green reads brighter there
   than it ever does on white. */
.pc-week {
  padding: 76px 0;
  background:
    radial-gradient(800px 440px at 92% 100%, rgba(120, 170, 240, .16), transparent 60%),
    var(--ink-band);
  color: var(--on-band);
}
.pc-week-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(330px, 1fr));
  gap: 18px;
}
.pc-week-panel {
  display: flex;
  flex-direction: column;
  margin: 0;
  padding: 28px;
  /* No card fill: a white panel on the band would put the whole figure back on
     white and waste the band. An outlined surface at low alpha is M3's
     surface-container on a dark scheme. */
  background: rgba(255, 255, 255, .06);
  border: 1px solid rgba(255, 255, 255, .16);
  border-radius: var(--radius-xl);
}
.pc-week-panel figcaption { order: -1; }
.pc-week-panel h3 { margin: 0 0 8px; font-size: var(--fs-md); color: var(--on-band); }
.pc-week-panel figcaption p {
  margin: 0;
  font-size: var(--fs-base);
  color: var(--on-band-dim);
  line-height: 1.55;
}

/* Seven columns of three blocks. A block is a finished exercise and the stack
   fills from the bottom, so a part-finished day reads as part finished rather
   than as a different pattern of dots. */
.pc-week-chart {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 10px;
  margin: 26px 0 16px;
}
.pc-week-day { display: flex; flex-direction: column; align-items: center; gap: 8px; }
.pc-week-stack { display: flex; flex-direction: column; gap: 5px; width: 100%; }
.pc-week-stack span {
  height: 18px;
  border-radius: 5px;
  background: rgba(255, 255, 255, .14);
}
/* The count is also written out in the chart's aria-label, because four steps
   of one hue cannot be told apart by colour alone and colour is never the only
   encoding here. */
.pc-week-stack span.is-done { background: var(--grow-on-band); }
.pc-week-label { font-size: var(--fs-sm); font-weight: var(--fw-semibold); color: var(--on-band-dim); }
.pc-week-note { margin: auto 0 0; font-size: var(--fs-sm); color: var(--on-band-dim); line-height: 1.5; }

.pc-stages {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 12px;
  margin: 26px 0 16px;
  color: var(--grow-on-band);
}
.pc-stage { display: flex; flex-direction: column; align-items: center; gap: 10px; }
.pc-stage svg { display: block; width: 100%; max-width: 82px; height: auto; }
.pc-stage-label { font-size: var(--fs-sm); font-weight: var(--fw-semibold); color: var(--on-band-dim); }

/* ---- roles --------------------------------------------------------------

   Three cards of equal weight, and here that is the honest layout: the three
   roles are genuinely parallel, nobody among them matters more, and each has
   one line to say. Equal cards were wrong on the activities band, where the
   recording carries the product, and they are right on this one. */
.pc-roles {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 18px;
}
.pc-roles li {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  padding: 28px 26px;
  box-shadow: var(--shadow-sm);
  transition: transform var(--dur-short) var(--ease-standard),
              box-shadow var(--dur-short) var(--ease-standard);
}
.pc-role-ico {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 3.1em;
  height: 3.1em;
  margin-bottom: 16px;
  border-radius: 50%;
  background: var(--primary-tint);
  color: var(--primary);
}
.pc-roles li.is-accent .pc-role-ico { background: var(--accent-tint); color: var(--accent-dark); }
.pc-roles li.is-grow .pc-role-ico { background: var(--success-bg); color: var(--success); }
.pc-role-ico .pc-icon { width: 1.45em; height: 1.45em; }
.pc-roles h3 { margin: 0 0 8px; font-size: var(--fs-md); }
.pc-roles p { margin: 0; font-size: var(--fs-base); color: var(--text-muted); line-height: 1.55; }

/* ---- the notices --------------------------------------------------------

   One panel with rules between the items, not three cards. These are not
   three parallel things a visitor picks between, they are one set of terms,
   and a card each gave them the visual weight of features, which is the
   opposite of what they are. They close the page. */
.pc-notes {
  margin: 0;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
}
.pc-notes > div {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 20px;
  padding: 24px 28px;
}
.pc-notes > div + div { border-top: 1px solid var(--border); }
.pc-note-ico {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.6em;
  height: 2.6em;
  border-radius: var(--radius-sm);
  background: var(--primary-tint);
  color: var(--primary);
}
.pc-note-ico .pc-icon { width: 1.3em; height: 1.3em; }
.pc-notes dt { margin: 0 0 5px; font-size: var(--fs-md); font-weight: var(--fw-bold); }
.pc-notes dd { margin: 0; font-size: var(--fs-base); color: var(--text-muted); line-height: 1.55; }

@media (max-width: 560px) {
  .pc-notes > div { padding: 20px; gap: 16px; }
}

/* ---- interaction --------------------------------------------------------

   One state layer rule rather than a hover colour per component, which is how
   M3 keeps a white tile, a tinted chip and a filled button consistent. The
   lift is small on purpose: this page is read by people with tremor, and a
   card that jumps under a wobbling cursor is noise. */
@media (hover: hover) {
  .pc-tile:hover,
  .pc-roles li:hover { box-shadow: var(--shadow-md); transform: translateY(-3px); }
  .wa-btn.pc-btn-hero:hover { transform: translateY(-2px); }
}

@media (prefers-reduced-motion: reduce) {
  .pc-tile, .pc-roles li, .wa-btn.pc-btn-hero { transition: none; }
  .pc-tile:hover, .pc-roles li:hover, .wa-btn.pc-btn-hero:hover { transform: none; }
}

/* The display step is retuned for a phone rather than capped in px. At 2.4x
   the heading size the hero headline came out at 57.6px in a 325px column,
   which broke "speech routine" across three lines. The multiplier changes and
   the root does not, so a patient who has raised their text still moves this
   with it: 43.2 / 50.4 / 61.2px against 57.6 / 67.2 / 81.6 on a desktop.

   The kicker loses a little of its tracking at the same time. Uppercase at
   17px with 0.08em of letter-spacing needs about 330px for the longest of
   them, which is more than the column has. */
@media (max-width: 620px) {
  :root { --fs-4xl: calc(var(--pc-font-heading) * 1.8); }
  .pc-kicker { font-size: var(--fs-xs); letter-spacing: .05em; }
  .pc-display { max-width: none; }
}

/* ---- high contrast ------------------------------------------------------

   The two dark bands invert with the rest of the page: the whole mode is black
   ink on white, and a navy slab in the middle of it is the one thing that
   cannot be allowed to stay. --ink-band and its ink tokens do the inverting,
   so what is left here is removing the gradients painted over them and giving
   every tinted fill an edge to be held by.

   The landing is also the one screen where the media query is not redundant. A
   signed-out visitor has no account yet, so the page renders with the default
   settings and .pc-contrast is never applied: without the query these rules
   would be dead exactly where they are needed most. */
@media (prefers-contrast: more) {
  .pc-hero, .pc-week { background: var(--ink-band); }
  .pc-hero { border-bottom: 2px solid var(--border); }
  .pc-week { border-top: 2px solid var(--border); border-bottom: 2px solid var(--border); }
  .pc-hero-art { display: none; }
  .pc-lpbar { background: var(--surface); }
  .pc-band.is-tinted { background: var(--surface); border-top: 2px solid var(--border); border-bottom: 2px solid var(--border); }
  .pc-figs, .pc-tile, .pc-roles li, .pc-notes { border: 2px solid var(--border); box-shadow: none; }
  .pc-notes > div + div { border-top-width: 2px; }
  .pc-week-panel { background: none; border: 2px solid var(--border); }
  .pc-week-stack span { box-shadow: inset 0 0 0 2px var(--border); }
  .pc-role-ico, .pc-note-ico { border: 2px solid var(--border); }
  .pc-step-node { box-shadow: none; border: 2px solid var(--border); }
  .pc-steps li:not(:last-child) .pc-step-node::after { background: var(--border); }
  .wa-btn.pc-btn-hero { background: var(--primary); color: var(--on-fill); border: 2px solid var(--border); box-shadow: none; }
}

html.pc-contrast .pc-hero,
html.pc-contrast .pc-week { background: var(--ink-band); }
html.pc-contrast .pc-hero { border-bottom: 2px solid var(--border); }
html.pc-contrast .pc-week { border-top: 2px solid var(--border); border-bottom: 2px solid var(--border); }
html.pc-contrast .pc-hero-art { display: none; }
html.pc-contrast .pc-lpbar { background: var(--surface); }
html.pc-contrast .pc-band.is-tinted { background: var(--surface); border-top: 2px solid var(--border); border-bottom: 2px solid var(--border); }
html.pc-contrast .pc-figs,
html.pc-contrast .pc-tile,
html.pc-contrast .pc-roles li,
html.pc-contrast .pc-notes { border: 2px solid var(--border); box-shadow: none; }
html.pc-contrast .pc-notes > div + div { border-top-width: 2px; }
html.pc-contrast .pc-week-panel { background: none; border: 2px solid var(--border); }
html.pc-contrast .pc-week-stack span { box-shadow: inset 0 0 0 2px var(--border); }
html.pc-contrast .pc-role-ico,
html.pc-contrast .pc-note-ico { border: 2px solid var(--border); }
html.pc-contrast .pc-step-node { box-shadow: none; border: 2px solid var(--border); }
html.pc-contrast .pc-steps li:not(:last-child) .pc-step-node::after { background: var(--border); }
html.pc-contrast .wa-btn.pc-btn-hero {
  background: var(--primary);
  color: var(--on-fill);
  border: 2px solid var(--border);
  box-shadow: none;
}

/* ----------------------------------------------------------- sign-in (T1) --

   Two panels filling the viewport: context on the left, the form on the right.
   It was a 440px white card centred on a grey field, which is a login from a
   decade ago, and it also dropped everything the landing page had just
   established. Somebody who bookmarks /login never sees that page at all, so
   the left panel is where the three facts that matter live.

   The form column is the one that survives every breakpoint. Below 900px the
   panel goes and the form owns the screen, because a patient with tremor on a
   phone needs the whole width for two fields and a button, not a decorative
   half. */
.pc-auth {
  background: var(--bg);
  /* body is a flex column with min-height:100vh from the base rules, which is
     what lets the split fill the viewport without a second height. */
}
.pc-auth-split {
  flex: 1;
  display: grid;
  grid-template-columns: minmax(0, .95fr) minmax(0, 1.05fr);
}

/* ---- the context panel -------------------------------------------------- */
.pc-auth-side {
  display: flex;
  flex-direction: column;
  gap: 40px;
  padding: 40px 48px;
  background:
    radial-gradient(720px 460px at 20% 4%, rgba(120, 170, 240, .20), transparent 62%),
    radial-gradient(620px 420px at 90% 96%, rgba(161, 98, 7, .16), transparent 60%),
    var(--ink-band);
  color: var(--on-band);
}
.pc-auth-side-brand { display: inline-flex; align-items: center; text-decoration: none; }
/* The middle block takes the slack, so the brand sits at the top and the
   hospital at the foot however tall the window is. */
.pc-auth-side-body { margin: auto 0; max-width: 30rem; }
.pc-auth-side h2 {
  margin: 0 0 28px;
  font-size: var(--fs-2xl);
  font-weight: var(--fw-bold);
  line-height: 1.15;
  letter-spacing: -.02em;
  color: var(--on-band);
}
.pc-auth-points { list-style: none; margin: 0; padding: 0; display: grid; gap: 20px; }
.pc-auth-points li {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 16px;
  align-items: start;
  font-size: var(--fs-base);
  line-height: 1.55;
  color: var(--on-band-dim);
}
.pc-auth-points .pc-icon {
  width: 1.5em;
  height: 1.5em;
  color: var(--accent-on-band);
  margin-top: .1em;
}
/* UKM publish the mark in two versions and the panel takes the one drawn for
   a dark ground. The first attempt put the black-text version on a white chip,
   which did make it legible and also planted a bright white rectangle in the
   corner of a navy panel. Using their own white-text artwork is both tidier
   and more correct than giving their logo a background we invented for it.
   The dark-text version stays for the landing bar, which is white.

   Same collision as the landing bar, same three answers: the hospital's name
   leads, the mark is a mark, and a rule separates them. */
.pc-auth-side-org {
  display: flex;
  align-items: center;
  gap: 14px;
  margin: 0;
  font-size: var(--fs-base);
  font-weight: var(--fw-semibold);
  color: var(--on-band);
}
.pc-auth-side-org img {
  height: 38px;
  width: auto;
  display: block;
  padding-right: 14px;
  border-right: 1px solid rgba(255, 255, 255, .28);
}

/* ---- the form column ---------------------------------------------------- */
.pc-auth-main {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.pc-auth-form {
  /* auto margins centre it in whatever height is left once the funder footer
     has taken its own. */
  margin: auto;
  width: 100%;
  max-width: 27rem;
  padding: 48px 32px;
}
/* Shown only where the side panel is not. */
.pc-auth-mark { display: none; margin-bottom: 28px; text-decoration: none; }
.pc-auth-form h1 {
  margin: 0 0 8px;
  font-size: var(--fs-2xl);
  font-weight: var(--fw-bold);
  letter-spacing: -.02em;
}
.pc-auth-lede {
  margin: 0 0 30px;
  font-size: var(--fs-base);
  line-height: 1.55;
  color: var(--text-muted);
}

/* Inputs take the large shape step here. On the dashboard a field at this
   radius would be out of scale with the panels around it; on a page holding
   two of them it matches the button and the page reads as one object. */
.pc-auth-form .wa-input { border-radius: var(--radius-lg); }
.pc-auth-form label.fld { font-size: var(--fs-sm); color: var(--text); margin-bottom: 6px; }
.pc-auth-form label.fld + .wa-input,
.pc-auth-form .pc-pw { margin-bottom: 18px; }
.pc-auth-form .pc-pw .wa-input { margin-bottom: 0; }

/* The checkbox was 22px of inline style. A tick box is a tappable control and
   L1 has a floor for those, so it is sized against the touch variable and the
   whole row is the hit area, label included. */
.pc-auth-remember {
  display: flex;
  align-items: center;
  gap: 12px;
  /* Was calc(--pc-touch * .66), which rendered a 40px target. The header of
     auth/login.php states that this page clears the L1 floor on the default
     settings alone, because it is reached before Accessibility Settings can
     be; two controls on it did not, and this was the smaller of them. A
     checkbox is not a primary action, but it is still something a patient
     with a tremor has to hit on the first screen they ever meet. */
  min-height: var(--pc-touch);
  margin: 4px 0 22px;
  font-size: var(--fs-base);
  cursor: pointer;
}
.pc-auth-remember input {
  width: 24px;
  height: 24px;
  flex: none;
  accent-color: var(--primary);
  cursor: pointer;
}

.wa-btn.pc-auth-submit {
  min-height: 64px;
  margin: 0;
  border-radius: var(--radius-pill);
  font-size: var(--fs-md);
  box-shadow: var(--shadow-sm);
}
.wa-btn.pc-auth-submit .pc-icon { width: 1em; height: 1em; }

/* The note that replaces the sign-up and reset links this page deliberately
   does not have. Tinted rather than grey: it is the answer to the question a
   stuck patient is about to ask, and grey small print is where that answer
   goes to be missed. */
.pc-auth-note {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 12px;
  align-items: start;
  margin: 26px 0 0;
  padding: 16px 18px;
  border-radius: var(--radius-lg);
  background: var(--primary-tint);
  color: var(--primary-dark);
  font-size: var(--fs-sm);
  line-height: 1.55;
}
.pc-auth-note .pc-icon { width: 1.25em; height: 1.25em; margin-top: .15em; }

/* A control, not a sentence. It was an underlined line of body copy sitting
   under a tinted panel, which is the shape of a caption, and the only thing
   this element has to look like is something you can press. Sized against
   --pc-touch like every other target on the page. */
.pc-auth-back { margin: 24px 0 0; }
.pc-auth-back a {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  /* Same reasoning as .pc-auth-remember above: .8 rendered 48px on a page
     that cannot read a user's settings and therefore has to hold the floor
     by itself. */
  min-height: var(--pc-touch);
  padding: 10px 20px 10px 16px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--text);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  text-decoration: none;
}
.pc-auth-back a:hover {
  background: var(--primary-tint);
  border-color: var(--primary);
  color: var(--primary-dark);
}
.pc-auth-back .pc-icon { width: 1.15em; height: 1.15em; }

/* The funder footer belongs to the form column, so it stays under the form on
   a phone instead of being stranded beside a panel that is no longer there. */
.pc-auth-main .pc-funding { margin-top: 0; }

@media (max-width: 900px) {
  .pc-auth-split { grid-template-columns: minmax(0, 1fr); }
  .pc-auth-side { display: none; }
  .pc-auth-mark { display: inline-flex; }
  .pc-auth-form { padding: 36px 20px; }
}

/* ---- high contrast ------------------------------------------------------

   The panel inverts with everything else: --ink-band resolves to white there,
   so what is left is removing the gradients painted over it and giving the
   two tinted surfaces an edge, since both of their fills are about to
   disappear. As on the landing page, the media query is not redundant here:
   this screen is reached before S1 can be, so .pc-contrast is never set on
   it. */
@media (prefers-contrast: more) {
  .pc-auth-side { background: var(--ink-band); border-right: 2px solid var(--border); }
  .pc-auth-note { background: var(--surface); border: 2px solid var(--border); color: var(--text); }
  .wa-btn.pc-auth-submit { box-shadow: none; border: 2px solid var(--border); }
}

/* Azure clears 3:1 on the band, but only just, and the band has its own ink
   sitting right there at 13:1. */
.pc-auth-side a:focus-visible,
.pc-hero a:focus-visible { outline-color: var(--on-band); }

html.pc-contrast .pc-auth-side { background: var(--ink-band); border-right: 2px solid var(--border); }
html.pc-contrast .pc-auth-note { background: var(--surface); border: 2px solid var(--border); color: var(--text); }
html.pc-contrast .wa-btn.pc-auth-submit { box-shadow: none; border: 2px solid var(--border); }

/* ---------------------------------------------------------------- icons -- */

/* Sized in em so an icon tracks the text it sits beside: when a patient raises
   their font size through S1, the icons grow with the labels instead of
   shrinking into decoration. vertical-align keeps them on the text baseline
   rather than sitting proud of it. */
.pc-icon {
  width: 1.15em;
  height: 1.15em;
  flex: none;
  vertical-align: -0.2em;
}
.pc-icon-missing { color: var(--danger); }

/* Solid variant, for marks that read as filled shapes rather than outlines:
   a earned rating star, a selected state. */
.pc-icon-fill { fill: currentColor; stroke: none; }

/* Where an icon leads a label, the two are one unit. */
.pc-ico-row { display: inline-flex; align-items: center; gap: 8px; }

/* Material type mark on a library card. Same treatment as a tile icon: bigger,
   lighter stroke, brand colour. */
.mat-ico { font-size: var(--fs-3xl); line-height: 1; color: var(--primary); margin-bottom: 4px; }
.mat-ico .pc-icon { stroke-width: 1.5; }

/* ------------------------------------------------- audio feedback screen -- */

/* The written note is the reason this screen exists, so it is set at
   instruction size. It used to live in a table cell at small-print size, which
   is how a sentence written for a patient ends up being the least readable
   thing on the page. */
.pc-fb-label { font-size: var(--fs-base); color: var(--text-muted); margin: 0; }
.pc-fb-word {
  font-size: var(--fs-3xl);
  font-weight: var(--fw-bold);
  color: var(--primary-dark);
  line-height: 1.15;
  margin: 6px 0 16px;
}
.pc-fb-body {
  font-size: var(--fs-md);
  line-height: 1.6;
  margin: 0 0 14px;
  max-width: 60ch;
}
.pc-fb-rating {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  font-size: var(--fs-md);
  margin: 0;
}
.pc-fb-rating .stars { font-size: var(--fs-lg); }

/* Tinted so the note reads as something addressed to the patient rather than
   another panel of data about them. */
.pc-fb-note { background: var(--primary-tint); border-color: var(--primary); }

/* ------------------------------------------------------------ components -- */

.kpi-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin-bottom: 18px; }
/* A figure and what it counts. The label was 16px uppercase grey pressed
   against the number with 4px between them, which read as a form field rather
   than as a statistic. */
.kpi {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 20px 22px;
  box-shadow: var(--shadow-sm);
}
.kpi .lbl {
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: .06em;
}
.kpi .num {
  font-size: var(--fs-3xl);
  font-weight: var(--fw-bold);
  color: var(--primary);
  line-height: 1.05;
  letter-spacing: -.02em;
  margin-top: 10px;
}
.kpi .sub { font-size: var(--fs-sm); color: var(--text-muted); margin-top: 6px; }
.kpi.warn .num { color: var(--warning); }
.kpi.good .num { color: var(--success); }

/* A hairline plus a barely-there shadow, rather than a hard 1px box. The flat
   bordered rectangle is what made every panel sit on the same plane and read as
   an admin template.

   Padding went from 18px to 24px and the corner from medium to large. Both
   sound like fussing and both are the difference between a panel and a box:
   at 18px the content touched the edge on all four sides, which is the single
   most reliable way to make a card look like a table cell. */
.wcard {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 24px;
  margin-bottom: 18px;
  box-shadow: var(--shadow-sm);
}
/* The card's own heading row: title on the left, whatever the card offers on
   the right, and a rule under the pair. Without the rule a card with a busy
   body ran its title straight into the first row of content. */
.wcard h3 {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
  margin: -4px 0 18px;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--border);
  font-size: var(--fs-md);
}
.wcard h3 .sm { font-size: var(--fs-sm); color: var(--text-muted); font-weight: var(--fw-medium); }
.wcard h3 .wa-btn { margin-top: 0; width: auto; }
/* space-between is right for two children and wrong for three: where a card
   puts its icon, its title and a control in the heading, the title ended up
   floating in the middle of the rule with the icon stranded at the far left.
   The row packs from the left and only the trailing control is pushed away,
   so a heading reads as a heading however many pieces it is made of. */
.wcard h3 { justify-content: flex-start; }
.wcard h3 > .wa-btn:last-child,
.wcard h3 > .sm:last-child { margin-left: auto; }

.card-2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 18px; }
.card-3 { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 18px; }

.wa-input {
  width: 100%;
  padding: 12px 14px;
  margin-bottom: 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  font-size: var(--fs-base);
  font-family: inherit;
  min-height: var(--pc-touch);
  background: var(--surface);
  color: var(--text);
  transition: border-color .12s ease, box-shadow .12s ease;
}
/* The field the patient is typing in should be obvious without relying on the
   focus outline alone, which some browsers suppress for pointer interaction. */
.wa-input:hover { border-color: var(--border-strong); }
.wa-input:focus {
  border-color: var(--primary);
  /* A tint of --primary, and it has to be restated in rgb here because a CSS
     variable cannot carry an alpha on its own. It lagged two palettes behind:
     first the wireframe's #2E86AB, then the teal that replaced it. */
  box-shadow: 0 0 0 3px rgba(29, 78, 137, .16);
  outline: none;
}
.wa-input:focus-visible, .wa-btn:focus-visible, a:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
}
label.fld { display: block; font-size: var(--fs-xs); font-weight: var(--fw-bold); color: var(--text-muted); margin-bottom: 4px; }

/* Reveal control sits inside the field as quiet text, the way GOV.UK and the
   modern bank apps do it. It was a bordered box welded to the right edge of the
   input, which is the 2010 form pattern and read as a second, competing field.

   Absolute with top and bottom both pinned, so the button is exactly as tall as
   the input whatever --pc-touch and the font size are set to. It keeps the full
   60px hit height while looking like nothing more than a word. */
.pc-pw { position: relative; margin-bottom: 12px; }
.pc-pw .wa-input {
  margin-bottom: 0;
  padding-right: 60px;
}
.pc-pw-toggle {
  position: absolute;
  /* Inset by the width of the input's 1px border, so the focus ring below
     lands inside the field instead of sitting on top of its border. */
  top: 1px;
  bottom: 1px;
  right: 1px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  /* Square, since the word next to the glyph has gone. The field reserves
     104px of right padding for the old label, which would now leave the icon
     floating in the middle of a gap, so both numbers move together. */
  width: 56px;
  padding: 0;
  background: transparent;
  border: 0;
  /* The field rounds by --radius-md. This was a hardcoded 10px, which left the
     focus ring a different shape from the corner it sits in. Less the 1px inset
     above, so the two curves stay concentric. */
  border-radius: 0 calc(var(--radius-md) - 1px) calc(var(--radius-md) - 1px) 0;
  color: var(--primary-dark);
  font-family: inherit;
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  cursor: pointer;
}
/* Neither a fill nor an underline on hover. The fill painted --bg while the
   field's ground is --surface, so a grey block of a visibly different colour
   sat inside a white field. The underline that replaced it drew a line under
   the word and made it read as a link. Darkening the ink from blue to near
   black is the one cue that adds no shape and no line, and it holds in
   high-contrast mode where --bg and --surface are both #FFFFFF. */
.pc-pw-toggle:hover { color: var(--text); }
/* display:inline-flex on its own beats the [hidden] attribute, which is only
   a UA display:none, so both eyes rendered side by side. The attribute
   selector has to win back. */
.pc-pw-eye { display: inline-flex; }
.pc-pw-eye[hidden] { display: none; }
.pc-pw-toggle .pc-icon { width: 1.5em; height: 1.5em; }
/* Without this the button falls back to the browser default, a square outline
   drawn around an absolutely positioned box, which crosses both the field's
   border and its focus ring. Negative offset keeps the ring inside the field. */
.pc-pw-toggle:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: -3px;
}
.pc-pw-toggle .pc-icon { width: 1.05em; height: 1.05em; }

html.pc-contrast .pc-pw-toggle { text-decoration: underline; }

/* Tick boxes sit inside flex labels beside a run of text. Without this they are
   the flex item that gives way, so a box asked to be 24px renders as a 13px
   sliver on a phone, the smallest thing on screen, on the screens where an
   elderly user has to hit it. (.mode-card radios are positioned out of flow
   and are unaffected.) */
label input[type="checkbox"], label input[type="radio"] { flex-shrink: 0; }

.wa-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  min-height: var(--pc-touch);
  padding: 12px 18px;
  background: var(--primary);
  color: var(--on-fill);
  border: none;
  border-radius: var(--radius-sm);
  font-size: var(--fs-base);
  font-weight: var(--fw-bold);
  font-family: inherit;
  cursor: pointer;
  margin-top: 6px;
  text-decoration: none;
}
.wa-btn:hover { background: var(--primary-dark); }
/* There is no .wa-btn.s. It was a solid fill of --secondary, which after the
   palette change was a dark amber: a brown Download PDF beside a green
   Download Excel read as two warnings rather than as two exports.

   A tonal navy replaced it for about ten minutes and was wrong for a reason
   worth writing down. Its fill cleared 11:1 for the label and 1.17:1 for the
   button's own edge against the surface, and WCAG 1.4.11 asks 3:1 of the
   boundary of a control, not only of its text. That is the same fault this
   file already records against the old --border on form fields. A pale tint
   can be a panel; it cannot be a button on its own.

   So the two tiers that were already here do the work: .wa-btn filled for a
   primary action, .wa-btn.ghost outlined for a secondary one. Both are navy.
   The three former .s buttons were re-pointed at whichever of those they
   actually are. */
.wa-btn.warn { background: var(--warning); }
.wa-btn.danger { background: var(--danger); }
.wa-btn.success { background: var(--success); }
.wa-btn.w { width: auto; }
.wa-btn.ghost { background: var(--surface); color: var(--primary); border: 2px solid var(--primary); }
.wa-btn.ghost:hover { background: var(--primary-tint); }
.wa-btn[disabled] { opacity: .5; cursor: not-allowed; }

/* Tappable controls need clear separation so a tremor cannot hit the neighbour. */
.btn-row { display: flex; gap: var(--pc-gap); flex-wrap: wrap; }
.btn-row .wa-btn { margin-top: 0; }

/* Rows that can be scanned by an eye that is not young. 10px of vertical
   padding on an 18px page put the rows closer together than the text is tall,
   which is the density of a spreadsheet and the reason these tables read as
   an export rather than as a screen.

   The header keeps its grey but loses the filled band: a strip of --bg inside
   a white card drew a box around the head of every table and made the card
   look like two stacked panels. A single firmer rule does the same job. */
table.wa { width: 100%; border-collapse: collapse; font-size: var(--fs-base); }
table.wa th, table.wa td { border-bottom: 1px solid var(--border); padding: 16px 14px; text-align: left; }
table.wa th {
  padding-top: 0;
  padding-bottom: 12px;
  border-bottom: 2px solid var(--border);
  color: var(--text-muted);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  text-transform: uppercase;
  letter-spacing: .06em;
  white-space: nowrap;
}
table.wa tbody tr:last-child td { border-bottom: 0; }
table.wa tbody tr:hover { background: var(--bg); }
/* A row's controls belong at its end, and they are secondary to the row
   itself: full-size filled buttons in every line of a four-line table is four
   primary actions competing with the data they act on.

   nowrap because the cell is as narrow as the content lets it be, and without
   it Edit and Delete stacked one above the other and made every row twice as
   tall as it needed to be. */
table.wa td .btn-row { justify-content: flex-end; gap: 8px; flex-wrap: nowrap; }
table.wa td { white-space: normal; }
table.wa td:has(.btn-row) { white-space: nowrap; width: 1%; }
table.wa td .wa-btn {
  margin-top: 0;
  width: auto;
  /* Was calc(--pc-touch * .72). That floor is 32px on a staff screen, and
     padding alone was carrying the real height to 42px, under the 44px of
     WCAG 2.5.5. It also left the Reports row mixing a 42px button with the
     44px icon buttons beside it, so the row did not line up either. The
     token gives 44 for staff and 60 on any patient screen that grows a
     table button later. */
  min-height: var(--pc-touch);
  padding: 8px 16px;
  font-size: var(--fs-sm);
  white-space: nowrap;
}

/* Destructive actions in a list are outlined, not filled. A solid red button
   on every row makes deleting look like the thing the screen is for, and in a
   library of clinical material that is the opposite of true. It keeps the
   danger colour, so it is still unmistakably the destructive one, and it
   fills on hover once the pointer has committed to it. */
.wa-btn.danger.is-quiet,
table.wa td .wa-btn.danger {
  background: var(--surface);
  color: var(--danger);
  border: 2px solid var(--danger-border);
}
.wa-btn.danger.is-quiet:hover,
table.wa td .wa-btn.danger:hover {
  background: var(--danger);
  border-color: var(--danger);
  color: var(--on-fill);
}
.table-wrap { overflow-x: auto; }

.pill { display: inline-block; padding: 3px 10px; border-radius: var(--radius-pill); font-size: var(--fs-xs); font-weight: var(--fw-bold); }
.pill.blue { background: var(--primary-tint); color: var(--primary-dark); }
.pill.green { background: var(--success-bg); color: var(--success); }
.pill.amber { background: var(--warning-bg); color: var(--warning); }
.pill.grey { background: var(--bg); color: var(--text-muted); }
.pill.accent { background: var(--accent-tint); color: var(--accent-dark); }
/* Was .pill.pink, on the primary tint, in a plum that belonged to no other
   colour here. It now sits on its own hue's tint like every other pill. */

.avatar-sm {
  width: 32px; height: 32px; border-radius: 50%;
  background: var(--primary); color: var(--on-fill);
  display: inline-flex; align-items: center; justify-content: center;
  font-weight: var(--fw-bold); font-size: var(--fs-sm); flex-shrink: 0;
}

.stars { color: var(--warning); white-space: nowrap; display: inline-flex; gap: 2px; vertical-align: middle; }
.stars .off { color: var(--border); }

.muted { color: var(--text-muted); }
.small { font-size: var(--fs-sm); }
.center { text-align: center; }

/* ---- toasts -------------------------------------------------------------

   The look is toastr's, and it is toastr's because the stylesheet is:
   assets/css/vendor/toastr.min.css is the file ipitvet_ptpk loads, copied in
   unmodified and linked before this one. Nothing below restyles it. Three
   earlier attempts reproduced it by eye and none of them matched, which is
   the whole reason for vendoring the real thing.

   What is here is the motion toastr does in jQuery, done in CSS because this
   project has no jQuery: fade in over 300ms, hold, fade out over 1000ms, and
   a progress bar shrinking across the 5000ms in between. toastr's own
   numbers, so the two systems feel the same.

   The entrance is an animation rather than a transition toggled by script, so
   a toast is never invisible waiting for JavaScript. The exit needs the class
   the script adds, which is the half that can afford to depend on it: with
   scripting off nothing dismisses the toast and it simply stays. */
@keyframes pc-toastr-in {
  from { opacity: 0; }
  /* .8 and not 1: that is the opacity toastr's own stylesheet rests at, and
     overshooting it would make the toast flash brighter than it settles. */
  to   { opacity: .8; }
}
#toast-container > .toast {
  animation: pc-toastr-in 300ms ease-out both;
}
#toast-container > .toast.is-hiding {
  opacity: 0;
  transition: opacity 1000ms ease-in;
}

/* The countdown. toastr resizes this from JavaScript on an interval; the same
   thing in CSS, paused by the class the script sets while the toast is being
   read. transform rather than width, so it is composited and does not lay the
   toast out again sixty times a second. */
.toast-progress {
  width: 100% !important;
  transform-origin: left center;
  animation: pc-toastr-countdown 5000ms linear both;
}
#toast-container > .toast.is-held .toast-progress { animation-play-state: paused; }
@keyframes pc-toastr-countdown {
  from { transform: scaleX(1); }
  to   { transform: scaleX(0); }
}

/* Two departures from the vendored file, both about hitting the thing.
   toastr's close button is a 20px glyph tucked into the corner, which is
   under half the 44px minimum and well under what L1 asks of a control on a
   page read by people with tremor. It keeps its position and its look and
   gains a target. */
.toast-close-button {
  min-width: 34px;
  min-height: 34px;
  padding: 0 4px;
}
/* toastr fixes its toasts at 300px. Body text here is 18px rather than the
   14px that library assumed, so a message would set four words to a line. */
@media (min-width: 601px) {
  #toast-container > div { width: 22rem; }
}
@media (max-width: 600px) {
  #toast-container { left: 10px; right: 10px; width: auto; }
  #toast-container > div { width: auto; }
}

@media (prefers-reduced-motion: reduce) {
  #toast-container > .toast { animation: none; opacity: .8; }
  #toast-container > .toast.is-hiding { transition: none; }
  .toast-progress { animation: none; display: none; }
}

/* The word a patient is being asked to say. It is the whole screen, so it is
   sized like it: 3.4x the heading step, about 82px on the standard setting
   and larger on the other two. Derived rather than fixed, so S1 still moves
   it. The letter-spacing opens up because a single capital at this size sits
   oddly tight, and tabular figures keep a numeric prompt from shifting. */
.pc-prompt {
  padding: 28px 0 24px;
  font-size: calc(var(--pc-font-heading) * 3.4);
  font-weight: var(--fw-bold);
  line-height: 1.05;
  letter-spacing: .01em;
  color: var(--primary-dark);
  font-variant-numeric: tabular-nums;
}

/* ---- the focus band -----------------------------------------------------

   The therapist dashboard used to open with four identical boxes. Four
   figures of equal weight claim that all four matter equally, and two of them
   were counts of things that already exist while two were work waiting to be
   done. This band carries the second pair, on the dark ground the landing
   page uses for the thing it most wants read, and each card is a link into
   the queue it counts rather than a number to be looked at.

   The empty state is not an afterthought here. "Nothing waiting" is the most
   useful sentence this screen can produce, so it gets the band to itself
   rather than four zeroes in grey boxes. */
.pc-focus {
  padding: 26px 28px;
  margin-bottom: 18px;
  border-radius: var(--radius-lg);
  background:
    radial-gradient(620px 340px at 88% 8%, rgba(120, 170, 240, .18), transparent 62%),
    var(--ink-band);
  color: var(--on-band);
}
.pc-focus .pc-kicker { margin-bottom: 18px; }
.pc-focus.is-clear { padding: 22px 28px; }
.pc-focus.is-clear .pc-kicker { margin-bottom: 10px; }
.pc-focus-clear {
  margin: 0;
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  line-height: 1.45;
  color: var(--on-band);
  max-width: 60ch;
}

.pc-focus-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 16px;
}
.pc-focus-card {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-rows: auto auto;
  gap: 2px 16px;
  align-items: center;
  padding: 20px 22px;
  border-radius: var(--radius-md);
  background: rgba(255, 255, 255, .08);
  border: 1px solid rgba(255, 255, 255, .18);
  color: var(--on-band);
  text-decoration: none;
  transition: background var(--dur-short) var(--ease-standard),
              transform var(--dur-short) var(--ease-standard);
}
.pc-focus-card:hover { background: rgba(255, 255, 255, .14); transform: translateY(-2px); }
.pc-focus-ico {
  grid-row: span 2;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.8em;
  height: 2.8em;
  border-radius: 50%;
  background: rgba(255, 255, 255, .14);
  color: var(--accent-on-band);
}
.pc-focus-ico .pc-icon { width: 1.3em; height: 1.3em; }
.pc-focus-card b {
  font-size: var(--fs-2xl);
  font-weight: var(--fw-bold);
  line-height: 1;
  letter-spacing: -.02em;
}
.pc-focus-what { font-size: var(--fs-base); color: var(--on-band-dim); line-height: 1.4; }
.pc-focus-go {
  grid-column: 2;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 12px;
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  color: var(--accent-on-band);
}
.pc-focus-go .pc-icon { width: 1.1em; height: 1.1em; }

@media (prefers-reduced-motion: reduce) {
  .pc-focus-card { transition: none; }
  .pc-focus-card:hover { transform: none; }
}

/* The figures that are not work: one quiet line, not two more cards. */
.pc-facts {
  list-style: none;
  margin: 0 0 18px;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 12px 34px;
}
.pc-facts li {
  display: flex;
  align-items: center;
  gap: 12px;
  font-size: var(--fs-base);
  color: var(--text-muted);
}
.pc-facts-ico {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.2em;
  height: 2.2em;
  border-radius: 50%;
  background: var(--primary-tint);
  color: var(--primary);
  flex: none;
}
.pc-facts-ico .pc-icon { width: 1.1em; height: 1.1em; }
.pc-facts b { font-size: var(--fs-lg); font-weight: var(--fw-bold); color: var(--text); }
.pc-facts em { display: block; font-style: normal; font-size: var(--fs-sm); }

html.pc-contrast .pc-focus { background: var(--ink-band); border: 2px solid var(--border); }
html.pc-contrast .pc-focus-card { background: none; border: 2px solid var(--border); }
html.pc-contrast .pc-facts-ico { border: 2px solid var(--border); }

/* ---- the state of the caseload, in four figures -------------------------

   Replaces a 54px strip of two never-changing counts. Every tile carries a
   comparison, because a number with nothing beside it cannot be acted on.

   This is also where colour starts meaning something on this screen. Until
   now the dashboard was navy for everything and gold for "today", so colour
   carried no information at all and the page read as one flat block. Here:

     green  on track, nothing to do
     amber  slipping, worth a look
     red    stopped, needs action

   The colour is never the message on its own. Each tile writes the finding
   out in words, and the arrow glyphs differ in shape as well as hue. */
.pc-kpis {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 14px;
  margin: 0 0 18px;
  padding: 0;
}
.pc-kpi {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 16px 18px 16px 20px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  /* The status stripe. A left edge rather than a tinted card: a wash of
     colour behind a figure drags its contrast down, and these figures are
     the largest text on the screen. */
  overflow: hidden;
}
.pc-kpi::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 4px;
  background: var(--border);
}
.pc-kpi.is-good::before,
.pc-kpi.is-up::before    { background: var(--success); }
.pc-kpi.is-warn::before  { background: var(--accent); }
.pc-kpi.is-bad::before,
.pc-kpi.is-down::before  { background: var(--danger); }
/* is-flat keeps the neutral --border stripe from the base rule: a week that
   matched the one before it is not news and should not be coloured as if it
   were. */
.pc-kpi.is-quiet::before { background: transparent; }

.pc-kpi-label {
  font-size: var(--fs-sm);
  color: var(--text-muted);
  font-weight: var(--fw-bold);
}
.pc-kpi-figure {
  font-size: var(--fs-3xl);
  font-weight: var(--fw-bold);
  line-height: 1.05;
  letter-spacing: -.02em;
  color: var(--text);
}
/* The denominator is context, not the figure. At the same size "1/4" reads
   as one number four characters long. */
.pc-kpi-of {
  font-size: var(--fs-lg);
  font-weight: var(--fw-bold);
  color: var(--text-muted);
}
.pc-kpi-delta {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  font-size: var(--fs-sm);
  color: var(--text-muted);
  margin-top: 2px;
}
.pc-kpi-delta .pc-icon { flex: none; }
.pc-kpi-was { color: var(--text-muted); }

.pc-kpi.is-up   .pc-kpi-delta { color: var(--success); }
.pc-kpi.is-down .pc-kpi-delta { color: var(--danger); }
.pc-kpi.is-bad  .pc-kpi-delta { color: var(--danger); font-weight: var(--fw-bold); }
.pc-kpi.is-warn .pc-kpi-delta { color: var(--accent); font-weight: var(--fw-bold); }
.pc-kpi.is-good .pc-kpi-delta { color: var(--success); }

html.pc-contrast .pc-kpi { border-width: 2px; }
html.pc-contrast .pc-kpi::before { width: 6px; }

/* "optional" beside a field label. Marking the optional field is the right
   way round here: all but two of the fields on the patient form are
   optional, so marking the required ones would shout, and a therapist who
   cannot tell which boxes they may skip fills them all in. */
.pc-optional {
  margin-left: 8px;
  padding: 2px 9px;
  border-radius: var(--radius-pill);
  background: var(--primary-tint);
  color: var(--text-muted);
  font-size: var(--fs-xs);
  font-weight: var(--fw-normal);
  text-transform: none;
  letter-spacing: 0;
  vertical-align: 1px;
}
html.pc-contrast .pc-optional { outline: 1px solid var(--border); }

/* ---- the learning library -----------------------------------------------

   Cards that lead with the material itself. The old grid opened every one of
   the seven with the same grey outline icon, while the database was already
   holding a real frame for each video in uploads/thumbnails and a duration in
   seconds, and the infographic is an image file. None of it reached the page.

   Sizes still sit on the L1 floor from RESEARCH.md: the whole cover and title
   is one target, and the Save control clears the touch minimum on its own. A
   library for people with a tremor is not made usable by shrinking it. */
.pc-lib-filters {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px 16px;
  margin-bottom: 14px;
}
.pc-lib-search { display: flex; flex-wrap: wrap; gap: 10px; flex: 1 1 380px; margin: 0; }
.pc-lib-search-field { position: relative; display: flex; flex: 1 1 240px; }
.pc-lib-search-field .pc-icon {
  position: absolute;
  left: 14px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--text-muted);
  pointer-events: none;
}
.pc-lib-search-field .wa-input { margin: 0; padding-left: 46px; }
.pc-lib-search .wa-btn { width: auto; min-width: 120px; margin: 0; }
.pc-lib-saved { width: auto !important; margin: 0 !important; }

/* Topics. Quieter than the type control above them: six narrowings, not the
   main choice on the screen. */
.pc-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.pc-chips a {
  display: inline-flex;
  align-items: center;
  /* Topic chips carry a category icon on the Q&A screen. Without this the
     glyph sits flush against the first letter. */
  gap: 7px;
  /* var(--pc-touch), not a fixed 44px. These controls were built for the
     therapist library and then reused on the patient one, where the L1 floor
     is 60px: hardcoding 44 quietly dropped the patient-facing filters below
     the floor while the buttons beside them stayed at 60. The token gives
     staff 44 and patients 60 without either being written down here. */
  min-height: var(--pc-touch);
  padding: 8px 16px;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--text-muted);
  font-size: var(--fs-sm);
  text-decoration: none;
  transition: border-color var(--dur-short) var(--ease-standard),
              color var(--dur-short) var(--ease-standard);
}
.pc-chips a:hover { border-color: var(--primary); color: var(--primary); }
.pc-chips a[aria-current] {
  background: var(--primary);
  border-color: var(--primary);
  color: var(--on-fill);
  font-weight: var(--fw-bold);
}

.pc-cards {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  /* 280px, not 240: a card with a 16:9 cover needs the width or the image
     becomes a letterbox strip and stops being recognisable. */
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 18px;
}
.pc-card {
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  transition: transform var(--dur-short) var(--ease-standard),
              box-shadow var(--dur-short) var(--ease-standard);
}
.pc-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); }
.pc-card:focus-within { outline: 3px solid var(--focus); outline-offset: 2px; }

.pc-card-main {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  text-decoration: none;
  color: inherit;
}
/* The whole card is the link, so its own focus ring is redundant with the
   :focus-within above and would draw twice. */
.pc-card-main:focus-visible { outline: none; }

.pc-card-cover {
  position: relative;
  display: block;
  aspect-ratio: 16 / 9;
  max-width: 100%;
  background: var(--primary-tint);
  overflow: hidden;
}
.pc-card-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* An infographic is a tall poster. Cropping it to 16:9 cuts the content out,
   so it is fitted whole against a tinted ground instead. */
.pc-card[data-type="infographic"] .pc-card-cover img { object-fit: contain; }

.pc-card-cover-mark {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--primary);
  opacity: .55;
}
.pc-card[data-type="pdf"] .pc-card-cover { background: #FBEBE6; }
.pc-card[data-type="pdf"] .pc-card-cover-mark { color: #9A3412; }

.pc-card-play {
  position: absolute;
  inset: 0;
  margin: auto;
  width: 58px;
  height: 58px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  /* Dark disc behind a white glyph: a play mark laid straight on a video
     frame disappears against whatever happens to be bright in it. */
  background: rgba(15, 39, 71, .72);
  color: #FFFFFF;
  transition: background var(--dur-short) var(--ease-standard);
}
.pc-card:hover .pc-card-play { background: rgba(15, 39, 71, .9); }

.pc-card-time {
  position: absolute;
  right: 10px;
  bottom: 10px;
  padding: 3px 9px;
  border-radius: var(--radius-sm);
  background: rgba(15, 39, 71, .82);
  color: #FFFFFF;
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  letter-spacing: .02em;
}

.pc-card-body {
  display: flex;
  flex-direction: column;
  gap: 6px;
  flex: 1 1 auto;
  padding: 14px 16px 12px;
}
.pc-card-title {
  display: block;
  font-size: var(--fs-base);
  font-weight: var(--fw-bold);
  line-height: 1.3;
  color: var(--text);
}
.pc-card-desc {
  display: block;
  font-size: var(--fs-sm);
  color: var(--text-muted);
  line-height: 1.45;
}
.pc-card-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  /* Pinned down so the meta line sits level across a row of cards whose
     descriptions differ in length. */
  margin-top: auto;
  padding-top: 8px;
}
.pc-card-views {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

.pc-card-save { margin: 0; border-top: 1px solid var(--border); }
.pc-card-save button {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  width: 100%;
  min-height: var(--pc-touch);
  padding: 10px 16px;
  border: none;
  background: none;
  color: var(--primary);
  font-family: inherit;
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  cursor: pointer;
  transition: background var(--dur-short) var(--ease-standard);
}
.pc-card-save button:hover { background: var(--primary-tint); }
.pc-card-save button.is-saved { color: var(--accent); }

html.pc-contrast .pc-card { border-width: 2px; }
html.pc-contrast .pc-chips a { border-width: 2px; }
html.pc-contrast .pc-card-play,
html.pc-contrast .pc-card-time { background: #000000; }

/* ---- staff controls -----------------------------------------------------

   Icon-only buttons, allowed here and nowhere else.

   The L1 rule in RESEARCH.md forbids them, and for patient screens that is
   right: a patient meets this app a few times a week and should never have
   to decode a glyph. A therapist opens the same four screens every working
   day and reads "Open PDF Excel" on every row of every table, which is how a
   clinical tool ends up looking like a toolbar from 2008.

   The relaxation is paid for, not just taken:
     - every one carries an aria-label naming the action and its subject,
       so a screen reader announces more than the sighted user gets;
     - every one carries a title, so a hover explains it;
     - the target is var(--pc-touch), which is 44px for staff and grows if
       that person has asked for bigger controls in Settings;
     - the primary action on a row keeps its words. Only the secondary ones
       lose them, so a row is never a line of unlabelled squares.

   Destructive actions stay behind the existing data-confirm prompt and take
   the danger colour on hover, so a trash glyph is never one stray click. */
.pc-iconbtn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--pc-touch);
  height: var(--pc-touch);
  flex: none;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--text-muted);
  cursor: pointer;
  text-decoration: none;
  transition: background var(--dur-short) var(--ease-standard),
              border-color var(--dur-short) var(--ease-standard),
              color var(--dur-short) var(--ease-standard);
}
.pc-iconbtn:hover {
  background: var(--primary-tint);
  border-color: var(--primary);
  color: var(--primary);
}
.pc-iconbtn:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }
.pc-iconbtn.is-danger:hover {
  background: var(--danger-bg);
  border-color: var(--danger);
  color: var(--danger);
}
.pc-iconbtn .pc-icon { width: 22px; height: 22px; }

/* A row of actions at the end of a table row or card. */
.pc-actions { display: inline-flex; align-items: center; gap: 8px; }
.pc-actions form { margin: 0; }
.pc-actions .wa-btn { width: auto; min-width: 0; margin: 0; }

html.pc-contrast .pc-iconbtn { border-width: 2px; }

/* ---- breadcrumb ---------------------------------------------------------

   Only on screens that sit under a menu row. A caregiver running an exercise
   was the case that prompted it: the exercise screens are reached from Start
   Session but are not a row of their own, so the menu had nothing lit and the
   page gave no clue which section it belonged to.

   The separator is drawn by CSS rather than typed into the markup, so it is
   never read out as a word by a screen reader walking the list. */
.pc-crumb { margin: 0 0 6px; }
.pc-crumb ol {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin: 0;
  padding: 0;
  font-size: var(--fs-sm);
  color: var(--text-muted);
}
.pc-crumb li { display: inline-flex; align-items: center; gap: 6px; }
.pc-crumb li + li::before {
  content: "";
  width: 6px;
  height: 6px;
  border-top: 2px solid var(--border-strong, var(--border));
  border-right: 2px solid var(--border-strong, var(--border));
  transform: rotate(45deg);
  flex: none;
}
.pc-crumb a {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--primary);
  text-decoration: none;
  font-weight: var(--fw-bold);
  /* A trail is read far more often than it is pressed, so it is not given a
     full touch target of its own; the same destination is one row away in the
     menu, at the full size. It still clears the 24px AA minimum. */
  min-height: 28px;
}
.pc-crumb a:hover { text-decoration: underline; }
.pc-crumb [aria-current] { color: var(--text-muted); }

html.pc-contrast .pc-crumb a { text-decoration: underline; }

/* A filter bar, replacing the two stacked rows of .wa-btn pills that every
   staff list screen carried. Nine pills that all look like the primary
   action is not a filter, it is a wall. */
.pc-filterbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 18px;
  margin-bottom: 16px;
}
.pc-filterbar-group { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.pc-filterbar-label {
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  color: var(--text-muted);
}

@media (max-width: 1100px) {
  .pc-kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 560px) {
  .pc-kpis { grid-template-columns: minmax(0, 1fr); }
}

/* A fact with a ceiling. The storage figure is the only number on the
   materials screen that can run out, and a bare "105 MB" cannot say whether
   that is nearly all of it or almost none. */
.pc-facts-meter { align-items: center; }
.pc-meter-text b { display: block; line-height: 1.15; }
.pc-meter-text > span { font-size: var(--fs-sm); }
.pc-meter {
  width: 140px;
  height: 10px;
  border-radius: var(--radius-pill);
  background: var(--primary-tint);
  /* The track needs an edge of its own. Filled at 2 per cent, a borderless
     track on this near-white ground reads as a rendering fault rather than
     as a bar that is nearly empty. */
  box-shadow: inset 0 0 0 1px var(--border);
  overflow: hidden;
  flex: none;
}
.pc-meter > span {
  display: block;
  height: 100%;
  /* A real but tiny quantity must still be visible. Below this the fill
     rounds away to nothing and the bar lies about there being no data. */
  min-width: 5px;
  border-radius: inherit;
  background: var(--primary);
}
html.pc-contrast .pc-meter { border: 2px solid var(--border); }

/* ---- disclosure ---------------------------------------------------------

   A native <details>, styled as a control. Used for a form that is wanted
   occasionally and was previously open at all times, holding half the width
   of the materials screen for a task done once a week.

   No script anywhere in this block. The element opens on its own, which is
   the point of choosing it: this screen works with JavaScript switched off
   and the control that reveals the form must not be the thing that ends
   that. */
.pc-disclose {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  margin: 0 0 22px;
  overflow: hidden;
}
.pc-disclose > summary {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: var(--pc-touch);
  padding: 10px 18px;
  cursor: pointer;
  font-weight: var(--fw-bold);
  color: var(--primary);
  list-style: none;            /* Safari and Firefox */
  transition: background var(--dur-short) var(--ease-standard);
}
.pc-disclose > summary::-webkit-details-marker { display: none; }
.pc-disclose > summary:hover { background: var(--primary-tint); }
.pc-disclose > summary:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: -3px;
}
.pc-disclose-label { display: inline-flex; align-items: center; gap: 10px; }
.pc-disclose-hint {
  font-weight: var(--fw-normal);
  font-size: var(--fs-sm);
  color: var(--text-muted);
}
/* The arrow points right when shut and down when open: the state is in the
   glyph, not only in whether a panel happens to be visible below it. */
.pc-disclose-chev {
  margin-left: auto;
  display: inline-flex;
  transition: transform var(--dur-short) var(--ease-standard);
}
.pc-disclose[open] > summary { border-bottom: 1px solid var(--border); }
.pc-disclose[open] .pc-disclose-chev { transform: rotate(90deg); }

.pc-upload { padding: 18px; }
.pc-upload-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 4px 18px;
}
.pc-upload-wide { grid-column: 1 / -1; }
.pc-upload-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid var(--border);
}
/* .wa-btn is width:100% by default, which is right for a form column and
   wrong for a footer row: it pushed the button onto its own line below the
   checkbox it is meant to sit beside. */
.pc-upload-foot .wa-btn { width: auto; min-width: 200px; margin: 0; }

.pc-check {
  display: flex;
  gap: 12px;
  align-items: center;
  min-height: var(--pc-touch);
  cursor: pointer;
}
.pc-check input { width: 24px; height: 24px; flex: none; }
.pc-check em {
  display: block;
  font-style: normal;
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

/* ---- the material library -----------------------------------------------

   Cards, not a table. Seven items each carrying a name and three short facts
   is what a card list is for; as a six-column table in half a screen it wrapped
   every title into five or six fragments and pushed the delete button past the
   right edge of a sideways scroll. */
.pc-lib-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 14px;
  margin: 4px 0 14px;
}
.pc-lib-head h2 {
  margin: 0;
  font-size: var(--fs-lg);
  font-weight: var(--fw-bold);
  letter-spacing: -.01em;
}

/* The filters. aria-current marks the live one, and the rule below keys off
   that attribute rather than off a PHP-added class, so the thing the browser
   announces and the thing a person sees can never disagree. */
.pc-segmented {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 5px;
  background: var(--primary-tint);
  border-radius: var(--radius-pill);
}
.pc-segmented a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  /* Same reasoning as .pc-chips above: the touch target follows the token,
     so this control is 44px for staff and 60px on a patient screen. */
  min-height: var(--pc-touch);
  padding: 0 18px;
  border-radius: var(--radius-pill);
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  color: var(--primary);
  text-decoration: none;
  transition: background var(--dur-short) var(--ease-standard);
}
.pc-segmented a:hover { background: rgba(255, 255, 255, .65); }
.pc-segmented a[aria-current] {
  background: var(--primary);
  color: var(--on-fill);
  box-shadow: var(--shadow-sm);
}
.pc-segmented .pc-icon { width: 1.05em; height: 1.05em; }
html.pc-contrast .pc-segmented a[aria-current] { outline: 2px solid var(--border); }

.pc-lib { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px; }
.pc-lib-card {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 16px;
  padding: 16px 18px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  transition: box-shadow var(--dur-short) var(--ease-standard);
}
.pc-lib-card:hover { box-shadow: var(--shadow-sm); }
.pc-lib-ico {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 46px;
  height: 46px;
  flex: none;
  border-radius: var(--radius-md);
  background: var(--primary-tint);
  color: var(--primary);
}
/* The type is in the pill as words. The tint is a second, quieter carrier of
   the same fact, never the only one. */
.pc-lib-card[data-type="pdf"] .pc-lib-ico { background: #FBEBE6; color: #9A3412; }
.pc-lib-card[data-type="infographic"] .pc-lib-ico { background: #E7F2EA; color: #14532D; }

.pc-lib-body { min-width: 0; }
.pc-lib-body h3 {
  margin: 0 0 6px;
  font-size: var(--fs-base);
  font-weight: var(--fw-bold);
  line-height: 1.3;
}
.pc-lib-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 12px;
  margin: 0;
  font-size: var(--fs-sm);
  color: var(--text-muted);
}
.pc-lib-actions { display: flex; gap: 10px; flex: none; }

@media (max-width: 760px) {
  .pc-upload-grid { grid-template-columns: minmax(0, 1fr); }
  .pc-lib-card { grid-template-columns: auto minmax(0, 1fr); }
  /* The actions drop to their own row rather than shrinking: both are at the
     touch floor already and there is nothing left to take off them. */
  .pc-lib-actions { grid-column: 1 / -1; }
  /* The meter takes its own row rather than sharing one. Beside the text in
     a phone-width column it left "of 5 GB used" four words on four lines. */
  .pc-facts-meter { flex-wrap: wrap; }
  .pc-meter { flex: 1 0 100%; width: auto; }
}

/* ---- dashboard charts ---------------------------------------------------

   Drawn here rather than pulled in. No chart library: this project loads
   nothing from a CDN, and one would be the largest dependency in the app for
   three small figures. */
.pc-charts {
  display: grid;
  grid-template-columns: minmax(0, 2.1fr) minmax(0, 1fr);
  gap: 18px;
  margin-bottom: 18px;
}
/* The second row leads with the narrow card. Ordered rather than reordered
   in the markup, so the reading order stays wide-then-narrow for a screen
   reader and for print, where the mirroring means nothing. */
.pc-charts.is-mirrored { grid-template-columns: minmax(0, 1fr) minmax(0, 2.1fr); }
.pc-charts.is-mirrored > :first-child { order: 2; }
.pc-charts.is-mirrored > :last-child  { order: 1; }
@media (max-width: 900px) {
  .pc-charts { grid-template-columns: minmax(0, 1fr); }
}
.pc-charts .wcard { margin-bottom: 0; display: flex; flex-direction: column; }
.pc-chart-note { margin: 14px 0 0; font-size: var(--fs-sm); color: var(--text-muted); }
.pc-chart-note b { color: var(--text); }

/* Fourteen bars on a shared baseline. A zero day keeps a 2px stub: a day with
   nothing must still be a day, or the axis silently shortens and a fortnight
   with gaps looks like a shorter, busier one. */
.pc-bars {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: 6px;
  align-items: end;
  height: 132px;
  margin-top: auto;
}
.pc-bar { display: flex; flex-direction: column; justify-content: flex-end; height: 100%; gap: 8px; }
.pc-bar b {
  display: block;
  border-radius: 4px 4px 2px 2px;
  background: var(--primary);
  min-height: 2px;
}
.pc-bar.is-zero b { background: var(--heat-empty); }
.pc-bar em {
  font-style: normal;
  text-align: center;
  font-size: var(--fs-xs);
  color: var(--text-muted);
}
/* The most recent day is the one being asked about. */
.pc-bar:last-child b { background: var(--accent); }
.pc-bar:last-child em { color: var(--accent-dark); font-weight: var(--fw-bold); }

/* A ring is a stroked circle rotated to start at twelve o'clock, with the arc
   cut by a dash. The figure sits inside it, because a ring without a number
   is a picture of a fraction rather than the fraction. */
.pc-chart-ring { align-items: center; text-align: center; }
.pc-chart-ring h3 { align-self: stretch; }
.pc-ring { position: relative; width: 100%; max-width: 190px; margin: auto 0; }
.pc-ring svg { display: block; width: 100%; height: auto; transform: rotate(-90deg); }
.pc-ring circle { fill: none; stroke-width: 12; }
.pc-ring-track { stroke: var(--heat-empty); }
.pc-ring-fill { stroke: var(--success); stroke-linecap: round; }
.pc-ring-label {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
}
.pc-ring-label b {
  font-size: var(--fs-2xl);
  font-weight: var(--fw-bold);
  line-height: 1;
  letter-spacing: -.02em;
  color: var(--text);
}
.pc-ring-label em { font-style: normal; font-size: var(--fs-sm); color: var(--text-muted); }

/* The fortnight on a roster row. Deliberately unlabelled: it answers "is this
   holding or sliding", and the exact figures are the bar and the fraction
   beside it. */
.pc-spark { grid-column: 2; display: block; height: 26px; margin-top: 6px; }
/* :not(.pc-icon) because the empty state puts a flame icon in here too, and
   a bare `.pc-spark svg` rule outranks `.pc-icon`'s own width: the icon
   stretched to the full 657px of the column and shoved the label out of the
   card. This rule is for the plotted line only. */
.pc-spark > svg:not(.pc-icon) { display: block; width: 100%; height: 26px; }
.pc-spark polyline {
  fill: none;
  stroke: var(--primary);
  stroke-width: 2;
  stroke-linejoin: round;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}
.pc-roster-card.is-waiting .pc-spark polyline { stroke: var(--accent); }

/* Fourteen zeroes is not a line. See the note in the view: plotted, a patient
   with no activity draws a rule straight across the card that reads as a
   border and is indistinguishable from a low steady habit. */
.pc-spark.is-empty {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: var(--fs-sm);
  /* Was grey italic, which is how a page says "ignore this". A patient who
     has not practised in a fortnight is the opposite of an aside. */
  color: var(--danger);
  font-weight: var(--fw-bold);
}
.pc-spark.is-empty .pc-icon { flex: none; }

html.pc-contrast .pc-bar b { outline: 1px solid var(--border); }

/* ---- the year, and the mix ----------------------------------------------

   The rolling trend line and the distribution donut, taken from the PTPK
   dashboard's pairing but drawn here rather than handed to ApexCharts. That
   library is 526KB and these screens are also opened by patients on old
   phones, where the hand-drawn version costs nothing and still renders with
   JavaScript switched off.

   The three exercise types keep one colour each across both charts and the
   legends, so a reader learns the key once.

   Every one of the three clears 3:1 against the card (WCAG 1.4.11): navy
   #1D4E89 far above it, amber #A16207 at 4.9:1, green #15803D at 5.0:1.
   Colour is still never alone: the lines also differ by dash pattern, and
   both legends carry the written figure. */
:root {
  --series-video: #1D4E89;
  --series-audio: #A16207;
  --series-memory: #15803D;
}

.pc-trend {
  position: relative;
  height: 170px;
  margin-top: 6px;
  /* Gridlines in CSS rather than in the SVG. The plot is drawn with
     preserveAspectRatio="none" so it can fill any width, and anything inside
     it is stretched with it; a horizontal rule drawn there would thicken and
     thin as the card resized. */
  background-image: repeating-linear-gradient(
    to bottom,
    var(--border) 0,
    var(--border) 1px,
    transparent 1px,
    transparent 25%
  );
}
.pc-trend svg { display: block; width: 100%; height: 100%; }
.pc-trend-line {
  fill: none;
  stroke-width: 2.5;
  stroke-linejoin: round;
  stroke-linecap: round;
  /* Keeps the stroke an even weight despite the stretched viewBox, and stops
     the dash patterns from being squashed into different shapes at different
     card widths. */
  vector-effect: non-scaling-stroke;
}
.pc-trend-line[data-series="video"]  { stroke: var(--series-video); }
.pc-trend-line[data-series="audio"]  { stroke: var(--series-audio); }
.pc-trend-line[data-series="memory"] { stroke: var(--series-memory); }

/* The axis, stated. Without a ceiling written somewhere a line chart is a
   shape and not a measurement. */
.pc-trend-top,
.pc-trend-zero {
  position: absolute;
  right: 2px;
  font-size: var(--fs-xs);
  color: var(--text-muted);
  background: var(--surface);
  padding: 0 3px;
}
.pc-trend-top { top: -6px; }
.pc-trend-zero { bottom: -6px; }

.pc-trend-x {
  display: flex;
  justify-content: space-between;
  margin-top: 8px;
  font-size: var(--fs-xs);
  color: var(--text-muted);
}
.pc-trend-x .is-now { color: var(--text); font-weight: var(--fw-bold); }

.pc-legend {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 10px 22px;
  margin: 14px 0 0;
  padding: 14px 0 0;
  border-top: 1px solid var(--border);
  font-size: var(--fs-sm);
  color: var(--text-muted);
}
.pc-legend li { display: flex; align-items: center; gap: 8px; }
.pc-legend b { color: var(--text); font-weight: var(--fw-bold); }
.pc-legend em { font-style: normal; }

/* The legend repeats the line's own dash pattern, not just its colour, so the
   key works for a reader who cannot tell amber from green. */
.pc-legend-line { width: 24px; height: 4px; flex: none; overflow: visible; }
.pc-legend-line line { stroke-width: 2.5; stroke-linecap: round; }
.pc-legend-line line[data-series="video"]  { stroke: var(--series-video); }
.pc-legend-line line[data-series="audio"]  { stroke: var(--series-audio); }
.pc-legend-line line[data-series="memory"] { stroke: var(--series-memory); }

.pc-legend-dot { width: 14px; height: 14px; border-radius: 50%; flex: none; }
.pc-legend-dot[data-series="video"]  { background: var(--series-video); }
.pc-legend-dot[data-series="audio"]  { background: var(--series-audio); }
.pc-legend-dot[data-series="memory"] { background: var(--series-memory); }

/* Under the donut the legend is a small table: name, count, share. Stacked
   because three rows of three figures read down, not across. */
.pc-legend.is-stacked { flex-direction: column; gap: 10px; }
.pc-legend.is-stacked li { width: 100%; }
.pc-legend.is-stacked li > span:nth-of-type(2) { flex: 1; }

.pc-donut-slice {
  fill: none;
  stroke-width: 14;
  /* Butt, not round: rounded ends on adjacent arcs overlap their neighbours
     and each slice then reads a degree or two larger than its number. */
  stroke-linecap: butt;
}
.pc-donut-slice[data-series="video"]  { stroke: var(--series-video); }
.pc-donut-slice[data-series="audio"]  { stroke: var(--series-audio); }
.pc-donut-slice[data-series="memory"] { stroke: var(--series-memory); }
.pc-donut .pc-ring-label em { font-size: var(--fs-sm); }

/* An empty chart says so with its own icon, the way PTPK's does, rather than
   leaving a blank rectangle that reads as something failing to load. */
.pc-chart-activity .empty .pc-icon,
.pc-chart-ring .empty .pc-icon { opacity: .45; margin-bottom: 4px; }

html.pc-contrast .pc-trend { background-image: none; border-bottom: 2px solid var(--border); }
html.pc-contrast .pc-legend-dot { outline: 2px solid var(--border); }

/* The enhancement seam. The server's SVG sits in .pc-chart-fallback and is
   what a reader sees until ApexCharts has actually rendered into
   .pc-chart-canvas; charts.js swaps them only after a successful render, so
   a missing library or a thrown error leaves a correct chart on screen
   rather than a hole. Both are hidden with the `hidden` attribute, which
   needs the !important here because the canvas is display:block below. */
.pc-chart { position: relative; }
.pc-chart-canvas { min-height: 0; }
.pc-chart-canvas[hidden],
.pc-chart-fallback[hidden] { display: none !important; }
/* ApexCharts measures its own container, so it must not inherit the centring
   the hand-drawn ring needs. */
.pc-chart-canvas .apexcharts-canvas { margin: 0 auto; }

@media (max-width: 760px) {
  /* Twelve month labels in a phone-width card ran together as one word:
     OctNovDecJanFeb. Every other one is hidden, and hidden with visibility
     rather than display so the boxes stay in the flow and the survivors keep
     sitting over their own data points. Counting from the odd positions keeps
     the last month, the one actually being read. */
  .pc-trend-x span:nth-child(odd) { visibility: hidden; }
}
html.pc-contrast .pc-ring-track { stroke: var(--border); }

/* ---- the caseload -------------------------------------------------------

   People, not events. This replaced a table of the last eight recordings,
   which on a roster of four patients was eight rows carrying the same name.

   Each card is one patient and answers the three questions a therapist opens
   the dashboard with, in the order they are asked: is anything of theirs
   waiting for me, how is their week going, and when did I last see them. */
.pc-roster-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin: 4px 0 14px;
}
.pc-roster-head h2 {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0;
  font-size: var(--fs-lg);
  font-weight: var(--fw-bold);
  letter-spacing: -.01em;
}

/* The count of patients who have stopped, said once at the top of the list
   rather than left for the reader to find by counting grey italic lines
   down the cards. */
.pc-roster-alert {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 14px;
  border-radius: var(--radius-pill);
  background: var(--danger-bg);
  border: 1px solid var(--danger-border);
  color: var(--danger);
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
}

.pc-roster { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px; }
.pc-roster-card {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: 4px 16px;
  align-items: center;
  padding: 18px 20px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 4px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  text-decoration: none;
  color: var(--text);
  transition: transform var(--dur-short) var(--ease-standard),
              box-shadow var(--dur-short) var(--ease-standard);
}
.pc-roster-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); }
/* The one thing that makes a row worth opening today gets the edge. The
   same three-colour scheme as the KPI tiles above, so a stripe means the
   same thing wherever it appears on this screen. */
.pc-roster-card.is-waiting { border-left-color: var(--accent); }
.pc-roster-card.is-quiet   { border-left-color: var(--danger); }

.pc-roster-card .avatar-sm { grid-row: span 2; }
.pc-roster-who b { display: block; font-size: var(--fs-md); }
.pc-roster-who em {
  display: block;
  font-style: normal;
  font-size: var(--fs-sm);
  color: var(--text-muted);
  margin-top: 2px;
}
.pc-roster-flag {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 14px;
  border-radius: var(--radius-pill);
  background: var(--accent-tint);
  color: var(--accent-dark);
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  white-space: nowrap;
}
.pc-roster-flag .pc-icon { width: 1.1em; height: 1.1em; }
.pc-roster-seen { font-size: var(--fs-sm); color: var(--text-muted); white-space: nowrap; }

/* The week, as a bar rather than a fraction on its own. The fraction stays
   beside it: a bar alone cannot say 9 of 21, and colour alone is never the
   only encoding here. */
.pc-roster-bar {
  grid-column: 2;
  display: block;
  height: 8px;
  border-radius: 4px;
  background: var(--heat-empty);
  overflow: hidden;
}
.pc-roster-bar > span {
  display: block;
  height: 100%;
  border-radius: 4px;
  background: var(--success-mid);
}
.pc-roster-count { font-size: var(--fs-sm); color: var(--text-muted); white-space: nowrap; }
.pc-roster-count b { color: var(--text); }

/* ---- the question list --------------------------------------------------

   Beside the caseload, not under it: the two are different kinds of waiting
   and reading one should not mean scrolling past the other. */
.pc-qlist { list-style: none; margin: 0 0 18px; padding: 0; display: grid; gap: 16px; }
.pc-qlist li { display: grid; gap: 6px; }
.pc-qlist li + li { padding-top: 16px; border-top: 1px solid var(--border); }
.pc-qlist .pill { justify-self: start; }
.pc-qlist p { margin: 0; font-size: var(--fs-base); line-height: 1.5; }
.pc-qlist-when { font-size: var(--fs-sm); color: var(--text-muted); }

@media (prefers-reduced-motion: reduce) {
  .pc-roster-card { transition: none; }
  .pc-roster-card:hover { transform: none; }
}

html.pc-contrast .pc-roster-card { box-shadow: none; border-width: 2px; border-left-width: 6px; }

/* ---- inline panels ------------------------------------------------------

   Standing information, not a response to an action: the recorder's status
   line, the memory game's stage line, the note over patient setup. */
.alert { padding: 12px 16px; border-radius: var(--radius-sm); margin-bottom: 14px; font-weight: var(--fw-semibold); }
.alert.ok { background: var(--success-bg); color: var(--success); border: 1px solid var(--success-border); }
.alert.err { background: var(--danger-bg); color: var(--danger); border: 1px solid var(--danger-border); }
.alert.info { background: var(--primary-tint); color: var(--primary-dark); border: 1px solid var(--primary-border); }

.empty { padding: 28px; text-align: center; color: var(--text-muted); }

/* --------------------------------------------------------- exercise tiles -- */

.tile-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: var(--pc-gap); }
/* Cards in a row are stretched to the tallest of them, but their contents
   are not, so with descriptions of different lengths the Open and Save
   buttons landed at a different height in every card and the row read as
   ragged rather than as a set. Pushing the action row to the bottom lines
   them up without trimming anybody's description. */
.tile-grid > .wcard { display: flex; flex-direction: column; }
.tile-grid > .wcard > .btn-row { margin-top: auto; padding-top: 10px; }
/* The three choices a patient meets on opening the app, so this is the most
   used component in the product. */
.tile {
  background: var(--surface);
  border: 2px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 26px 20px 24px;
  text-align: center;
  text-decoration: none;
  color: var(--text);
  display: flex;
  flex-direction: column;
  align-items: center;
  min-height: calc(var(--pc-touch) * 2.2);
  transition: border-color var(--dur-short) var(--ease-standard),
              background var(--dur-short) var(--ease-standard),
              transform var(--dur-short) var(--ease-standard);
}
.tile:hover {
  border-color: var(--primary);
  background: var(--primary-tint);
  transform: translateY(-2px);
}
@media (prefers-reduced-motion: reduce) {
  .tile { transition: none; }
  .tile:hover { transform: none; }
}
/* The glyph sits in a tinted disc rather than loose on the card. Loose, at
   this size, it read as a stray mark above the words. */
.tile .ico {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.6em;
  height: 2.6em;
  margin-bottom: 16px;
  border-radius: 50%;
  background: var(--primary-tint);
  font-size: var(--fs-xl);
  color: var(--primary);
}
.tile:hover .ico { background: var(--surface); }

/* A 2px stroke drawn at 24px becomes a 4px slab when the same icon is scaled to
   a 50px tile. Line weight has to come down as the icon grows, or the family
   stops looking like one family. */
.tile .ico .pc-icon { stroke-width: 1.5; }
.plant .tree .pc-icon { stroke-width: 1.4; color: var(--success); width: 1em; height: 1em; }
/* display:block, and that is the whole fix for something that looked like a
   styling choice. These three were inline spans: the name, the description
   and the count ran together into one sentence, and every margin-top below
   was silently doing nothing, because vertical margins do not apply to an
   inline box. The tile read "Watch & practise A short video, then simple
   movements Today: 0 done". */
.tile .name { display: block; font-weight: var(--fw-bold); font-size: var(--fs-lg); line-height: 1.25; }
.tile .desc {
  display: block;
  color: var(--text-muted);
  font-size: var(--fs-base);
  line-height: 1.45;
  margin-top: 8px;
  max-width: 26ch;
}
/* The count is the one line that changes during the day, so it is set apart
   as a chip rather than as a third paragraph. */
.tile .meta {
  display: inline-block;
  margin-top: auto;
  padding: 6px 14px;
  border-radius: var(--radius-pill);
  background: var(--bg);
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  color: var(--primary-dark);
}
.tile .desc + .meta { margin-top: 16px; }

/* Mode selector (C0 step 2, E2 toggle) */
.mode-card {
  border: 2px solid var(--border);
  border-radius: var(--radius-md);
  padding: 16px;
  text-align: center;
  cursor: pointer;
  background: var(--surface);
  min-height: var(--pc-touch);
}
.mode-card:hover { border-color: var(--primary); }
.mode-card.active { border-color: var(--primary); background: var(--primary-tint); }
.mode-card .mc-ico { font-size: var(--fs-2xl); color: var(--text-muted); line-height: 1; }
.mode-card .mc-ico .pc-icon { stroke-width: 1.6; }
/* The chosen card's icon takes the selected colour with the rest of the card,
   which is the whole point of the icons being strokes rather than pictures. */
.mode-card.active .mc-ico { color: var(--primary); }
.mode-card .mc-name { font-weight: var(--fw-bold); margin: 6px 0 4px; }
.mode-card .mc-desc { font-size: var(--fs-sm); color: var(--text-muted); }
.mode-card input { position: absolute; opacity: 0; pointer-events: none; }

/* Plant motivator (layer L5) */
.plant {
  background: linear-gradient(135deg, var(--success-bg), var(--success-bg));
  border: 1px solid var(--success);
  border-radius: var(--radius-md);
  padding: 16px;
  text-align: center;
}
.plant .tree { font-size: var(--fs-3xl); line-height: 1; }
.plant .bar { height: 12px; background: var(--surface); border-radius: var(--radius-sm); overflow: hidden; margin: 10px 0 6px; border: 1px solid var(--success-border); }
.plant .bar > i { display: block; height: 100%; background: var(--success); }

/* Heatmap (C2 / R1) */
/* ---- practice consistency ------------------------------------------------

   Replaces the adherence heatmap. See the note in results/index.php: four
   steps of one green could not be told apart, and the cell count followed the
   range selector, so "All time" drew ninety squares in thirteen rows.

   Nothing here needs a legend. Each day carries its name, a done-or-not mark
   and its own count, so the panel reads without reference to a key, prints,
   and survives a reader who cannot separate the green from the grey: the mark
   differs in shape (a tick against an empty ring), not only in colour. */
.pc-adh {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 6px;
}
.pc-adh li {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  padding: 10px 2px;
  border-radius: var(--radius-md);
  background: var(--surface-sunken, #F5F3EF);
}
.pc-adh li.is-done { background: var(--success-bg); }

.pc-adh-day {
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  color: var(--text-muted);
  letter-spacing: .02em;
}
.pc-adh-mark {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  /* A tick in a filled disc against an empty ring: the two states differ in
     shape first, so the panel still reads in greyscale and in print. */
  background: var(--success);
  color: #FFFFFF;
}
.pc-adh li.is-missed .pc-adh-mark {
  background: none;
  border: 2px dashed var(--border);
  color: transparent;
}
.pc-adh-n {
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  color: var(--text);
}
.pc-adh li.is-missed .pc-adh-n { color: var(--text-muted); font-weight: var(--fw-normal); }

html.pc-contrast .pc-adh li { outline: 1px solid var(--border); }
html.pc-contrast .pc-adh-mark { outline: 2px solid var(--border); }

@media (max-width: 420px) {
  .pc-adh { gap: 4px; }
  .pc-adh li { padding: 8px 0; }
  .pc-adh-mark { width: 26px; height: 26px; }
}

.heat { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; max-width: 320px; }
/* The day letter sits under its cell, not inside it. Inside, it would have
   to stay legible against four different greens from --heat-empty to
   --success, and no single ink colour clears 4.5:1 on all four. */
.heat span { display: flex; flex-direction: column; gap: 5px; }
.heat i { display: block; aspect-ratio: 1; border-radius: var(--radius-sm); background: var(--heat-empty); }
.heat b {
  font-size: var(--fs-xs);
  font-weight: var(--fw-normal);
  color: var(--text-muted);
  text-align: center;
}
.heat i[data-n="1"] { background: var(--success-soft); }
.heat i[data-n="2"] { background: var(--success-mid); }
.heat i[data-n="3"] { background: var(--success); }

/* Weekly bar chart.
   A 14-day chart at 8px gaps overflows a 360px phone, so the bars scroll
   horizontally below their minimum legible width rather than being squashed. */
.bars {
  display: flex; align-items: flex-end; gap: 8px; height: 140px;
  overflow-x: auto; overflow-y: hidden; padding-bottom: 2px;
}
.bars .b {
  flex: 1 0 auto; min-width: 18px;
  display: flex; flex-direction: column; justify-content: flex-end;
  align-items: center; gap: 6px; height: 100%;
}
.bars .b > i { display: block; width: 100%; background: var(--primary); border-radius: 6px 6px 0 0; min-height: 3px; }
.bars .b > span { font-size: var(--fs-xs); color: var(--text-muted); }

/* A chart card placed in a two-column grid is stretched to the height of
   whatever sits beside it. With .bars fixed at 140px that surplus became a
   third of a card of white space under the bars, which reads as something
   failing to load. The chart takes the room instead. */
.pc-chart-fill { display: flex; flex-direction: column; }
.pc-chart-fill .bars { flex: 1 1 auto; height: auto; min-height: 140px; }
/* The chain has to continue through the ApexCharts wrapper, or the stretch
   stops at .pc-chart and the dead space comes back under the enhanced
   chart instead of under the hand-drawn one. ApexCharts is told
   height:'100%' for this chart and measures the parent, so the parent needs
   a real height rather than an auto one. */
.pc-chart-fill .pc-chart,
.pc-chart-fill .pc-chart-fallback { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
.pc-chart-fill .pc-chart-canvas { flex: 1 1 auto; min-height: 200px; }

/* ------------------------------------------------------------- responsive --

   Guiding rule: never shrink text or touch targets to make something fit.
   Layer L1 sets an 18px / 60px floor, and a phone is exactly where a patient
   with tremor needs that floor most. So narrow screens reflow into a single
   column, and genuinely wide content (data tables, the 14-day chart, the
   largest card grid) scrolls inside its own box instead.
   ---------------------------------------------------------------------------- */

/* Nothing may push the page itself sideways. */
html, body { max-width: 100%; overflow-x: hidden; }
.wa-main, .wa-content, .wcard { min-width: 0; }

/* A grid item's automatic minimum size is its content, not zero, so one wide
   child (the memory-game deck, a chart, a long word) stretches its whole track
   and drags the column past the screen edge. Because the page hides its own
   sideways overflow, the result is not a scrollbar but content silently cut
   off. Every direct child of a layout grid therefore has to be shrinkable;
   the ones that genuinely need width scroll inside their own box instead. */
.kpi-grid > *, .card-2 > *, .card-3 > *, .tile-grid > *, .btn-row > * { min-width: 0; }

/* Any data table can scroll within its card, whether or not the view
   remembered to add .table-wrap. */
.table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* The menu button and its backdrop exist only in the phone layout. */
.nav-toggle, .nav-backdrop, .wa-sidebar .nav-close { display: none; }

@media (max-width: 900px) {
  .pc-shell { flex-direction: column; }

  /* The sidebar used to become a horizontally scrolling strip of chips. That
     put half the menu off the side of the screen with nothing to say it was
     there, and asked a hand with tremor to swipe sideways to reach it. It is a
     drawer instead: one press opens it, every destination is visible at once,
     and each row keeps the full L1 touch height. */
  .nav-toggle {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    min-height: var(--pc-touch);
    padding: 10px 16px;
    border: 2px solid var(--primary);
    border-radius: var(--radius-sm);
    background: var(--surface);
    color: var(--primary-dark);
    font: inherit;
    font-weight: var(--fw-bold);
    cursor: pointer;
  }

  .wa-sidebar {
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    z-index: 60;
    /* The desktop rule floors the width at 220px so xlarge labels have room.
       On a narrow phone that floor would beat 86vw and push the drawer past
       the screen edge, so it is released here. */
    min-width: 0;
    height: auto;
    width: min(300px, 86vw);
    border-right: 1px solid var(--border);
    padding: 12px 0 20px;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    transform: translateX(-100%);
    transition: transform .22s ease;
    box-shadow: 4px 0 24px rgba(0, 0, 0, .3);
  }
  .wa-sidebar.open { transform: translateX(0); }

  .wa-sidebar .nav-close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: calc(100% - 24px);
    margin: 0 12px 10px;
    min-height: var(--pc-touch);
    border: 2px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface);
    color: var(--text);
    font: inherit;
    font-weight: var(--fw-bold);
    cursor: pointer;
  }

  .nav-backdrop.open {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 55;
    background: rgba(0, 0, 0, .45);
  }

  /* While the drawer is open the page behind it must not scroll away. */
  body.nav-open { overflow: hidden; }

  .wa-content { padding: 14px; }
}

@media (min-width: 901px) {
  /* Desktop keeps the sidebar in the flow, so the drawer state is meaningless. */
  .wa-sidebar.open { transform: none; }
}

@media (max-width: 640px) {
  /* Logo above the sentence rather than beside it, so neither has to shrink. */
  .pc-funding { flex-direction: column; gap: 10px; padding: 14px; }
  .pc-funding-text { text-align: center; flex: 0 1 auto; }

  /* Topbar stacks: page title on one line, identity and actions below. */
  .wa-topbar { flex-direction: column; align-items: stretch; gap: 10px; padding: 12px 14px; }
  .wa-topbar .user { flex-wrap: wrap; row-gap: 8px; }

  /* Every multi-column grid collapses to one column. auto-fit already does
     this at most widths, but the explicit rule removes the awkward
     two-narrow-columns state on mid-size phones. */
  .kpi-grid, .card-2, .card-3, .tile-grid { grid-template-columns: minmax(0, 1fr); }

  .wa-content { padding: 12px; }
  .wcard { padding: 14px; }

  /* Full-width action buttons are easier to hit than a wrapped row of
     half-width ones. */
  .btn-row { flex-direction: column; align-items: stretch; }
  .btn-row .wa-btn, .btn-row form, .btn-row form .wa-btn { width: 100%; }
  .wa-btn.w { width: 100%; }

  .heat { max-width: 100%; }
  .plant .tree { font-size: var(--fs-3xl); }

  /* Tables keep their own horizontal scroll; cells stop wrapping to one
     character per line. */
  table.wa { min-width: 520px; }
  table.wa th, table.wa td { padding: 9px 10px; }
}

@media (max-width: 400px) {
  .wa-content { padding: 10px; }
  .wcard { padding: 12px; border-radius: var(--radius-sm); }
  .kpi { padding: 12px; }
  table.wa { min-width: 460px; }
}

/* Simple layout (S1): hide secondary chrome, one task per screen. */
html.pc-simple .pc-secondary { display: none !important; }
html.pc-simple .wcard { margin-bottom: 18px; }

@media print {
  .wa-sidebar, .wa-topbar, .no-print { display: none !important; }

  /* Undo the sticky-footer column: a 100vh body pads the last sheet out with
     blank space, and on a short page prints an extra empty one. */
  body { background: #FFFFFF; display: block; min-height: 0; }  /* paper, not the theme */
  .pc-funding { margin-top: 24px; break-inside: avoid; }
}
