/* ============================================================
   UCMS Provider Scheduler (Epic 2) - design prototype
   Screen E2-5: Appointments, operator view, single-provider mode.

   This sheet is additive. It is loaded after css/styles.css,
   css/calendar.css, css/details.css and css/search.css and it changes
   none of them, so no Epic 1 screen and no earlier Epic 2 screen moves.
   Everything an Epic 1 calendar already draws is reused as drawn: the
   .cal-panel surface, the .cal-bar rows, the week grid, .cal__ev, the
   .cal-legend keys, the field and check components and the .drawer
   shell. What is here is only what E2-5 adds on top of that.

   Board rulings this sheet carries:
     128:6865  one calendar design, operator single-provider mode
     365:2004  colour codes for status, green booked, orange hold
     365:2011  tags on calendar cards, the same tags the list uses
     366:2105  a rescheduled session carries a Rescheduled tag
     365:2015  reschedule yellow, no-show grey, invert the contrast
     366:2072  a cancelled session stays on the calendar, and a new
               booking in the same slot stacks beside it
     128:6877  the agenda list stays a peer of the calendar
     370:5502  case ID chip and modality icon on the agenda row
     370:4747  language, time zone and authorization date on the row
     370:5514  Schedule New Appointment on the row
     128:6869  a filter side drawer decides what the calendar shows
     361:4212  search by member in that drawer
     128:6873  the session details drawer with its key actions
   ============================================================ */

/* ============================================================
   Row height

   Page-scoped override of the one calendar metric this screen has to
   move. Epic 1 sizes a half-hour row at 44px, which is exactly the two
   lines its card carries: the time and the member. 365:2011 adds a
   third line to every card here, the status tag, and three lines do not
   fit in 44px: the tag has a fixed height, so the member name shrank to
   nothing and the most important thing on the card disappeared.

   The rule this states: when a ruling adds a line to a card, the row
   the card lives in has to grow with it. 60px is the smallest step that
   holds all three lines with the card's own padding and boundary, and
   it is declared here rather than in css/calendar.css so no Epic 1
   screen moves. The hour gutter reads the same token, so the two grids
   stay locked.
   ============================================================ */
:root {
  --pp-cal-slot: 60px;
}

/* ============================================================
   Page states

   Three overlays live on this page: the session details drawer, the
   Add stub drawer and the left filter panel. Each one carries its own
   state on the body or on the layout root, the same separation E2-1
   uses for its provider drawer, so no overlay can be left drawn by
   another overlay's close handler. css/details.css hides .drawer only
   under [data-drawer="closed"], so both drawers here state their own
   resting display rather than borrowing that attribute.
   ============================================================ */
.drawer--session,
.drawer--add {
  width: min(420px, 100vw);
  display: none;
}

.scrim--session,
.scrim--add {
  display: none;
}

[data-session="open"] .drawer--session,
[data-add="open"] .drawer--add {
  display: flex;
}

[data-session="open"] .scrim--session,
[data-add="open"] .scrim--add {
  display: block;
}

/* ============================================================
   Toolbar

   The calendar owns its toolbar, per section 6 of the design-system
   pass. Two rows inside one .cal-panel: the surface switch and the
   actions on the first, the legend and the week stepper on the second.
   ============================================================ */

/* The provider the operator is looking at. Single-provider mode is the
   decided model (2026-06-30, confirmed by 128:6865) and the org-wide
   calendar is on hold behind Q83, so this control moves between one
   provider and the next rather than opening an all-provider view. */
.prov-pick {
  display: flex;
  align-items: center;
  gap: var(--cds-space-50);
}

.prov-pick__label {
  font-size: var(--pp-type-meta-size);
  color: var(--pp-ink-secondary);
  white-space: nowrap;
}

.prov-pick .select-wrap {
  min-width: 208px;
}

.prov-pick .cds-select {
  width: 100%;
}

/* The count sits with the stepper, so the week label and what is in
   that week read as one statement. */
.cal-count {
  font-size: var(--pp-type-meta-size);
  color: var(--pp-ink-secondary);
  white-space: nowrap;
}

/* ============================================================
   Legend

   One quiet band, not a paragraph. Jaspal asked for a key on July 14
   because the reference coloured cards with nothing anywhere saying
   what the colours meant. The two surface keys already exist in
   css/calendar.css; the four status keys below complete the set
   365:2004, 365:2015, 122:4178, 122:4182 and 366:2072 name between
   them.

   Every key is a left rule on white, the same shape the card carries,
   so the legend is a specimen of the card rather than a swatch.
   ============================================================ */
/* The two surface keys describe the calendar's own grounds, which the
   agenda list has none of, so they leave with the grid. The status keys
   stay: both surfaces read the same tag system. Same reasoning as the
   week stepper below, which is meaningless over a two-week list. */
.cal-panel[data-surface="list"] .cal-legend__item--surface,
.cal-panel[data-surface="list"] .cal-range {
  display: none;
}

.cal-legend__key--confirmed {
  background: var(--cds-neutral-0);
  border-left: 3px solid var(--cds-green-400);
}

.cal-legend__key--resched {
  background: var(--cds-neutral-0);
  border-left: 3px solid var(--cds-orange-350);
}

.cal-legend__key--cancelled {
  background: var(--cds-neutral-10);
  border-left: 3px solid var(--cds-neutral-300);
}

