/* CY6 roll call — a staff phone, held one-handed, in a studio.
 *
 * Design constraints that are not preferences:
 *   - Tap targets are large because the person is standing, moving, and
 *     looking at a room rather than at the phone.
 *   - Faces are as big as the grid allows, because matching a face to a name
 *     across a room of moving dancers is the hard part of the task.
 *   - The undo bar sits at the bottom where a thumb already is.
 *   - Single-theme: a studio is bright and this is used at arm's length, so
 *     the contrast is fixed rather than following the phone's dark mode.
 */

:root {
  --bg:        #14161A;
  --surface:   #1E2127;
  --surface-2: #262A32;
  --ink:       #F2F4F6;
  --muted:     rgba(242,244,246,.55);
  --hairline:  rgba(242,244,246,.12);

  --present:   #1B7A4F;
  --sitting:   #8A6410;
  --partial:   #2A5F87;
  --absent:    #9B3125;
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

html, body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  -webkit-font-smoothing: antialiased;
  user-select: none; -webkit-user-select: none;
  overscroll-behavior-y: contain;
}

.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }

/* ---------- header ---------- */
.bar {
  position: sticky; top: 0; z-index: 20;
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem;
  padding: max(env(safe-area-inset-top), .6rem) 1rem .6rem;
  background: var(--bg);
  border-bottom: 1px solid var(--hairline);
}
.counts { display: flex; align-items: baseline; gap: .5rem; }
.count { font-size: 2.1rem; font-weight: 700; line-height: 1; font-variant-numeric: tabular-nums; }
.count-label { font-size: .8rem; color: var(--muted); text-transform: uppercase; letter-spacing: .08em; }
.meta { display: flex; flex-direction: column; align-items: flex-end; gap: .15rem; font-size: .7rem; color: var(--muted); }
/* Queued marks are safe. Never red — a warning colour makes staff retry, and
   retrying is how one dancer gets two marks. */
.sync-ok      { color: var(--muted); }
.sync-pending { color: #D9A23F; }

/* ---------- area + mode ---------- */
.area-row {
  position: sticky; top: 62px; z-index: 19;
  display: flex; gap: .6rem; align-items: center;
  padding: .6rem 1rem .7rem;
  background: var(--bg);
  border-bottom: 1px solid var(--hairline);
}
#area-select {
  flex: 1 1 auto; min-width: 0;
  background: var(--surface); border: 1px solid var(--hairline); border-radius: 10px;
  color: var(--ink); font: inherit; font-size: .95rem; padding: .65rem .8rem;
  /* 1rem-equivalent tap height; a select is a native sheet on iOS, which is
     the right control for seven fixed options and needs no custom menu. */
  -webkit-appearance: none; appearance: none;
}
.next-studio {
  flex: 0 0 auto;
  background: var(--surface); border: 1px solid var(--hairline); border-radius: 10px;
  color: var(--ink); font: inherit; font-size: .88rem; font-weight: 600;
  padding: .65rem .8rem; min-height: 44px; cursor: pointer;
}
.next-studio:active { background: var(--surface-2); }
.modes { display: flex; flex: 0 0 auto; background: var(--surface); border-radius: 10px; padding: 3px; }
.mode {
  border: 0; background: transparent; color: var(--muted);
  font: inherit; font-size: .82rem; font-weight: 600;
  padding: .5rem .8rem; border-radius: 8px; cursor: pointer;
}
.mode.active { background: var(--surface-2); color: var(--ink); }

/* ---------- the list ---------- */
.list { padding: .7rem 1rem 6rem; display: grid; gap: .7rem; }

/* FACES: two across on a phone. Bigger than a typical avatar on purpose —
   the task is recognising a person across a moving room. */
.list.faces { grid-template-columns: repeat(2, 1fr); }
@media (min-width: 640px)  { .list.faces { grid-template-columns: repeat(3, 1fr); } }
/* An iPad is the primary sweep surface, and three columns on a 1024pt screen
   wastes two thirds of it. Six columns puts a whole group -- around thirty
   faces -- on one screen with no scrolling, which is what makes clearing a
   room by tapping faster than reading a list. Sized by breakpoint rather than
   auto-fill so the tile never shrinks below a comfortable thumb target. */