.cal-legend__key--noshow {
  background: var(--cds-neutral-10);
  border-left: 3px dashed var(--cds-neutral-300);
}

/* ============================================================
   Status tags - ONE system, two sizes

   365:2011 and 366:2105 ask for the same tags on the calendar cards
   that the list already carries, so there is one vocabulary here and
   both surfaces read it: Confirmed, On Hold, Rescheduled, Cancelled,
   No-Show. .status-tag and .status-tag--hold come from
   css/calendar.css unchanged; the three new grounds and the row size
   are stated here.

   365:2015 asks for the reschedule mark in yellow, no-show in grey,
   and inverts the contrast. Measured on the ground each tag paints:

     Confirmed    green-400   on green 7 percent over white   4.56:1
     On Hold      orange-400  on orange 15 percent over white 4.53:1
     Rescheduled  neutral-500 on orange-100 #ffd497           9.81:1
     Cancelled    neutral-450 on neutral-100 #e3e6e8          7.71:1
     No-Show      neutral-450 on white, neutral-300 boundary  9.66:1

   The inversion is what buys the headroom: Rescheduled and Cancelled
   put dark ink on a saturated ground instead of coloured ink on a pale
   one, and both clear the 4.5:1 text line by a wide margin. CDS as
   extracted publishes no yellow ramp, so the yellow is orange-100,
   which is the warmest light step the ramp carries. No new colour
   value is introduced by this sheet.

   Cancelled and No-Show are both grey, which is what the board asks
   for, so they are never told apart by tone alone: Cancelled is a
   filled grey and strikes the record it belongs to, No-Show is an
   outlined grey and does not.
   ============================================================ */
.status-tag--confirmed {
  background-color: var(--cds-neutral-0);
  background-image: linear-gradient(
    var(--cds-green-400-opacity-07),
    var(--cds-green-400-opacity-07)
  );
  color: var(--cds-green-400);
}

.status-tag--resched {
  background: var(--cds-orange-100);
  color: var(--pp-ink);
}

.status-tag--cancelled {
  background: var(--cds-neutral-100);
  color: var(--pp-ink-emphasis);
}

.status-tag--noshow {
  background: var(--cds-neutral-0);
  box-shadow: inset 0 0 0 1px var(--cds-neutral-300);
  color: var(--pp-ink-emphasis);
}

/* The row size of the same tag. A calendar card is 44px tall at a half
   hour and can only carry the 18px tag; a list row reads at the badge
   height the rest of the product uses. Same grounds, same words. */
.status-tag--lg {
  height: 24px;
  padding: 0 var(--cds-padding-small);
  font-size: var(--pp-type-meta-size);
}

/* The status word varies in length. Fixing the box keeps the case chips
   beside it on one vertical edge, which is what makes the column
   scannable. Same rule the Epic 1 appointment row states. */
.agrow__meta .status-tag--lg {
  min-width: 94px;
  justify-content: center;
}

/* ============================================================
   Calendar cards

   The card is css/calendar.css's .cal__ev unchanged. Three status
   variants are added to the two it already draws, each a left rule
   that clears the 3:1 non-text line on white: green-400 5.01:1,
   orange-400 4.99:1, orange-350 3.53:1, neutral-300 3.36:1.
   ============================================================ */
.cal__ev--resched {
  border-left-color: var(--cds-orange-350);
}

.cal__ev--noshow {
  border-left-color: var(--cds-neutral-300);
  border-left-style: dashed;
  background: var(--cds-neutral-10);
}

/* 366:2072, first half. A cancelled session does not disappear from the
   calendar: it stays where it was, struck through and grey, so the
   operator can see that the slot was given up rather than never
   booked. */
.cal__ev--cancelled {
  border-left-color: var(--cds-neutral-300);
  background: var(--cds-neutral-10);
}

.cal__ev--cancelled .cal__ev__time,
.cal__ev--cancelled .cal__ev__name {
  color: var(--pp-ink-secondary);
  text-decoration: line-through;
}

/* 366:2072, second half, built as the proposal the ruling records: a
   new booking in the same slot stacks beside the cancelled card rather
   than replacing it, so both the giving up and the rebooking are on the
   grid at once. The day column is a one-column grid, so every card in a
   slot lands in the same area; the stack splits that area by index.
   Two is the width this is drawn for and the width the seed data uses;
   a third card narrows to a third rather than breaking the layout. */
.cal__ev[data-stack-n] {
  width: calc(100% / var(--stack-n) - 5px);
  margin-left: calc(var(--stack-i) * 100% / var(--stack-n) + 3px);
  margin-right: 0;
  justify-self: start;
}

/* ============================================================
   Agenda list (screen 18)

   128:6877 keeps the list a peer of the calendar rather than a second
   screen, so the two share the panel, the toolbar and the filter set,
   and the surface switch is what moves between them.

   The row is its own grid rather than Epic 1's .appt, because it
   carries three things Epic 1's row does not: the case chip and
   modality icon 370:5502 asks for, the language, time zone and
   authorization date 370:4747 leaves on the row, and the Schedule New
   Appointment action 370:5514 adds. Create Note and More Info are gone
   per 366:2058.
   ============================================================ */
.ag {
  padding: var(--cds-space-100) var(--pp-card-pad) var(--pp-card-pad-tight);
}

.ag__group + .ag__group {
  margin-top: var(--cds-space-200);
}