@media (min-width: 820px)  { .list.faces { grid-template-columns: repeat(4, 1fr); } }
@media (min-width: 1024px) { .list.faces { grid-template-columns: repeat(5, 1fr); gap: .8rem; } }
@media (min-width: 1280px) { .list.faces { grid-template-columns: repeat(6, 1fr); } }

.card {
  background: var(--surface); border-radius: 14px; overflow: hidden;
  display: flex; flex-direction: column;
}
.list.faces .portrait { width: 100%; aspect-ratio: 1 / 1; background: var(--surface-2); position: relative; }
.list.faces .portrait img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }

/* 71 of 285 dancers have no headshot. Designed for, not around: a large
   coloured monogram reads at the same distance as a photo. */
.initials {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  font-size: 2.6rem; font-weight: 700; color: rgba(255,255,255,.9);
}
.card .who { padding: .55rem .6rem .1rem; }

/* The roster's longest names are 30 characters -- "María Fernanda Garza
   Hernández", "Jaedah Valenzuela (Blackshere)" -- and its longest unbreakable
   word is "Goggleye-Paulson" at 16. Three rules keep those honest:
     - overflow-wrap: anywhere, so a 16-character surname breaks instead of
       spilling out of the card. Nothing is ever truncated: a roll call where
       you cannot read the whole name has failed at its one job.
     - two lines are RESERVED, so a row of short names does not sit visibly
       shorter than the row beneath it.
     - a third line is allowed to grow. Grid stretches every card in a row to
       the tallest, and .acts is pushed to the bottom (below), so the buttons
       stay on one line across the row no matter how the names wrap. */
.card .nm {
  font-size: .98rem; font-weight: 600; line-height: 1.15;
  overflow-wrap: anywhere;
  min-height: 2.3em;
}
.card .sub { font-size: .72rem; color: var(--muted); margin-top: .15rem; min-height: .9rem; }

/* NAMES: one per row, name large. Better when the room is dancing and faces
   are moving — the owner's own suggestion from the meeting. */
/* Unset, it is a prompt rather than a filter: nobody has said who is holding
   the tablet, and every mark until they do carries no human name at all. */
#sweeper-select.unset { color: var(--muted); border-color: var(--hairline); }
#sweeper-select[hidden] { display: none; }

/* The portrait is the control in faces mode, so it has to read as one --
   a tile you can tap, and one that acknowledges the tap. Without the active
   state a staffer clearing a room fast cannot tell a registered tap from a
   missed one, and taps the same face twice. */
.list.faces .portrait { cursor: pointer; -webkit-tap-highlight-color: transparent; transition: transform 90ms ease, filter 90ms ease; }
.list.faces .card:not(.done) .portrait:active { transform: scale(.96); filter: brightness(1.25); }
/* A finished tile stays tappable, but for undo rather than for marking. */
.list.faces .card.done .portrait { cursor: pointer; }

.list.names { grid-template-columns: 1fr; }
.list.names .portrait { display: none; }
.list.names .card { flex-direction: column; }
.list.names .nm { font-size: 1.25rem; }
.list.names .who { padding: .8rem .9rem .2rem; }

/* ---------- the two buttons ---------- */
/* The sweep asks one question and takes one answer: is this person here.
   Present and Absent, side by side, nothing else to weigh.

   Sitting out used to be a third button here and that was backwards. This
   list contains only people who did NOT scan in at the front desk, and a
   dancer sitting out injured has almost certainly scanned in -- so they were
   never on this list, and the button was offered to precisely the population
   it does not apply to. Worse, a scanned-in dancer's card renders .done with
   its buttons hidden, so there was no way to mark them at all. It moved to
   its own per-room picker.

   Partial is gone for a different reason: it needs a from/until time and an
   approval, which is the absence-request layer's job, not something to settle
   while standing in a doorway. The value stays legal in the database and this
   list still renders one; there is simply no button that emits it.

   margin-top: auto pins the buttons to the bottom of the card. Grid stretches
   every card in a row to the tallest one, so a name that wraps to three lines
   no longer shunts its own buttons down out of line with its neighbour's. */