.ag__label {
  display: flex;
  align-items: baseline;
  gap: var(--cds-space-75);
  margin: 0 0 var(--cds-space-50);
}

.ag__name {
  font-size: var(--pp-type-meta-size);
  font-weight: var(--cds-font-weight-semibold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--pp-ink-secondary);
}

.ag__count {
  font-size: var(--pp-type-meta-size);
  color: var(--pp-ink-secondary);
}

.ag__rule {
  flex: 1;
  height: 1px;
  background: var(--pp-border-divider);
}

.ag__group--today .ag__name {
  color: var(--pp-primary);
}

.ag__list {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* The row's text sits on the card's own content edge and its hover wash
   runs one bleed step past it, which is the rule section 3 of the
   design-system pass states for a list row. */
.agrow {
  position: relative;
  display: grid;
  grid-template-columns: 92px 24px minmax(0, 1fr) auto auto;
  align-items: start;
  gap: var(--cds-space-100);
  margin: 0 calc(-1 * var(--pp-row-bleed));
  padding: var(--cds-padding-medium) var(--pp-row-bleed);
  border-radius: var(--pp-block-radius);
  border-bottom: 1px solid var(--pp-border-divider);
  transition: background-color 150ms ease-out;
}

.agrow:last-child {
  border-bottom: 0;
}

.agrow:hover {
  background: var(--cds-neutral-10);
}

.agrow__date {
  text-align: right;
  padding-top: 2px;
}

.agrow__weekday {
  display: block;
  font-size: var(--pp-type-meta-size);
  font-weight: var(--cds-font-weight-semibold);
  letter-spacing: 0.04em;
  color: var(--pp-ink-emphasis);
}

.agrow__day {
  display: block;
  font-size: var(--pp-type-meta-size);
  color: var(--pp-ink-secondary);
  white-space: nowrap;
}

/* 370:5502, second half. The modality icon, with a screen-reader label
   beside it, so video, phone and in person are never distinguishable by
   shape alone. Same treatment and the same icon set as the Epic 1 row
   and the E2-1 results table. */
.agrow__modality {
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  color: var(--pp-ink-secondary);
}

.agrow__modality svg {
  width: 18px;
  height: 18px;
}

.agrow__main {
  min-width: 0;
}

/* The row opens the details drawer. The button carries the time, which
   is what an operator looks for first, and its ring stretches over the
   whole row so the target is the row rather than the six characters of
   the clock time. The controls that sit above it take their own
   stacking context back. */
.agrow__link {
  border: 0;
  padding: 0;
  background: none;
  font-family: inherit;
  cursor: pointer;
  text-align: left;
}

.agrow__link::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: var(--pp-block-radius);
}

.agrow__time {
  font-size: var(--pp-type-body-size);
  font-weight: var(--cds-font-weight-semibold);
  color: var(--pp-ink);
}

.agrow__link:hover .agrow__time {
  color: var(--pp-primary);
}

.agrow__desc {
  margin: 2px 0 0;
  font-size: var(--pp-type-body-size);
  color: var(--pp-ink-secondary);
}

.agrow__desc strong {
  font-weight: var(--cds-font-weight-semibold);
  color: var(--pp-ink-emphasis);
}

/* 370:4747, adapted. Language, time zone and authorization date stay on
   the row because they decide whether the appointment can happen at all
   and an operator scans for them. Phone, email and permission to leave
   a message are member contact data and go to the details drawer
   instead, which is the exposure ruling the Aug 18 plan asks to
   confirm. */
.agrow__facts {
  display: flex;
  flex-wrap: wrap;
  gap: 2px var(--cds-space-100);
  margin: var(--cds-space-50) 0 0;
  font-size: var(--pp-type-meta-size);
  color: var(--pp-ink-secondary);
}

.agrow__facts span {
  display: inline-flex;
  align-items: center;
  gap: var(--cds-space-25);
}

.agrow__meta {
  display: flex;
  align-items: center;
  gap: var(--cds-space-50);
  padding-top: 1px;
}

/* 370:5514. Schedule New Appointment sits on the row and opens the Add
   drawer with the member already carried into it. It is a quiet button:
   the screen has one call to action and it is the toolbar's. */
.agrow__act {
  position: relative;
  z-index: 1;
  padding-top: 1px;
}

.agrow--cancelled .agrow__time,
.agrow--cancelled .agrow__desc,
.agrow--cancelled .agrow__desc strong {
  color: var(--pp-ink-secondary);
  text-decoration: line-through;
}

/* The Earlier group is present and scannable and deliberately quieter.
   Quieter is stated by dropping weight, never by dropping under the
   4.5:1 text line: neutral-350 measures 4.96:1 on the canvas. */
.ag__group--past .agrow__time,
.ag__group--past .agrow__weekday {
  font-weight: var(--cds-font-weight-regular);
  color: var(--pp-ink-secondary);
}

.ag__group--past .agrow__desc strong {
  font-weight: var(--cds-font-weight-regular);
  color: var(--pp-ink-secondary);
}

.ag__empty {
  padding: var(--cds-space-200) 0;
}

/* ============================================================
   Filter panel (128:6869, 361:4212)

   A left side drawer, which is where the board draws it and the one
   left-side surface DSGN-290 does not move: it filters a calendar
   rather than a search.

   Jaspal's July 14 point on this screen is that the reference stacked
   three unrelated concerns, Appointments, Availability and Providers,
   under a single Apply, so an operator could not change providers
   without also committing whatever they had touched in availability.
   Each group here owns its own Apply and applies only its own fields.
   ============================================================ */
/* z-index 60, above the navigation rail's 50. Every other overlay in
   the set opens on the right, where the rail's stacking order never
   matters; a left panel at the rail's own edge has to come over it, or
   its first 72px sit under the icons and its close control is the only
   part of the head on screen. The scrim goes with it, so the rail dims
   like the rest of the page. */
.fpanel {
  position: fixed;
  z-index: 60;
  top: 0;
  left: 0;
  bottom: 0;
  width: min(360px, 100%);
  display: flex;
  flex-direction: column;
  background: var(--cds-neutral-0);
  border-right: 1px solid var(--pp-border-divider);
  /* Closed means parked past the left edge and inert, not hidden: a
     transitioned visibility is not settled when the open handler wants
     to move focus into the panel. inert keeps it out of the tab order
     without that race. Same contract as the Epic 1 filter drawer. */
  transform: translateX(-100%);
  transition: transform 200ms ease-out;
}

/* The closed scrim is hidden, not merely transparent. Polish pass
   2026-08-22: at opacity 0 with pointer-events none it was still a
   focusable button in the document, so every state of this screen carried
   one invisible Tab stop between the last control of whatever was in
   front and the page behind it. The session and Add scrims are display
   none when closed and were never in the ring; this one is not, because
   it transitions. visibility carries the fade out and takes it out of the
   ring at the same time. */
.fscrim {
  position: fixed;
  inset: 0;
  z-index: 55;
  border: 0;
  padding: 0;
  background: var(--pp-scrim);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 200ms ease-out, visibility 200ms ease-out;
}

:root[data-filters="open"] .fpanel {
  transform: translateX(0);
}

:root[data-filters="open"] .fscrim {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}

.fpanel .drawer__body {
  gap: var(--cds-space-150);
}

/* A group is a card inside the panel: one boundary, one radius, its
   fields and the Apply that commits them.

   flex: none is load-bearing. The shared .drawer__body is a flex column
   and a flex item shrinks by default, so with the panel taller than the
   viewport all three groups compressed and clipped their own contents
   against the overflow rule below. The body is the scroller; the groups
   keep their natural height and the panel scrolls past them. */
.fgroup {
  flex: none;
  border: 1px solid var(--pp-border-card);
  border-radius: var(--pp-card-radius);
  overflow: hidden;
}

.fgroup + .fgroup {
  margin-top: var(--cds-space-150);
}

.fgroup__head {
  padding: var(--cds-space-75) var(--pp-card-pad-tight);
  border-bottom: 1px solid var(--pp-border-divider);
  font-size: var(--pp-type-sub-size);
  line-height: var(--pp-type-sub-line);
  font-weight: var(--cds-font-weight-semibold);
  color: var(--pp-ink);
}

.fgroup__body {
  display: flex;
  flex-direction: column;
  gap: var(--cds-space-100);
  padding: var(--pp-card-pad-tight);
}

.fgroup__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--cds-space-50);
  padding: var(--cds-space-75) var(--pp-card-pad-tight);
  border-top: 1px solid var(--pp-border-divider);
  background: var(--cds-neutral-10);
}

.fgroup__state {
  font-size: var(--pp-type-meta-size);
  color: var(--pp-ink-secondary);
}

.fgroup .fieldset + .fieldset {
  margin-top: var(--cds-space-100);
}

/* Clear beside Apply. Clear is the quiet button and Apply is the
   neutral one: the screen has one call to action and it is the
   toolbar's Add Appointment, per section 7 of the design-system pass. */
.fgroup__acts {
  display: flex;
  align-items: center;
  gap: var(--cds-space-50);
}

/* ============================================================
   Session details drawer (128:6873)

   The drawer the calendar card and the agenda row both open, filled
   from the record that was clicked. The fact list is the shared
   .mdl__facts component from css/search.css, which is where section 13
   of the design-system pass put it: a list of facts is not a booking
   part, so both a dialog and a drawer state their facts in the same
   shape.

   The action group is called Actions. Jaspal flagged on July 14 that
   the reference put Cancel and Reschedule inside a section headed Mark
   Attendance, which is a mislabel: neither is an attendance outcome.
   ============================================================ */
.drawer--session .drawer__body {
  display: flex;
  flex-direction: column;
  gap: var(--cds-space-150);
}

/* Same rule as the filter groups: the body is the scroller and the
   blocks inside it keep their natural height rather than compressing. */
.drawer--session .drawer__body > *,
.drawer--add .drawer__body > * {
  flex: none;
}

.sd__id {
  display: flex;
  align-items: flex-start;
  gap: var(--cds-space-100);
}

/* The subject of the drawer, so the avatar takes the panel size rather
   than the table size: 34 is where it rides a 14px name, and this name
   runs at the section role. Same step E2-2's details drawer takes. */
.sd__id .res__avatar {
  width: 40px;
  height: 40px;
  border-radius: 20px;
  font-size: var(--pp-type-body-size);
}

.sd__id-text {
  min-width: 0;
}

.sd__name {
  margin: 0;
  font-size: var(--pp-type-section-size);
  line-height: var(--pp-type-section-line);
  font-weight: var(--cds-font-weight-semibold);
  color: var(--wpo-ink-strong);
}