.acts {
  display: grid; grid-template-columns: repeat(2, 1fr); gap: 5px;
  padding: .5rem; margin-top: auto;
}
.act {
  border: 0; border-radius: 9px; padding: .7rem .2rem;
  font: inherit; font-size: .8rem; font-weight: 700; letter-spacing: .01em;
  color: var(--ink); background: var(--surface-2); cursor: pointer;
  min-height: 46px;   /* thumb-sized while walking */
  display: flex; align-items: center; justify-content: center; text-align: center; line-height: 1.1;
}
/* Pressed only. There is no latched state: nothing anywhere adds an `on`
   class to a button, and a tapped card is replaced by its finished form
   rather than staying lit. The `.on` half these rules used to carry read as
   a live toggle that does not exist. */
.act:active { transform: scale(.97); }
.act.p:active { background: var(--present); }
.act.a:active { background: var(--absent); }

/* ---------- the finished card ---------- */
/* A marked card stays visible so the sweep reads as progress rather than as
   things vanishing, and so a mis-tap is still there to correct.

   It used to fade the whole card to opacity .42, which cost more than it
   bought: scrolling back over a finished room told you a card was handled but
   not HOW, and any colour put inside a 42%-opaque card comes out as a muddy
   pastel of the surface behind it. So the dimming is now applied only to the
   parts that have done their job -- the portrait and the name -- while the
   outcome itself is rendered at full strength.

   Two redundant encodings, because this is read at arm's length by somebody
   walking: a full-height colour stripe down the leading edge, and the word
   itself in the same hue. Colour alone would fail for a colour-blind staffer;
   the word alone is too small to catch in peripheral vision. */
.card { position: relative; }
.card.done .acts { display: none; }
/* Owner, the first scanning day (2026-09-10): "instead of faces being greyed out
   maybe they should have a green border or green overlay? Maybe a green check
   on their image? So it is ultra clear that no one is missing from that
   studio." So a finished card keeps its face at full strength and says how it
   was settled three ways at once: a ring in the outcome's colour, a light tint
   of the same colour over the portrait, and a badge in the corner -- a check
   for present or scanned, a cross for absent. The fade is gone. */
.card.done { box-shadow: inset 0 0 0 3px var(--muted); }
.card.done .portrait::after { content: ""; position: absolute; inset: 0; background: rgba(120,120,120,.18); pointer-events: none; }
.badge {
  position: absolute; top: .45rem; right: .45rem; z-index: 3;
  width: 2rem; height: 2rem; border-radius: 50%;
  display: grid; place-items: center;
  font-size: 1.1rem; font-weight: 900; line-height: 1; color: #fff;
  background: var(--muted); box-shadow: 0 2px 8px rgba(0,0,0,.4);
}
.list.names .badge { top: 50%; transform: translateY(-50%); right: .8rem; }

.card.done::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0;
  width: 6px; background: var(--muted); border-radius: 14px 0 0 14px;
}

.card .result {
  display: none; padding: .5rem .7rem .7rem;
  font-size: .82rem; font-weight: 700; letter-spacing: .01em;
  color: var(--muted);
}
.card.done .result { display: block; }

/* Text tints are lifted well above the button fills. The fills sit under white
   at full opacity; these sit as small text on --surface, where #1B7A4F fails
   contrast badly enough to be unreadable in a bright studio. */