.sd__line {
  margin: 2px 0 0;
  font-size: var(--pp-type-body-size);
  color: var(--pp-ink-secondary);
}

.sd__tags {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--cds-space-50);
}

.sd__block {
  border-top: 1px solid var(--pp-border-divider);
  padding-top: var(--cds-space-150);
}

.sd__acts {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--cds-space-50);
  margin-top: var(--cds-space-100);
}

.sd__acts .cds-btn {
  width: 100%;
}

/* Epic 1 session action component (.sess-action, css/calendar.css), used
   here for parity with 7-home-calendar-session-details. calendar.css is
   already linked on this page, so only the two things the drawer adds are
   stated: the grid cell takes the whole column, and the settled-record
   disabled state the JS sets on cancel and no-show reads as disabled,
   which .cds-btn:disabled used to carry. */
.sd__acts .sess-action {
  width: 100%;
  justify-content: flex-start;
}

.sd__acts .sess-action:disabled,
.sd__acts .sess-action:disabled:hover {
  border-color: transparent;
  background: var(--cds-neutral-100);
  color: var(--pp-ink-secondary);
  cursor: not-allowed;
}

.sd__acts .sess-action:disabled svg {
  color: var(--pp-ink-secondary);
}

/* ============================================================
   Add stub drawer (370:5514)

   The Add To Calendar form is screen E2-10 and is not built yet. What
   is built here is the step into it: the drawer opens, states which
   member and which case it is being created for, and says so in a
   quiet band. The alternative was a button that opens nothing, which
   is the exact defect 366:2058 removes from this screen.
   ============================================================ */
.drawer--add .drawer__body {
  display: flex;
  flex-direction: column;
  gap: var(--cds-space-150);
}

/* ============================================================
   Narrow widths

   The prototype is reviewed at 1440 and 1920. Below the calendar's own
   minimum the grid already scrolls inside .cal-scroll; these two steps
   keep the agenda row and the overlays usable if the window is dragged
   smaller during a review.
   ============================================================ */
@media (max-width: 1180px) {
  .agrow {
    grid-template-columns: 92px 24px minmax(0, 1fr) auto;
  }

  .agrow__act {
    grid-column: 3 / -1;
    padding-top: var(--cds-space-50);
  }
}

@media (max-width: 640px) {
  .drawer--session,
  .drawer--add,
  .fpanel {
    width: 100vw;
  }
}

/* ============================================================
   BUILD 2 - E2-6 to E2-10

   Everything below is added by Build 2: the session drawer's info
   block and its origin marker, the three commit dialogs, and the
   create form. It is still additive. css/styles.css, css/calendar.css,
   css/details.css and css/search.css are unchanged, so no Epic 1 screen
   and no earlier Epic 2 screen moves.

   The dialogs are Epic 1's .mdl-layer, unchanged. .mdl__facts,
   .mdl__note and the two faces come from css/search.css and
   css/details.css exactly as E2-3 and E2-4 use them, and the slot
   picker, the reason chips, the tally and the .readout line are all
   Epic 1 components this screen already loads. Nothing here forks a
   component that already exists.
   ============================================================ */

/* ------------------------------------------------------------
   The session drawer marks the record it was opened from

   Section 11.4 recorded this as open: the details drawer did not mark
   its origin row, and a selected-row wash would not have been seen
   under a 55 percent scrim anyway. Build 2 settles it, because the four
   actions in the drawer all change the record on the surface behind it
   and the operator has to see which card moved.

   The session scrim keeps its box and its click-to-close job and gives
   up its dim. It is the one overlay on this page whose subject is drawn
   behind it. The Add drawer and the filter panel keep their scrims: a
   create form and a filter set have nothing behind them to read.
   ------------------------------------------------------------ */
.scrim--session {
  background: transparent;
}

.agrow[data-selected],
.cal__ev[data-selected] {
  box-shadow: inset 0 0 0 2px var(--pp-primary);
}

.agrow[data-selected] {
  background: var(--pp-primary-tint-soft);
  border-radius: var(--pp-block-radius);
}

/* ------------------------------------------------------------
   Sessions info block (370:5542)

   Two figures, not a sentence. An operator deciding whether to
   reschedule or cancel is reading a count, and a count inside prose has
   to be found before it can be read. It is a dl with the value first
   and the label under it, which is why the value carries dd and the
   label dt: the reading order on screen is the figure, then what the
   figure is.
   ------------------------------------------------------------ */
.sinfo {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--cds-space-50);
  margin: 0;
}

.sinfo__cell {
  padding: var(--cds-padding-small) var(--cds-padding-medium);
  border: 1px solid var(--pp-border-card);
  border-radius: var(--pp-block-radius);
  background: var(--cds-neutral-10);
}

.sinfo__value {
  margin: 0;
  font-size: var(--cds-txt-subhead-size);
  line-height: var(--pp-type-section-line);
  font-weight: var(--cds-font-weight-semibold);
  color: var(--pp-ink);
  font-variant-numeric: tabular-nums;
}

.sinfo__label {
  font-size: var(--pp-type-meta-size);
  color: var(--pp-ink-secondary);
}

/* A marked placeholder (366:2084)

   "Billable if X criteria met" names no X, and the billing rule is
   policy rather than design. The row is built, the value says it is to
   be confirmed, and it is drawn as a placeholder so nobody reads it as
   a stated rule. It carries no owner, no question tag and no link: open
   points live in crit/e2-open-points.md, per the standing rule.

   .mdl__placeholder itself is in css/details.css, because the same
   sticky says to apply the change to Epic 1 and five Epic 1 screens
   carry the same popup. One class, one treatment, both epics.
   ------------------------------------------------------------ */

/* ------------------------------------------------------------
   Field status lines

   One line per field, under the control it belongs to, hidden until the
   field has been touched or the form has been submitted. The control is
   never locked and the save button is never disabled: a disabled button
   states that something is wrong without saying what, and the operator
   then has to guess which field it means.
   ------------------------------------------------------------ */
.field__status {
  margin-top: var(--cds-space-25);
  font-size: var(--pp-type-meta-size);
  color: var(--cds-red-450);
}

.field__status[hidden] {
  display: none;
}

.field__status--count {
  color: var(--pp-ink-secondary);
}

/* ------------------------------------------------------------
   Reschedule: the empty state 366:2094 asks for

   The reference had no empty state at all. This one states the
   condition, states what narrowed it, and carries the action the sticky
   names. The action is a real commit with its own success face, not a
   dead control.
   ------------------------------------------------------------ */
.rs__none {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--cds-space-50);
  margin-top: var(--cds-space-75);
  padding: var(--cds-padding-medium);
  border: 1px dashed var(--pp-border-control);
  border-radius: var(--pp-block-radius);
  background: var(--cds-neutral-10);
}

.rs__none[hidden] {
  display: none;
}

.rs__none-title {
  font-size: var(--pp-type-sub-size);
  line-height: var(--pp-type-sub-line);
  font-weight: var(--cds-font-weight-semibold);
  color: var(--pp-ink);
}

.rs__none-body {
  font-size: var(--pp-type-body-size);
  color: var(--pp-ink-secondary);
}

/* A slot the authorization does not reach is stated rather than left
   out of the list, so the reason a week looks thin is on the screen.
   The tag is the same second-line component the back-to-back warning
   takes; the difference is that this one is not choosable. */
.slot--blocked .slot__face {
  background: var(--cds-neutral-10);
  border-style: dashed;
  cursor: not-allowed;
}

.slot--blocked .slot__day,
.slot--blocked .slot__time {
  color: var(--pp-ink-secondary);
}

.slot--blocked .slot__tag {
  background: var(--cds-neutral-100);
  color: var(--pp-ink-emphasis);
}

/* The reschedule dialog is the one long dialog in the set, so its body
   scrolls inside the layer rather than pushing the foot off screen. */
#m-resched .mdl {
  max-height: calc(100vh - var(--cds-space-250) * 2);
}

/* The scroller is two levels down, and both levels have to be told.
   .mdl is the flex column, .mdl__form is the face inside it that holds
   the head, the body and the foot, and .mdl__body is what should give.
   A flex item does not shrink below its own content by default, so
   without min-height: 0 on both the capped .mdl grew back to the height
   of its content and the foot went off the bottom of the viewport with
   Confirm Reschedule on it. */
#m-resched .mdl__form {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
}

#m-resched .mdl__head,
#m-resched .mdl__foot {
  flex: none;
}

#m-resched .mdl__body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
}

/* ------------------------------------------------------------
   E2-10 create form

   A drawer, not a modal, and the foot is outside the scroller, so Save
   and Cancel are on screen at every scroll position. That is Jaspal's
   July 14 affordance point answered: the complaint was that required
   fields ran past the fold with the commit past them, and a sticky foot
   is the rule the set already has for that.
   ------------------------------------------------------------ */
.drawer--add {
  width: min(480px, 100vw);
}

.drawer--add .drawer__foot {
  display: flex;
  gap: var(--cds-space-75);
}

.drawer--add .drawer__foot .cds-btn {
  flex: 1;
  width: auto;
}

/* One face at a time, the same contract the dialogs run on. */
.drawer--add .add__done,
.drawer--add .add__done-foot {
  display: none;
}

.drawer--add[data-state="done"] .add__form,
.drawer--add[data-state="done"] .add__foot {
  display: none;
}

.drawer--add[data-state="done"] .add__done {
  display: block;
}

.drawer--add[data-state="done"] .add__done-foot {
  display: flex;
}

/* The member record block (370:5542) inside the create form. The same
   two figures the session drawer states, plus the case and the zone,
   because here they are what the operator checks the new appointment
   against rather than what they read off it. */
.mcard {
  flex: none;
  padding: var(--pp-card-pad-tight);
  border: 1px solid var(--pp-border-card);
  border-radius: var(--pp-block-radius);
  background: var(--cds-neutral-10);
}

.mcard__head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--cds-space-50);
  margin-bottom: var(--cds-space-75);
}

.mcard__name {
  font-size: var(--pp-type-sub-size);
  line-height: var(--pp-type-sub-line);
  font-weight: var(--cds-font-weight-semibold);
  color: var(--pp-ink);
}

@media (max-width: 640px) {
  .drawer--add {
    width: 100vw;
  }
}

/* ============================================================
   POLISH PASS, 2026-08-22

   An impeccable-style review of E2-5 and its five states, measured in
   headless Chrome against the live apex at 1440x900 and 1920x1080. Every
   rule below answers one finding written up in
   design/reviews/e2-5-appointments-review-2026-08-22.md. Still additive:
   css/styles.css, css/calendar.css, css/details.css and css/search.css
   are unchanged, so no Epic 1 screen and no earlier Epic 2 screen moves,
   and every selector here is scoped to a surface only this screen draws.
   ============================================================ */