.card[data-outcome="present"].done::before     { background: var(--present); }
.card[data-outcome="present"].done, .card[data-outcome="scanned"].done { box-shadow: inset 0 0 0 3px var(--present); }
.card[data-outcome="present"].done .portrait::after, .card[data-outcome="scanned"].done .portrait::after { background: rgba(27,122,79,.45); }
.card[data-outcome="present"] .badge, .card[data-outcome="scanned"] .badge { background: var(--present); }
.card[data-outcome="absent"].done { box-shadow: inset 0 0 0 3px var(--absent); }
.card[data-outcome="absent"].done .portrait::after { background: rgba(190,60,40,.45); }
.card[data-outcome="absent"] .badge { background: var(--absent); }
.card[data-outcome="sitting_out"].done { box-shadow: inset 0 0 0 3px var(--sitting); }
.card[data-outcome="sitting_out"].done .portrait::after { background: rgba(200,150,40,.40); }
.card[data-outcome="sitting_out"] .badge { background: var(--sitting); }
.card[data-outcome="partial"].done { box-shadow: inset 0 0 0 3px var(--partial); }
.card[data-outcome="partial"].done .portrait::after { background: rgba(60,130,200,.40); }
.card[data-outcome="partial"] .badge { background: var(--partial); }
.card[data-outcome="present"] .result          { color: #4FCF95; }
.card[data-outcome="sitting_out"].done::before { background: var(--sitting); }
.card[data-outcome="sitting_out"] .result      { color: #E6BB55; }
.card[data-outcome="absent"].done::before      { background: var(--absent); }
.card[data-outcome="absent"] .result           { color: #F2836F; }
.card[data-outcome="partial"].done::before     { background: var(--partial); }
.card[data-outcome="partial"] .result          { color: #74B6EA; }

/* Someone the kiosk already caught is settled by a different route than
   someone a person judged in a studio. Deliberately quiet -- it is not an
   outcome anybody chose, and colouring it would compete with the ones that
   need attention. */
.card[data-outcome="scanned"].done::before { background: var(--present); }
.card[data-outcome="scanned"] .result      { color: #4FCF95; font-weight: 600; }

/* ---------- states ---------- */
.empty, .notday {
  padding: 5rem 2rem 7rem; text-align: center; color: var(--muted);
}
.empty {
  margin: .7rem 1rem 7rem; padding: 2.2rem 1.5rem;
  border: 2px solid var(--present); border-radius: 18px;
  background: rgba(27,122,79,.14);
}
.empty[hidden], .notday[hidden] { display: none; }
.empty-mark { font-size: 3.4rem; color: var(--present); }
.empty-title, .notday-title { font-size: 1.4rem; font-weight: 700; color: var(--ink); margin-top: .6rem; }
.empty-sub, .notday-sub { margin-top: .3rem; font-size: .92rem; }

/* ---------- sitting out ---------- */
/* Always reachable, including when the sweep says everyone is accounted for:
   "nobody is missing" and "nobody is hurt" are different questions, and the
   second still has to be asked in every room. */
.so-launch {
  position: fixed; left: 1rem; right: 1rem;
  bottom: max(env(safe-area-inset-bottom), 1rem);
  z-index: 25;
  display: flex; align-items: center; justify-content: center; gap: .5rem;
  border: 1px solid var(--hairline); background: var(--surface-2); color: var(--ink);
  font: inherit; font-size: .95rem; font-weight: 600;
  padding: .95rem 1rem; border-radius: 14px; cursor: pointer;
  box-shadow: 0 10px 26px rgba(0,0,0,.42);
}
.so-launch:active { background: var(--surface); }
.so-count {
  background: var(--sitting); color: #fff;
  font-size: .78rem; font-weight: 700; padding: .1rem .5rem; border-radius: 999px;
}
.so-count[hidden] { display: none; }

/* Full screen. Picking two people out of 285 while standing up is the whole
   task; nothing else should be on the glass. */
.picker {
  position: fixed; inset: 0; z-index: 60;
  background: var(--bg); display: flex; flex-direction: column;
}
.picker[hidden] { display: none; }
.picker-bar {
  display: flex; align-items: center; justify-content: space-between; gap: .6rem;
  padding: max(env(safe-area-inset-top), .7rem) .8rem .7rem;
  border-bottom: 1px solid var(--hairline);
}
.picker-title { font-size: 1rem; font-weight: 700; text-align: center; line-height: 1.2; }
.picker-area { display: block; font-size: .72rem; font-weight: 500; color: var(--muted); }
.picker-x, .picker-ok {
  border: 0; background: transparent; color: var(--muted);
  font: inherit; font-size: .92rem; font-weight: 600;
  padding: .55rem .3rem; cursor: pointer; flex: 0 0 auto;
}
.picker-ok { color: var(--ink); font-weight: 700; }

.picker-search { padding: .7rem .8rem .5rem; }
#picker-q {
  width: 100%; box-sizing: border-box;
  background: var(--surface); border: 1px solid var(--hairline); border-radius: 10px;
  color: var(--ink); font: inherit; font-size: 1rem; padding: .75rem .85rem;
  /* 1rem, not smaller: iOS Safari zooms the whole page on focus below 16px,
     and a kiosk-ish page that zooms mid-sweep is a page somebody has to
     pinch back out of one-handed. */
}
#picker-q::placeholder { color: var(--muted); }

.picker-status {
  padding: 0 .9rem .5rem; font-size: .8rem; color: var(--muted);
}
.picker-status.some { color: #E6BB55; font-weight: 600; }

.picker-list { flex: 1 1 auto; overflow-y: auto; -webkit-overflow-scrolling: touch;
               padding: 0 .8rem calc(env(safe-area-inset-bottom) + 1.4rem); }

/* A row, not a card: this list is scanned by name, and 285 cards would be a
   very long scroll. The headshot is here to confirm the right person once
   search has narrowed it, not to be matched across a room. */
.pick {
  display: flex; align-items: center; gap: .7rem;
  padding: .5rem .2rem; border-bottom: 1px solid var(--hairline);
  cursor: pointer;
}
.pick input { position: absolute; opacity: 0; pointer-events: none; }
.pick-face {
  flex: 0 0 auto; width: 44px; height: 44px; border-radius: 50%;
  overflow: hidden; position: relative; background: var(--surface-2);
}
.pick-face img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.pick-face .initials { font-size: 1rem; }
.pick-who { flex: 1 1 auto; min-width: 0; }
.pick-nm { font-size: .95rem; font-weight: 600; line-height: 1.2; overflow-wrap: anywhere; }
.pick-sub { font-size: .72rem; color: var(--muted); margin-top: .1rem; }
.pick-box {
  flex: 0 0 auto; width: 26px; height: 26px; border-radius: 8px;
  border: 2px solid var(--hairline); display: flex; align-items: center;
  justify-content: center; color: transparent; font-size: .9rem; font-weight: 900;
}
.pick input:checked ~ .pick-box {
  background: var(--sitting); border-color: var(--sitting); color: #fff;
}
.pick input:focus-visible ~ .pick-box { outline: 2px solid var(--ink); outline-offset: 2px; }
.pick-none { padding: 2.5rem 1rem; text-align: center; color: var(--muted); font-size: .92rem; }

/* ---------- undo ---------- */
/* Sits above the sitting-out launcher rather than on top of it -- both are
   bottom-anchored, and a toast that buries the button you were reaching for
   is how a mis-tap becomes two mis-taps. */
.undo {
  position: fixed; left: 1rem; right: 1rem;
  bottom: calc(max(env(safe-area-inset-bottom), 1rem) + 3.9rem);
  z-index: 30;
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  background: var(--surface-2); border: 1px solid var(--hairline);
  border-radius: 14px; padding: .85rem 1rem;
  box-shadow: 0 12px 30px rgba(0,0,0,.45);
}
.undo[hidden] { display: none; }
#undo-text { font-size: .92rem; }
#undo-btn {
  border: 0; background: var(--ink); color: var(--bg);
  font: inherit; font-weight: 700; font-size: .88rem;
  padding: .6rem 1.2rem; border-radius: 999px; cursor: pointer;
}

@media (prefers-reduced-motion: reduce) { .act:active { transform: none; } }

/* ---------- who is in the building ---------- */
/* Sits above the sitting-out launcher. Used rarely and urgently -- at the end
   of a day, or during an evacuation -- so it is always reachable rather than
   hidden behind a mode switch somebody has to remember exists. */
/* One row, two buttons. Stacked, they stood eight rem tall and covered the
   "Everyone accounted for" card at the end of a swept studio (owner, first
   scanning day). Side by side they take one row, and the list and the states
   above leave room for it. */
.so-launch { left: 50%; margin-left: .35rem; }
.inb-launch { left: 1rem; right: 50%; margin-left: 0; margin-right: .35rem; }

.inb-b {
  display: flex; align-items: baseline; justify-content: space-between;
  padding: .9rem .2rem .35rem; border-bottom: 1px solid var(--hairline);
  font-size: .78rem; text-transform: uppercase; letter-spacing: .08em; color: var(--muted);
}
.inb-b .n { font-size: 1.5rem; font-weight: 700; color: var(--ink);
            letter-spacing: -.01em; font-variant-numeric: tabular-nums; }
.inb-row {
  display: flex; align-items: baseline; justify-content: space-between; gap: 1rem;
  padding: .55rem .2rem; border-bottom: 1px solid var(--hairline);
}
.inb-row .nm { font-size: .98rem; font-weight: 600; overflow-wrap: anywhere; }
.inb-row .since { font-size: .72rem; color: var(--muted); flex: 0 0 auto;
                  font-variant-numeric: tabular-nums; }
/* Somebody whose day STARTED in the other building. Worth flagging: they are
   here now, but the other building's roster still expects them. */
.inb-row .moved { font-size: .68rem; color: #74B6EA; font-weight: 600; }

/* This list is a best guess and saying so is the whole point. A screen that
   implies certainty during an evacuation is worse than no screen. */
.inb-foot {
  padding: 1rem .2rem calc(env(safe-area-inset-bottom) + 1.4rem);
  font-size: .78rem; color: var(--muted); line-height: 1.5;
}
.inb-foot b { color: #E6BB55; font-weight: 600; }

.inb-actions { padding: 0 .2rem calc(env(safe-area-inset-bottom) + 1.2rem); }
.inb-close-day {
  width: 100%; border: 1px solid var(--hairline); background: transparent;
  color: var(--muted); font: inherit; font-size: .9rem; font-weight: 600;
  padding: .85rem 1rem; border-radius: 12px; cursor: pointer; min-height: 46px;
}
.inb-close-day:active { background: var(--surface); color: var(--ink); }

/* ---------- device chip + un-pairing ---------- */
/* The chip sits in the header meta beside the sync state: always legible so a
   person can tell sweep-a from sweep-b, never in the thumb zone where the
   marking happens. Styled as text rather than a button on purpose -- it is a
   label first and a door second. */
.device-chip {
  appearance: none; -webkit-appearance: none;
  background: none; border: 0;
  font: inherit; font-size: .7rem; color: var(--muted);
  letter-spacing: .04em; cursor: pointer;
  /* SMALL ON PURPOSE, and left that way after measuring it.
     It renders 49x15, under the usual ~44pt touch minimum, and enlarging it
     was tried and reverted: padding plus a negative margin pushed the target
     outside the header, where it sat over the top edge of the Faces/Names
     buttons. Stealing taps from a control used on every dancer, to make a
     once-a-year control easier, is the wrong trade.
     The affordance is deliberate anyway -- this un-pairs the tablet, and it
     should take a considered tap rather than a thumb brushing the header
     during a sweep. Confirmed reachable in the field on 2026-08-31, when it
     was used to un-pair Building B's iPad.
     The station's footer button is a different case: its bar had room, so the
     target there was widened to 69x47 with no overflow. */
  padding: .05rem 0; margin: 0;
}
.device-chip[hidden] { display: none; }
.device-chip:active { color: var(--ink); }

/* The confirmation. Centred and modal rather than a corner toast, because the
   action cannot be undone from inside this app and a dismissible toast invites
   a reflex tap. */
.confirm {
  position: fixed; inset: 0; z-index: 60;
  display: flex; align-items: center; justify-content: center;
  padding: 1.25rem;
  background: rgba(0,0,0,.6);
}
.confirm[hidden] { display: none; }
.confirm-card {
  width: 100%; max-width: 22rem;
  background: var(--surface); border: 1px solid var(--hairline); border-radius: .9rem;
  padding: 1.1rem 1.1rem .9rem;
}
.confirm-title { font-size: 1.05rem; font-weight: 700; margin-bottom: .45rem; }
.confirm-body { font-size: .85rem; line-height: 1.4; color: var(--muted); }
.confirm-actions { display: flex; gap: .6rem; margin-top: 1.1rem; }
/* Cancel is the wide one. The destructive choice should not be the easy
   target, and on a tablet held one-handed the larger button is the one a thumb
   finds first. */
.confirm-cancel, .confirm-go {
  appearance: none; -webkit-appearance: none;
  border: 1px solid var(--hairline); border-radius: .6rem;
  padding: .7rem 1rem; font: inherit; font-weight: 700; cursor: pointer;
}
.confirm-cancel { flex: 2; background: var(--surface-2); color: var(--ink); }
.confirm-go     { flex: 1; background: none; color: var(--absent); }

/* The escape hatch on the sign-in-required screen. That branch replaces the
   whole body, so the header chip is gone -- and it is exactly the screen a
   device with a rotated or mistyped token is stuck on. */
.notday-forget {
  appearance: none; -webkit-appearance: none;
  display: block; margin: 1.4rem auto 0;
  background: none; border: 1px solid var(--hairline); border-radius: .6rem;
  padding: .6rem 1rem; font: inherit; font-size: .8rem;
  color: var(--muted); cursor: pointer;
}
.notday-forget:active { color: var(--ink); }

/* Has this building been walked. The daily report waits for both rows to read
   "Sweep complete" (or for nobody to be unaccounted, or for 13:00). */
.sweep-strip { display: flex; flex-direction: column; gap: 6px; padding: 8px 14px 0; }
.sweep-row { display: flex; align-items: center; gap: 10px; min-height: 3.6rem; padding: 8px 12px; border: 1px solid var(--line, #3a3040); border-radius: 12px; background: var(--surface, #1a1028); }
.sweep-row.done { border-color: #2f7a4f; }
.sweep-name { font-weight: 800; }
.sweep-state { color: var(--muted, #b8afc4); font-size: 13px; margin-left: auto; text-align: right; }
.sweep-state.pending { opacity: .8; }
/* The button and the two pills share one box, so a row with the button and a row
   with a pill stand the same height (owner: the rows had different heights). */
.sweep-done, .sweep-pill { margin-left: auto; font: inherit; font-size: .95rem; line-height: 1.2; font-weight: 800; padding: 10px 14px; border-radius: 10px; border: 1px solid transparent; white-space: nowrap; }
.sweep-done { border-color: #a655a4; background: #2a1a3a; color: #fff; }
.sweep-pill.pending { border-color: var(--hairline, #3a3040); background: var(--surface-2, #241a30); color: var(--muted, #b8afc4); }
.sweep-pill.done { border-color: #2f7a4f; background: rgba(27,122,79,.18); color: #4FCF95; }
.sweep-done.armed { background: #e11d74; border-color: #e11d74; }
.sweep-done:active { filter: brightness(1.15); }
.sweep-undo { margin-left: 8px; font: inherit; font-size: 13px; padding: 6px 10px; border-radius: 8px; border: 1px solid var(--line, #3a3040); background: transparent; color: var(--muted, #b8afc4); }
.sweep-error { color: #f6a0c0; font-size: 13px; padding: 2px 4px; }

/* ---------- each studio at a glance ---------- */
.studio-strip { display: flex; gap: .5rem; align-items: center; overflow-x: auto; padding: .7rem 1rem .6rem; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
.studio-group { flex: 0 0 auto; color: var(--muted); font-size: .74rem; font-weight: 800; letter-spacing: .07em; text-transform: uppercase; padding: 0 .15rem 0 .35rem; }
.studio-group:first-child { padding-left: 0; }
.studio-strip::-webkit-scrollbar { display: none; }
.studio-strip[hidden] { display: none; }
.studio-chip {
  flex: 0 0 auto; display: inline-flex; align-items: center; gap: .45rem;
  border: 1px solid var(--hairline); background: var(--surface-2); color: var(--ink);
  border-radius: 999px; padding: .55rem .95rem; font: inherit; font-size: .9rem; font-weight: 700; cursor: pointer;
}
/* A ring drawn inside the chip: an outline sits outside the box and the strip's
   horizontal scroll clips it against the rows above and below. */
.studio-chip.active { border-color: var(--ink); box-shadow: inset 0 0 0 1.5px var(--ink); }
.studio-chip.done.active { border-color: #4FCF95; box-shadow: inset 0 0 0 1.5px #4FCF95; }
.studio-chip .left { font-size: .78rem; font-weight: 800; color: var(--sitting); }
.studio-chip .left.dim { color: var(--muted); }
.studio-chip.done { border-color: var(--present); background: rgba(27,122,79,.20); color: #4FCF95; }
.studio-chip.done .left { color: #4FCF95; font-size: .95rem; }

/* The dropdown is the mechanism, the pills are the control (owner, 2026-09-10). */
#area-select { display: none; }

/* Find a dancer: sits with the sweeper picker and the walk controls. */
#find { flex: 1 1 10rem; min-width: 9rem; font: inherit; font-size: 1rem; padding: .6rem .8rem; border-radius: 12px; border: 1px solid var(--hairline); background: var(--surface-2); color: var(--ink); }
#find::placeholder { color: var(--muted); }
#find::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; height: 1em; width: 1em; background: var(--muted); border-radius: 50%; }

/* ---------- the sweeper control, a control ---------- */
/* A native select rendered as a white line on the iPad (owner: "make it more
   clear other than a white line that that is where you go to click your name").
   Styled as the app's own pill, with a person mark and a chevron. */
#sweeper-select {
  flex: 0 1 15rem; min-width: 11rem;
  -webkit-appearance: none; appearance: none;
  font: inherit; font-size: .95rem; font-weight: 700; color: var(--ink);
  background-color: var(--surface-2); border: 1px solid var(--hairline); border-radius: 12px;
  padding: .65rem 2.1rem .65rem 2.4rem;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23b8afc4' stroke-width='2' stroke-linecap='round'><circle cx='12' cy='8' r='4'/><path d='M4 21c0-4 4-6 8-6s8 2 8 6'/></svg>"),
                    url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23b8afc4' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'><path d='M6 9l6 6 6-6'/></svg>");
  background-repeat: no-repeat, no-repeat;
  background-position: .7rem center, right .7rem center;
  background-size: 1.15rem, .9rem;
}
#sweeper-select.unset { color: #f2c56b; border-color: #a67a2a; }

/* ---------- Find: one line per match ---------- */
.list.finding { grid-template-columns: 1fr; }
.list.finding .card { flex-direction: row; align-items: center; gap: .8rem; padding-right: .6rem; }
.list.finding .portrait { flex: 0 0 64px; width: 64px; height: 64px; aspect-ratio: 1 / 1; border-radius: 10px; overflow: hidden; }
.list.finding .who { flex: 1 1 auto; padding: .5rem 0; }
.list.finding .nm { font-size: 1.1rem; }
.list.finding .acts { display: flex; gap: .4rem; padding: 0; }
.list.finding .card.done .acts { display: none; }
.list.finding .result { padding: 0 .6rem; }
.list.finding .badge { position: static; width: 1.7rem; height: 1.7rem; font-size: .95rem; margin-right: .2rem; order: -1; }
.list.finding .card.done::before { display: none; }

/* ---------- the building rows are tappable ---------- */
.sweep-row { cursor: pointer; }
.sweep-row.active { border-color: var(--ink); }
.sweep-view { font-size: .72rem; font-weight: 600; color: var(--muted); margin-left: .35rem; }

/* ---------- the panels: capitals, one building, no scrolling the page behind ---------- */
.picker-title.caps { text-transform: uppercase; letter-spacing: .06em; }
body.modal-open { overflow: hidden; touch-action: none; }
.picker { overscroll-behavior: contain; }
.picker-list { overscroll-behavior: contain; touch-action: pan-y; }
.inb-other { display: block; width: calc(100% - 2rem); margin: 1rem; font: inherit; font-weight: 800; padding: .95rem; border-radius: 12px; border: 1px solid var(--hairline); background: var(--surface-2); color: var(--ink); }

/* Approved out: excused for the day by an approved request. Its own colour --
   neither the green of present nor the amber of sitting out -- so a glance
   says "not here, and that is fine". */
.card[data-outcome="approved_out"].done { box-shadow: inset 0 0 0 3px #2aa1b3; }
.card[data-outcome="approved_out"].done::before { background: #2aa1b3; }
.card[data-outcome="approved_out"].done .portrait::after { background: rgba(42,161,179,.40); }
.card[data-outcome="approved_out"] .badge { background: #2aa1b3; }
.card[data-outcome="approved_out"] .result { color: #7fd6e3; }