/* ------------------------------------------------------------
   The session drawer's Actions sit in the foot

   Twelve facts put the four actions at y=1052 in a 900px viewport, so the
   thing the drawer exists for was past the fold on every open and the
   operator had to scroll a read-only list to reach it. The foot is
   outside .drawer__body, which is the scroller, so the group is on screen
   at every scroll position. That is the rule section 16.1 already states
   for the create form, applied to the surface that raises the same four
   commits.

   The group keeps its heading. Actions is the label the July 14 crit
   asked for in place of Mark Attendance, and a foot of four unlabelled
   buttons would have dropped it.
   ------------------------------------------------------------ */
.sd__foot .sd__block {
  border-top: 0;
  padding-top: 0;
}

.sd__foot .pav__h {
  margin-bottom: var(--cds-space-75);
}

/* The four are one group of equal weight and none of them is the primary.
   Which one is right depends on what happened to the session, and the
   screen's one call to action is the toolbar's Add Appointment, per
   section 7 of the design-system pass. */
.sd__foot .sd__acts {
  margin-top: 0;
}

/* A read-only fact list is not a form, and 54px a row is the rhythm of
   one. Twelve rows took 649px of an 819px scroller. At the row step below
   the same twelve take 553px and the block reads as a list rather than as
   twelve stacked cards. The dialogs keep the wider step, except the one
   dialog that scrolls. */
.drawer--session .mdl__fact,
#m-resched .mdl__fact {
  padding: var(--cds-space-75) 0;
}

/* ------------------------------------------------------------
   The reschedule dialog scrolls, so it says so

   #m-resched is the one dialog on the screen whose body is a scroller.
   Nothing marked the clip edge, so the slot list was cut through the
   middle of a row with no fade, no shadow and no arrow, which reads as a
   rendering fault rather than as more content. A mask on the scroller's
   own bottom edge fades the last 24px while there is anything below it.
   ------------------------------------------------------------ */
#m-resched .mdl__body {
  mask-image: linear-gradient(to bottom, #000 calc(100% - 24px), transparent);
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 24px), transparent);
}

/* The mask is a scroll affordance, not a rule about the last row: at the
   end of the scroller there is nothing below to fade, so it goes. */
#m-resched .mdl__body[data-scroll-end="true"] {
  mask-image: none;
  -webkit-mask-image: none;
}

/* The four facts added to the no-show dialog sit above the tally, and the
   fact list ends on a divider rule, so the block under it needs the
   interval the rest of the dialog uses. */
#m-noshow .tally {
  margin-top: var(--cds-space-150);
}

/* ------------------------------------------------------------
   The calendar's second toolbar row holds one line at 1440

   Measured: the legend is 792px, the count and the week stepper 471px,
   and the row has 1268px, so the two groups overflowed by 19px and the
   bar wrapped to 91px against the first bar's 67px. The legend is seven
   short items with their own wrap, so the step it can give up is its own
   column gap; nothing else on either bar moves.
   ------------------------------------------------------------ */
.cal-panel[data-surface] .cal-legend {
  column-gap: var(--cds-space-100);
}

/* ------------------------------------------------------------
   The day header stays in view while the grid scrolls

   The week grid is 1401px tall at 60px a half hour and the page is the
   scroller, so reading a 4:00 pm card meant the day names had gone. The
   header is inside .cal-scroll, and overflow-x: auto makes that box the
   scrollport a sticky child resolves against, which is a box that never
   scrolls vertically. overflow-x: clip contains the same way without
   creating a scroll container, so the header resolves against the
   viewport instead.

   Guarded to 1440 and up, because clip is only safe where the grid needs
   no horizontal scroll: measured scrollWidth 1302 against clientWidth
   1302 at 1440, so nothing is cut. Below that the panel keeps the
   scroller it needs and the header keeps its old behaviour.
   ------------------------------------------------------------ */
@media (min-width: 1440px) {
  .cal-panel[data-surface="calendar"] .cal-scroll {
    overflow-x: clip;
  }

  .cal-panel[data-surface="calendar"] .cal__head {
    position: sticky;
    top: 0;
    z-index: 2;
    /* Cards pass underneath it, so the header needs a ground of its own
       rather than the panel's. */
    background: var(--cds-neutral-0);
  }
}

/* ------------------------------------------------------------
   Agenda row

   The status tag and Schedule New Appointment start on the same grid line
   and are different heights, so their centres sat 8px apart on all
   twenty-six rows and the right of the row read as two ragged columns.
   Both columns now take the button's height and centre inside it.
   ------------------------------------------------------------ */
.agrow__meta {
  min-height: 40px;
  padding-top: 0;
}

.agrow__act {
  padding-top: 0;
}

/* Language, time zone and the authorization date are three separate facts
   set in one grey at one size with a space between them, so they read as
   one run-on line. A hairline between them is the cheapest thing that
   makes three facts look like three. */
.agrow__facts {
  gap: 2px 0;
}

.agrow__facts span + span {
  margin-left: var(--cds-space-75);
  padding-left: var(--cds-space-75);
  border-left: 1px solid var(--pp-border-divider);
}

/* ------------------------------------------------------------
   Filter panel

   Two of the three groups opened below the fold at 1440x900, so the one
   thing the panel is built to show, that each concern owns its own Apply,
   was not visible until the operator scrolled. The rows come in by one
   step each and the Availability head clears the fold. Nothing is
   removed and no group is collapsed: the panel still states all three
   scopes, and now it states them where they can be seen.
   ------------------------------------------------------------ */
.fpanel .check {
  padding-top: 2px;
  padding-bottom: 2px;
}

.fpanel .fgroup__body {
  gap: var(--cds-space-75);
}

.fpanel .fgroup .fieldset + .fieldset {
  margin-top: var(--cds-space-75);
}

/* The interval between two groups was stated twice: .drawer__body sets a
   24px gap and .fgroup + .fgroup added a 24px margin inside it, so the
   panel ran a 48px interval between groups and a 16px one everywhere
   else. The gap is the one that belongs to the scroller, so the margin
   goes. */
.fpanel .fgroup + .fgroup {
  margin-top: 0;
}

/* At 360px the state line and the two buttons did not fit on one line, so
   the line wrapped inside the space the buttons wanted. It takes its own
   row when it needs one and the buttons stay together. */
.fgroup__foot {
  flex-wrap: wrap;
}

.fgroup__state {
  flex: 1 1 auto;
  min-width: 0;
}

.fgroup__acts {
  flex: none;
  margin-left: auto;
}

/* ------------------------------------------------------------
   Legend: Cancelled against No-Show

   The two grey keys differed only by a solid against a dashed 3px rule
   inside a 22x14 swatch, which is not a difference anyone reads at that
   size. On the grid the two are told apart by the strike through a
   cancelled card's own text, so the key takes the same strike. Both keys
   still state their word, so neither is ever colour alone.
   ------------------------------------------------------------ */
.cal-legend__key--cancelled {
  background-image: linear-gradient(
    to bottom,
    transparent calc(50% - 1px),
    var(--pp-ink-tertiary) calc(50% - 1px),
    var(--pp-ink-tertiary) calc(50% + 1px),
    transparent calc(50% + 1px)
  );
}

/* ------------------------------------------------------------
   The dialog head's close control sits at the head's right edge

   .mdl__title carries flex: 1, but on these three dialogs the flex item
   is the wrapper div that holds the title and the lede, and that div
   carries nothing. The close button therefore sat wherever the longest
   line in the wrapper ended rather than at the corner. It was invisible
   while the lede was a full sentence of member, date and time; shortening
   the lede in this pass is what put the X beside the title. The wrapper
   takes the flex, not the h2 inside it.
   ------------------------------------------------------------ */
#m-resched .mdl__head > div:first-child,
#m-cancel .mdl__head > div:first-child,
#m-noshow .mdl__head > div:first-child {
  flex: 1;
  min-width: 0;
}

/* ============================================================
   Final review fixes, 2026-08-22

   Findings F-05, F-06 and F-14 of
   design/reviews/e2-5-final-review-2026-08-22.md. Still additive and
   still page-scoped: every selector below is either a new class this
   screen states for the first time or a shared class narrowed by a
   surface only this screen draws.
   ============================================================ */

/* ------------------------------------------------------------
   F-06. The member strip says what it is

   The strip names one member on a surface that lists twenty-six
   appointments for twenty of them, and it does not change when the
   provider does. Nothing on it said whether it was the case the
   operator arrived on or the subject of the grid. The standing role
   label answers that in two words, and it takes the label treatment the
   strip's own facts already use, so "Working On" over the name reads
   as the same label-over-value shape as "Case ID" over the case.
   ------------------------------------------------------------ */
.member-strip__ident {
  display: flex;
  flex-direction: column;
  gap: var(--cds-space-25);
}

.member-strip__role {
  font-size: var(--pp-type-meta-size);
  line-height: 16px;
  color: var(--pp-ink-secondary);
}

/* ------------------------------------------------------------
   F-05. The time off band's word stays visible

   The band is a hatch and the word on it is what keeps the hatch from
   being pattern alone. A card drawn over the top of the band covered
   the label and left an unnamed hatch on the grid. js/appointments.js
   measures the tallest part of the band no card is covering and states
   it as --label-top, so the word sits in the part of the band that can
   be seen. The default keeps the label where css/calendar.css puts it,
   at the top, for any band drawn without the variable.
   ------------------------------------------------------------ */
.cal-panel .cal__band--off {
  position: relative;
}

.cal-panel .cal__band--off .cal__band-label {
  position: absolute;
  top: var(--label-top, 0);
  left: 0;
  right: 0;
}

/* ------------------------------------------------------------
   F-14. The stacked card's truncated name, measured and left alone

   The review asks for the member name in full on a stacked card, by
   wrapping it to two lines or by dropping the type line. Built and
   measured, both fail on the geometry rather than on the intent. A
   30-minute card at half a column is 81px wide and 58px tall, which is
   50px of content: the time line at 12px, the name line at 16px and the
   status tag at 18px already spend 46 of it. A second name line needs
   16 more and there are 4. Wrapping therefore renders one line anyway,
   which is what it does now, only with the overflow rule hidden. The
   alternatives each give up something the grid states nowhere else: the
   time is what 244:804 puts first on the card, the tag is what 365:2011
   asks the card to carry, and shortening the name changes what the
   screen says about the member. The full name stays in the card's
   accessible name, on the agenda row and in the drawer the card opens.
   Left as it is, and recorded as open point 37.
   ------------------------------------------------------------ */
