/* ================================================================
   SWO dashboard — "the office, lit from behind"

   The tool is a pane of frosted glass held over the same nebula the
   main site runs on. One rule keeps that from turning into soup:

     GLASS IS THE FRAME, NEVER THE PAGE.

   The rail and the deck are translucent. Everything text-dense that
   sits on them — inputs, tables, the results popover, the action bar —
   is solid and opaque. Only .rail and .deck carry a backdrop-filter, plus
   .bar on phones where it floats clear of both; nesting more would mean
   re-blurring the live canvas a dozen times a frame.

   Tokens come from base.css (--bg/--ink/--muted/--gold/--gold-2/--line).
   ================================================================ */

:root{
  --void:#0a0708;
  /* Near-neutral on purpose, and this matters more than it looks. A warm grey fill
     over a red sky averages to brown — the pane stops transmitting the colour and
     starts muddying it. A neutral dark fill lets whatever the sky is doing be the
     colour that comes through, which is the only way one set of tokens can sit under
     indigo, plum and ember in turn without being wrong for two of them. */
  --glass:rgba(12,10,12,.52);      /* the rail + the deck */
  --glass-lift:rgba(22,19,21,.74); /* the floating action capsule */
  --field:#151011;                 /* solid input trough — deliberately not glass */
  --field-on:#1f1719;              /* …and its focused state */
  --hair:rgba(255,255,255,.07);
  --hair-2:rgba(255,255,255,.14);
  --gold-hair:rgba(210,147,80,.3);
  --dim:#7b7370;                   /* field labels, hints, row numbers */
  --ok:#5fbf87; --bad:#e0685a;

  /* Popover glass — every menu that opens over the page: the select and combo
     menus, the calendar, the roster search results. One set of four so they cannot
     drift apart; a menu that is glass while the menu beside it is not is worse than
     either choice made consistently. The blur is deliberately much heavier than the
     deck's 9px, because this pane has to destroy the legibility of what is behind it,
     not merely soften it. */
  --pop:rgba(22,18,21,.55);
  --pop-blur:blur(34px) saturate(1.7) brightness(1.08);
  --pop-edge:rgba(255,255,255,.11);
  --pop-shadow:inset 0 1px 0 rgba(255,255,255,.10),0 26px 60px rgba(0,0,0,.55);
}

/* Nothing to blur means the fill is doing all the work alone, and at .55 alpha the
   table underneath would read straight through the menu. Firefox before 103 and any
   locked-down browser land here. */
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  :root{--pop:#1e1618;--pop-blur:none}
}

html,body.dash{height:100%}
/* touch-action backs up the viewport meta's user-scalable=no: some mobile
   browsers still allow a pinch gesture to zoom the layout even with that meta
   present, but honour touch-action on the scrolling element. */
html{touch-action:pan-x pan-y}
body.dash{background:var(--void);overflow:hidden;touch-action:pan-x pan-y;
  font-family:'Inter Tight',system-ui,sans-serif;-webkit-font-smoothing:antialiased}

/* ---------------- backdrop ----------------
   The canvas sits under the veil, both under everything else. Negative
   z-index still paints into the backdrop root, so the panes above can
   sample them — this is the same arrangement the login modal uses to
   blur the hero. */
#bgfx{position:fixed;inset:0;width:100vw;height:100vh;z-index:-2;pointer-events:none}
/* Holds the nebula back to atmosphere, and stands in for it entirely when
   there is no WebGL.

   Only the gold stays coloured. The sky now travels indigo → plum → ember on its
   own, and a fixed red wash on top of it would sit on the blue hours as a bruise —
   whatever colour the canvas is doing has to be allowed to be the colour.

   The neutral darkening runs the other way round now: heaviest at the TOP, almost
   nothing at the bottom. It used to be the reverse, which meant the veil's densest
   black landed exactly on the horizon glow and flattened the one part of the frame
   that is supposed to look lit. Dark sky overhead, fire on the ground. */
/* Darkening only. A warm radial used to sit in here too, over the top-right, and
   between it and the shader's gold bloom the top edge of the frame was permanently
   tinted orange no matter where the colour cycle was. Both are gone: the veil now
   does one job, which is to hold the top of the frame down so the horizon can be
   the brightest thing on screen. */
.veil{position:fixed;inset:0;z-index:-1;pointer-events:none;
  background:linear-gradient(rgba(7,6,7,.34),rgba(6,5,6,.08))}

/* ---------------- the light-catch ----------------
   The one detail that makes glass read as glass and not as a translucent
   div: a specular hairline along the top edge, brightest where the gold
   bloom sits in the nebula behind it. Every pane gets it; almost nothing
   else on this page is decorated. */
.pane{position:relative}
.pane::after{content:'';position:absolute;left:0;right:0;top:0;height:1px;z-index:5;pointer-events:none;
  background:linear-gradient(90deg,transparent,rgba(255,255,255,.2) 34%,rgba(236,196,146,.4) 63%,rgba(255,255,255,.07) 82%,transparent)}

/* ================================================================
   shell
   ================================================================ */
/* Both panes float, held off every edge — that margin of open sky is the whole
   reason the glass reads as glass. */
.dash-wrap{display:none;height:100%;grid-template-columns:252px 1fr;gap:16px;padding:18px}
.dash-wrap.on{display:grid}

/* ---------------- rail ---------------- */
.rail{display:flex;flex-direction:column;padding:26px 16px 18px;min-height:0;
  border-radius:20px;border:1px solid var(--hair);overflow:hidden;
  background:linear-gradient(rgba(13,11,13,.52),rgba(8,7,9,.68));
  backdrop-filter:blur(8px) saturate(1.28);-webkit-backdrop-filter:blur(8px) saturate(1.28);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.045),0 28px 60px rgba(0,0,0,.42)}

.rail-brand{display:flex;align-items:center;gap:11px;margin:0 8px 30px;flex:none;
  text-decoration:none;color:inherit;cursor:pointer;
  transition:opacity .16s}
.rail-brand:hover{opacity:.8}
.rail-brand:hover img{filter:brightness(1.12)}
.rail-brand img{width:31px;height:31px;border-radius:50%;flex:none;transition:filter .16s}
.rail-brand b{display:block;font-size:14.5px;letter-spacing:1.1px;font-weight:700;line-height:1;color:var(--ink)}
.rail-brand span{display:block;font-size:9.5px;letter-spacing:3px;text-transform:uppercase;color:var(--gold);margin-top:5px;transition:color .16s}
.rail-brand:hover span{color:var(--gold-2)}

.rail-nav{display:flex;flex-direction:column;gap:1px;flex:none}
/* No fill on any item, active or otherwise. A tinted rounded block behind the
   current page is the default every framework ships and it does the same job
   twice — the gold bar on the rail's own edge already says which page you are
   on, and gold text says it again. The label just lights up. */
.rail-nav button{position:relative;display:flex;align-items:center;gap:11px;width:100%;
  padding:11px 13px;border:0;border-radius:0;background:transparent;color:var(--muted);
  font:inherit;font-size:13px;text-align:left;white-space:nowrap;cursor:pointer;
  transition:color .22s,letter-spacing .3s cubic-bezier(.16,1,.3,1)}
.rail-nav button::before{content:'';position:absolute;left:-16px;top:50%;width:2px;height:0;border-radius:0 2px 2px 0;
  background:var(--gold);transform:translateY(-50%);transition:height .3s cubic-bezier(.16,1,.3,1)}
.rail-nav button:hover{color:var(--ink)}
.rail-nav button:hover::before{height:11px;background:var(--hair-2)}
.rail-nav button.on{color:var(--gold-2)}
.rail-nav button.on::before,.rail-nav button.on:hover::before{height:21px;background:var(--gold)}
.rail-nav button.on svg{color:var(--gold)}
.rail-nav svg{width:16px;height:16px;flex:none;stroke:currentColor;fill:none;stroke-width:1.6}

/* ---------------- the rail readout ----------------
   One panel per view, only the active one shown. The shape is shared — a big
   serif number, a label, a quiet sub-line — so a new tool can drop a panel in
   and look native without inventing anything. The ticks belong to the yellow
   form alone: what a form is, underneath the formatting, is a count of bodies,
   so the rail keeps it the way you would on paper. */
.stat{margin-top:auto;padding:22px 12px;border-top:1px solid var(--hair);flex:none;
  animation:statIn .4s cubic-bezier(.16,1,.3,1) both}
.stat[hidden]{display:none}
@keyframes statIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
.stat-n{font-family:'Fraunces',Georgia,serif;font-weight:600;font-size:52px;line-height:.86;
  letter-spacing:-.035em;color:var(--gold-2);font-variant-numeric:tabular-nums;
  text-shadow:0 0 34px rgba(210,147,80,.28)}
.stat-lbl{font-size:10px;letter-spacing:2.6px;text-transform:uppercase;color:var(--dim);margin-top:11px}
.ticks{display:flex;flex-wrap:wrap;gap:3px;margin-top:16px;min-height:14px}
.ticks i{width:2px;height:14px;border-radius:1px;background:var(--gold);opacity:.62;
  animation:tickIn .32s cubic-bezier(.16,1,.3,1) both}
.ticks i:nth-child(5n){margin-right:6px}
.ticks .more{width:auto;height:auto;background:none;font-style:normal;font-size:10px;
  color:var(--dim);letter-spacing:.6px;align-self:flex-end;line-height:1;opacity:1}
@keyframes tickIn{from{opacity:0;transform:scaleY(.2)}to{opacity:.62;transform:none}}
.stat-sub{margin-top:18px;font-size:11.5px;color:var(--dim);letter-spacing:.2px}
.stat-sub b{color:var(--muted);font-weight:600;font-variant-numeric:tabular-nums}

/* Says the draft is safe without ever asking to be looked at. It appears only
   after the first keystroke and never moves anything around it. */
.draft-state{display:flex;align-items:center;gap:7px;margin-top:12px;
  font-size:10.5px;letter-spacing:.3px;color:#5e5e66;transition:color .3s}
.draft-state[hidden]{display:none}
.draft-state::before{content:'';width:5px;height:5px;border-radius:50%;flex:none;
  background:currentColor;transition:background .3s}
.draft-state.saving{color:var(--gold)}
.draft-state.restored{color:var(--ok)}

.rail-foot{padding-top:16px;border-top:1px solid var(--hair);flex:none}
.lnk{display:block;width:100%;padding:6px 4px;border:0;background:none;color:var(--muted);font:inherit;
  font-size:12.5px;text-align:left;cursor:pointer;transition:color .2s}
.lnk:hover{color:var(--ink)}
.lnk-back{display:flex;align-items:center;gap:8px;color:var(--gold);margin-bottom:2px}
.lnk-back:hover{color:var(--gold-2)}
.lnk-back svg{width:14px;height:14px;flex:none;stroke:currentColor;fill:none;stroke-width:1.8;
  transition:transform .25s cubic-bezier(.16,1,.3,1)}
.lnk-back:hover svg{transform:translateX(-3px)}
.rail-foot small{display:block;margin-top:6px;padding:0 4px;color:#5e5e66;font-size:10.5px;line-height:1.6}

/* ================================================================
   deck — one continuous plane. No cards, no nested panels.
   ================================================================ */
/* The frosting lives on ::before, NOT on .deck itself, and that is the only reason
   the glass menus inside work.

   An element with a backdrop-filter becomes a Backdrop Root, and a nested
   backdrop-filter can only sample what is behind that root — never what is painted
   inside it. With the blur on .deck, a menu opening over the form could blur the sky
   and nothing else: the field labels underneath it came through pin-sharp no matter
   how far the blur was pushed, because they were not in its backdrop at all. Moving
   the filter to a pseudo-element leaves .deck an ordinary box, so the menus sample
   the real page and blur the form the way glass should.

   The pseudo sits behind the views and blurs the sky through .deck's own fill; the
   result is the same frosting as before, one layer down. */
.deck{position:relative;display:flex;flex-direction:column;min-width:0;min-height:0;
  border-radius:20px;overflow:hidden;border:1px solid var(--hair);
  background:var(--glass);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.05),0 34px 80px rgba(0,0,0,.5)}
.deck::before{content:'';position:absolute;inset:0;z-index:0;pointer-events:none;
  backdrop-filter:blur(9px) saturate(1.3);-webkit-backdrop-filter:blur(9px) saturate(1.3)}
/* Every direct child, not just .view — the action bar is a sibling of the views, and
   left in the static flow it painted UNDER the frosting and came out blurred. */
.deck > *{position:relative;z-index:1}

.view{display:none;flex-direction:column;flex:1;min-height:0}
/* backwards, NOT both — and this is load-bearing, not tidying.
   `both` includes `forwards`, which leaves the element under the animation's control
   after it ends. Even though the last keyframe is `transform:none`, Chrome keeps a
   transformed layer on it forever, and an ancestor with a transform severs a
   descendant's backdrop-filter from everything behind it: the glass menus inside
   this view rendered as flat translucent fills with the form text showing straight
   through, and no amount of blur changed anything because the blur was not running.
   `backwards` only fills before the animation starts. The last keyframe already
   matches the element's own styles, so dropping the forwards fill changes nothing
   visible about the entrance. Same reasoning for .bulk.on and .hist-body below. */
.view.on{display:flex;animation:deckIn .55s cubic-bezier(.16,1,.3,1) backwards}
@keyframes deckIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}

/* ---------------- deck head ---------------- */
.deck-head{flex:none;display:flex;align-items:flex-end;justify-content:space-between;gap:32px;flex-wrap:wrap;
  padding:30px 34px 24px;border-bottom:1px solid var(--hair)}
.kicker{display:block;font-size:9.5px;letter-spacing:3.4px;text-transform:uppercase;color:var(--gold);margin-bottom:12px}
.deck-head h2{font-family:'Fraunces',Georgia,serif;font-weight:600;font-size:clamp(27px,2.5vw,37px);
  letter-spacing:-.032em;line-height:1}
.deck-note{color:var(--muted);font-size:12.5px;line-height:1.65;max-width:44ch;padding-bottom:3px}
.head-acts{display:flex;gap:24px;padding-bottom:3px}

/* the only scroll container on the page — which also caps how much glass the
   browser ever has to blur */
/* the tail used to be 118px of dead space held open under the floating capsule —
   the docked bar takes that room for real now */
.deck-body{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;padding:0 34px 34px}
.deck-body::-webkit-scrollbar{width:10px}
.deck-body::-webkit-scrollbar-thumb{background:rgba(255,255,255,.1);border-radius:99px;border:3px solid transparent;background-clip:content-box}
.deck-body::-webkit-scrollbar-thumb:hover{background:rgba(255,255,255,.2);background-clip:content-box}

/* ---------------- sections ----------------
   Divided by a hairline, not by a card. .band is setup; .stage is the work. */
.band{padding:26px 0 30px;border-bottom:1px solid var(--hair)}
.stage{padding:30px 0 0}
.stage-top{padding-top:26px}
/* the form's commit action, on its own line at the end of the fields rather than
   sitting in the field flow as if it were one */
.band-go{display:flex;justify-content:flex-end;margin-top:26px}

.sec{display:flex;align-items:center;gap:12px;font-size:10.5px;font-weight:500;letter-spacing:3.2px;
  text-transform:uppercase;color:var(--gold);margin-bottom:20px}
.sec::before{content:'';width:20px;height:1px;background:var(--gold);flex:none;opacity:.85}
.sec .count{font-style:normal;letter-spacing:.4px;text-transform:none;color:var(--dim);font-size:12px;
  font-variant-numeric:tabular-nums}

.stage-head{display:flex;align-items:center;justify-content:space-between;gap:20px;flex-wrap:wrap;margin-bottom:20px}
.stage-head .sec{margin-bottom:0}

/* ================================================================
   fields — an explicit 12-column grid, so a date field is date-width
   and a name field is name-width
   ================================================================ */
.grid{display:grid;grid-template-columns:repeat(12,1fr);gap:16px 18px}
/* align-self:start so a field with a trailing hint doesn't stretch to its row and
   drag its own input out of line with its neighbours */
.f{display:flex;flex-direction:column;gap:7px;min-width:0;grid-column:span 12;align-self:start}
.s2{grid-column:span 2}.s3{grid-column:span 3}.s4{grid-column:span 4}.s5{grid-column:span 5}
/* the slot that holds a button instead of a field: sit it on the baseline of the
   inputs beside it, and let it be its own width rather than the column's */
.f-go{justify-content:flex-end;align-items:flex-start}

/* min-height reserves the label's line box and the text sits at the bottom of it,
   so inputs in a row stay on one line even when a neighbouring label wraps —
   see the 1120px block, where the reserve goes to two lines. */
.f label{display:flex;align-items:flex-end;gap:7px;min-height:12px;font-size:9.5px;letter-spacing:1.7px;
  text-transform:uppercase;color:var(--dim)}
.f .hint{font-size:9.5px;letter-spacing:.9px;color:#57575f;text-transform:none}
.f > .hint{letter-spacing:.2px;font-size:11px;line-height:1.5}

/* ---------------- underline fields ----------------
   No box. The value sits on a rule that lights gold on focus — the rounded filled
   troughs read as a component library, and this reads as the same editorial page
   the Fraunces headings come from. The focus rule thickens via box-shadow rather
   than border-width so nothing shifts by a pixel. */
input,textarea{width:100%;background:transparent;border:0;border-bottom:1px solid var(--hair-2);
  border-radius:0;padding:9px 2px;color:var(--ink);font:inherit;font-size:14px;
  transition:border-color .22s,box-shadow .22s,background .22s}
input:hover,textarea:hover{border-bottom-color:rgba(255,255,255,.3)}
input:focus,textarea:focus{outline:0;border-bottom-color:var(--gold);box-shadow:0 1px 0 0 var(--gold)}
input::placeholder,textarea::placeholder{color:#55555d}
input[type=date]{font-variant-numeric:tabular-nums}
/* Chrome's calendar button — the popup it opens is the "default old drop down box".
   Hidden; .pick-icon opens ours instead. Typing into the field still works. */
input[type=date]::-webkit-calendar-picker-indicator{display:none;-webkit-appearance:none}
input[type=date]::-webkit-inner-spin-button{display:none}
input[type=number]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}

/* the textarea is the one field that still needs an edge to contain a block of
   pasted text — a left rule rather than a box */
textarea{border:0;border-left:2px solid var(--gold-hair);padding:12px 14px;
  background:rgba(0,0,0,.24)}
textarea:hover{border-left-color:rgba(210,147,80,.5);box-shadow:none}
textarea:focus{border-left-color:var(--gold);box-shadow:none;background:rgba(0,0,0,.34)}

/* ================================================================
   custom select / date / combo popups
   The native <select> menu, date calendar and <datalist> are OS chrome and
   cannot be styled — these replace the popups. See js/dashboard/fields.js.
   ================================================================ */
.pick{position:relative;display:flex;align-items:center}
/* the real <select>, kept as the value holder and reachable by nothing else */
.pick-native{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none;border:0;padding:0}

/* the select's stand-in — dressed exactly like an underline input */
.pick-trigger{display:flex;align-items:center;gap:10px;width:100%;background:transparent;
  border:0;border-bottom:1px solid var(--hair-2);border-radius:0;padding:9px 2px;
  color:var(--ink);font:inherit;font-size:14px;text-align:left;cursor:pointer;
  transition:border-color .22s,box-shadow .22s}
.pick-trigger:hover{border-bottom-color:rgba(255,255,255,.3)}
.pick.on .pick-trigger{border-bottom-color:var(--gold);box-shadow:0 1px 0 0 var(--gold)}
.pick-val{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pick-val.is-ph{color:#55555d}
.pick-chev{width:11px;height:7px;flex:none;color:var(--dim);transition:transform .26s cubic-bezier(.16,1,.3,1),color .2s}
.pick.on .pick-chev{transform:rotate(180deg);color:var(--gold)}

/* date + combo keep their real input; it just has to share the row */
.pick-input{flex:1;min-width:0}
.pick-icon{position:absolute;right:0;bottom:5px;width:28px;height:28px;display:grid;place-items:center;
  border:0;border-radius:7px;background:transparent;color:var(--dim);cursor:pointer;
  transition:color .2s,background .2s}
.pick-icon:hover{color:var(--gold);background:rgba(210,147,80,.1)}
.pick-icon svg{width:15px;height:15px}
.pick.on .pick-icon{color:var(--gold)}
.pick-date .pick-input{padding-right:32px}

/* the popup itself — liquid glass.
   It used to be a solid #1e1618, on the reasoning that a popover sitting over dense
   text has to stay legible. That constraint has not changed; it just turns out a
   hard blur satisfies it better than an opaque fill does. Past about 30px the rows
   underneath stop being text at all and become a soft field of tone, so nothing
   under there competes with the labels — and unlike an opaque rectangle, the menu
   still reads as a pane lifted off the page rather than a hole cut through it.

   Same recipe as the mobile nav drawer and the login card, which is the point:
   heavy blur, saturation so the sky's colour survives the frosting instead of
   going grey, a little brightness so the pane lifts rather than sinks, and the
   specular top hairline that every other pane on this page carries. */
.pick-menu{position:absolute;top:calc(100% + 7px);left:0;min-width:100%;z-index:60;
  background:var(--pop);border:1px solid var(--pop-edge);border-radius:12px;padding:5px;
  backdrop-filter:var(--pop-blur);-webkit-backdrop-filter:var(--pop-blur);
  box-shadow:var(--pop-shadow);
  opacity:0;visibility:hidden;transform:translateY(-5px);pointer-events:none;
  transition:opacity .18s ease,transform .22s cubic-bezier(.16,1,.3,1),visibility 0s linear .22s}
.pick.on .pick-menu{opacity:1;visibility:visible;transform:none;pointer-events:auto;
  transition:opacity .18s ease,transform .22s cubic-bezier(.16,1,.3,1),visibility 0s}
.pick-select .pick-menu,.pick-combo .pick-menu{max-height:264px;overflow-y:auto}

.pick-menu > button{display:flex;align-items:center;gap:10px;width:100%;padding:9px 11px;border:0;
  border-radius:8px;background:transparent;color:var(--ink);font:inherit;font-size:13.5px;
  text-align:left;cursor:pointer;transition:background .15s,color .15s}
.pick-menu > button span{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pick-menu > button svg{width:13px;height:10px;flex:none;color:var(--gold)}
/* .06 was tuned against a solid panel; on glass it disappears, because the pane it
   has to stand out from is already carrying a lift of its own. */
.pick-menu > button:hover,.pick-menu > button.hi{background:rgba(255,255,255,.10)}
.pick-menu > button.on{color:var(--gold-2)}

/* ---------------- calendar ---------------- */
.pick-date .pick-menu{width:274px;padding:12px}
.cal-head{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:11px}
.cal-head b{font-family:'Fraunces',Georgia,serif;font-weight:600;font-size:15px;letter-spacing:-.01em}
.cal-head button{width:27px;height:27px;display:grid;place-items:center;border:0;border-radius:7px;
  background:transparent;color:var(--dim);cursor:pointer;transition:background .18s,color .18s}
.cal-head button:hover{background:rgba(255,255,255,.07);color:var(--ink)}
.cal-head svg{width:14px;height:14px}
.cal-dow{display:grid;grid-template-columns:repeat(7,1fr);gap:2px;margin-bottom:4px}
.cal-dow i{font-style:normal;text-align:center;font-size:9.5px;letter-spacing:1.2px;color:#55555d}
.cal-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:2px}
.cal-grid i{display:block}
.cal-grid button{height:32px;border:0;border-radius:8px;background:transparent;color:var(--ink);
  font:inherit;font-size:12.5px;font-variant-numeric:tabular-nums;cursor:pointer;
  transition:background .15s,color .15s}
.cal-grid button:hover{background:rgba(255,255,255,.08)}
.cal-grid button.today{color:var(--gold-2);font-weight:600}
.cal-grid button.on{background:var(--gold);color:#17110a;font-weight:600}
.cal-foot{display:flex;gap:6px;margin-top:11px;padding-top:10px;border-top:1px solid var(--hair)}
.cal-foot button{flex:1;padding:7px;border:0;border-radius:7px;background:transparent;color:var(--muted);
  font:inherit;font-size:11.5px;cursor:pointer;transition:background .18s,color .18s}
.cal-foot button:hover{background:rgba(255,255,255,.06);color:var(--ink)}

/* ================================================================
   buttons — two tiers, and only one of them has a shape.

   A row of outlined capsules is what a page looks like when every action
   was given a box because a box is what buttons come in. It also flattens
   the hierarchy: Add everyone looked exactly as consequential as Generate
   Excel. So the utility tier loses its box entirely and is set as text on
   a rule — the same rule the fields sit on, which is already the page's
   one recurring mark — and the gold pill is left as the only enclosed
   object on screen, which is what makes it read as the thing that commits.
   ================================================================ */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;
  padding:7px 1px 6px;border:0;border-bottom:1px solid var(--hair-2);border-radius:0;
  background:transparent;color:var(--muted);
  font:inherit;font-size:12.5px;font-weight:500;cursor:pointer;white-space:nowrap;
  transition:color .22s,border-color .22s,box-shadow .22s,opacity .2s}
/* thickened with a shadow rather than the border, so nothing shifts a pixel —
   the same trick the focused fields use */
.btn:hover{color:var(--ink);border-bottom-color:var(--gold);box-shadow:0 1px 0 0 var(--gold)}
.btn:disabled{opacity:.32;cursor:not-allowed;box-shadow:none;border-bottom-color:var(--hair-2);color:var(--muted)}
.btn svg{width:15px;height:15px;stroke:currentColor;fill:none;stroke-width:1.7}

/* primary — the site's own CTA, verbatim: gold pill, uppercase, tracked out.
   The only filled, enclosed thing on the page. */
.btn.pri{padding:12px 22px;border:1px solid var(--gold);border-radius:99px;background:var(--gold);
  box-shadow:none;color:#17110a;font-size:10.5px;font-weight:600;letter-spacing:2px;
  text-transform:uppercase;gap:9px;transition:background .2s,border-color .2s,gap .25s,opacity .2s}
.btn.pri:hover{background:var(--gold-2);border-color:var(--gold-2);box-shadow:none;color:#17110a;gap:13px}
.btn.pri:disabled{opacity:.34}
.btn.pri:disabled:hover{background:var(--gold);border-color:var(--gold);gap:9px}
.btn.pri.sm{padding:10px 18px;font-size:10px}

/* destructive — no border, so it stops competing with the primary action */
/* destructive — no rule under it either, so it never reads as an equal option
   sitting alongside the others */
.btn.quiet{border-bottom-color:transparent;color:var(--dim)}
.btn.quiet:hover{border-bottom-color:transparent;box-shadow:none;color:var(--muted)}
.btn.quiet.danger:hover{color:var(--bad)}

.btn-row{display:flex;gap:24px;flex-wrap:wrap;align-items:center}

/* ================================================================
   picker
   ================================================================ */
.picker{position:relative;margin-bottom:18px}
.picker input{padding-left:38px}
.picker .mag{position:absolute;left:14px;top:50%;transform:translateY(-50%);width:14px;height:14px;
  stroke:var(--dim);fill:none;stroke-width:1.8;pointer-events:none;z-index:1}
.picker-sm{margin-bottom:0;width:min(280px,100%)}
.picker-sm input{padding-top:8px;padding-bottom:8px;font-size:12.5px}

/* the search box in the yellow-form browse panel carries the magnifier the same way */
.pick-search{position:relative;margin-bottom:12px}
.pick-search input{width:100%}

/* ---- contextual "add all in this wing" bar, shown only when the list is filtered ---- */
/* the [hidden] guard has to be explicit: display:flex would otherwise override the
   attribute's default display:none and the bar would never hide (leaving a stale label). */
.pick-bar{display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:9px 4px 12px;margin-bottom:2px}
.pick-bar[hidden]{display:none}
.pick-bar-sum{font-size:11.5px;letter-spacing:.3px;color:var(--dim);
  font-variant-numeric:tabular-nums;white-space:nowrap}
.pick-bar-add{height:32px;padding:0 15px;border:1px solid var(--gold-hair);border-bottom:1px solid var(--gold-hair);
  border-radius:999px;color:var(--gold-2);font-size:12px;
  transition:border-color .2s,color .2s,background .2s,box-shadow .2s}
.pick-bar-add:hover:not(:disabled){border-color:var(--gold);background:rgba(210,147,80,.14);color:var(--gold-2);box-shadow:none}
.pick-bar-add:disabled{border-color:var(--hair);color:var(--dim);opacity:1;box-shadow:none}

/* ---- wing filter chips ---- */
.wing-chips{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:13px}
.chip{display:inline-flex;align-items:center;gap:7px;padding:7px 12px 7px 13px;border-radius:999px;
  border:1px solid var(--hair);background:transparent;color:var(--muted);font:inherit;font-size:12px;
  letter-spacing:.2px;cursor:pointer;transition:color .18s,border-color .18s,background .18s,opacity .18s;white-space:nowrap}
.chip em{font-style:normal;font-size:10.5px;font-variant-numeric:tabular-nums;color:var(--dim);
  background:rgba(255,255,255,.06);padding:1px 6px;border-radius:999px;min-width:17px;text-align:center;transition:inherit}
.chip:hover{border-color:var(--gold-hair);color:var(--ink)}
.chip.on{background:rgba(210,147,80,.16);border-color:var(--gold);color:var(--gold-2)}
.chip.on em{background:rgba(210,147,80,.24);color:var(--gold-2)}
.chip.empty{opacity:.45}
.chip.empty:hover,.chip.empty.on{opacity:1}

/* the small × riding beside a wing's chip — only rendered once that wing has
   entries on the current form, so it never competes with the filter click */
.chip-group{display:inline-flex;align-items:center;gap:4px}
.chip-clear{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;
  border:0;border-radius:999px;background:transparent;color:var(--dim);cursor:pointer;
  transition:color .18s,background .18s}
.chip-clear:hover{background:rgba(224,104,90,.15);color:var(--bad)}
.chip-clear svg{width:12px;height:12px;stroke:currentColor;fill:none;stroke-width:2}

/* ---- the member list: always open, and scrolls on its own so a long wing is fully
   reachable (it used to be a floating dropdown that ran off the bottom of the screen) ---- */
.roster-browse{max-height:min(46vh,360px);overflow-y:auto;overscroll-behavior:contain;
  border:1px solid var(--hair);border-radius:12px;background:rgba(255,255,255,.014)}
.roster-browse button{display:flex;align-items:center;gap:12px;width:100%;padding:11px 15px;border:0;background:transparent;
  color:var(--ink);font:inherit;font-size:13px;text-align:left;cursor:pointer;border-bottom:1px solid var(--hair)}
.roster-browse button:last-child{border-bottom:0}
.roster-browse button:hover,.roster-browse button.hi{background:rgba(210,147,80,.15)}
/* Already on the form — dimmed like the old disabled state, but still a real
   button: clicking it now toggles the student back off, so the hover wash
   turns to the "bad" red to read as a removal rather than another add. */
.roster-browse button.added{opacity:.55}
.roster-browse button.added:hover{opacity:1;background:rgba(224,104,90,.16)}
.roster-browse .r-name{font-weight:500;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.roster-browse .r-meta{color:var(--dim);font-size:11.5px;white-space:nowrap;font-variant-numeric:tabular-nums}
.roster-browse .r-new{color:var(--gold-2)}
.roster-browse .r-newrow{justify-content:flex-start}
.roster-browse .empty{padding:26px 16px;color:var(--muted);font-size:12.5px;text-align:center}

/* ================================================================
   roster stage tools — sort + edit
   No underlines: sort dropdown, direction arrow and edit toggle are a matched set
   of quiet rounded pills, aligned by their centres so they read as one clean row.
   ================================================================ */
.stage-tools{display:flex;align-items:center;gap:10px;flex-wrap:wrap}

.sortbar{display:flex;align-items:center;gap:8px}
.sortbar > label{font-size:9.5px;letter-spacing:1.7px;text-transform:uppercase;color:var(--dim);
  white-space:nowrap;margin-right:3px}

/* the shared pill shape — a hairline outline that warms to gold on hover, no bottom line */
.sortbar .pick{width:auto;min-width:0}
.sortbar .pick-trigger{gap:9px;height:36px;padding:0 15px;border:1px solid var(--hair);
  border-bottom:1px solid var(--hair);border-radius:999px;font-size:13px;color:var(--ink);
  transition:border-color .2s,background .2s,box-shadow .2s}
.sortbar .pick-trigger:hover{border-color:var(--gold-hair);box-shadow:none}
.sortbar .pick.on .pick-trigger{border-color:var(--gold);box-shadow:none;background:rgba(210,147,80,.08)}
.sortbar .pick-val{flex:0 0 auto}

/* direction arrow — a round icon button in the same pill language */
.sort-dir{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;
  border:1px solid var(--hair);border-radius:999px;background:transparent;color:var(--muted);
  cursor:pointer;transition:border-color .2s,color .2s}
.sort-dir svg{width:15px;height:15px;stroke:currentColor;fill:none;stroke-width:1.8;
  transition:transform .24s cubic-bezier(.16,1,.3,1)}
.sort-dir:hover{border-color:var(--gold-hair);color:var(--ink)}
.sort-dir.desc svg{transform:rotate(180deg)}

/* edit toggle: the same quiet pill, then clearly lit (gold fill) once pressed so
   "the roster is editable right now" is never in doubt. Pencil sits after the label. */
.edit-toggle{display:inline-flex;align-items:center;gap:8px;height:36px;padding:0 17px;margin-left:2px;
  border:1px solid var(--hair);border-bottom:1px solid var(--hair);border-radius:999px;
  color:var(--muted);font-size:12.5px;
  transition:border-color .2s,color .2s,background .2s,box-shadow .2s}
.edit-toggle:hover{border-color:var(--gold-hair);color:var(--ink);box-shadow:none}
.edit-toggle svg{width:14px;height:14px;stroke:currentColor;fill:none;stroke-width:1.8}
.edit-toggle.on{border-color:var(--gold);color:var(--gold-2);background:rgba(210,147,80,.16);box-shadow:none}

/* ---- roster search: full width across the table, sitting just above the rows ---- */
.roster-search{width:100%;margin:2px 0 20px}

/* ---- read-only table: no input chrome, a little more air per row ---- */
.tbl:not(.editing) tbody td{padding:9px 12px}
.tbl:not(.editing) tbody td .mut{color:#4f4f57}
/* sortable headers announce themselves and light the active one */
th.th-sort{cursor:pointer;user-select:none;transition:color .16s}
th.th-sort:hover{color:var(--ink)}
th.th-sort.on{color:var(--gold-2)}
.sort-i{display:inline-block;width:0;height:0;margin-left:6px;vertical-align:middle;
  border-left:4px solid transparent;border-right:4px solid transparent}
.sort-i.up{border-bottom:5px solid currentColor}
.sort-i.dn{border-top:5px solid currentColor}

/* ================================================================
   bulk paste
   ================================================================ */
.bulk{display:none;margin-bottom:20px;padding:2px 0 0}
.bulk.on{display:block;animation:deckIn .4s cubic-bezier(.16,1,.3,1) backwards}
.bulk .f{margin-bottom:13px}
.bulk textarea{min-height:130px;resize:vertical;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
  font-size:12.5px;line-height:1.65}

/* ================================================================
   tables — solid and ruled. No wrapper card, no zebra: the rules
   are enough, and the row is the only thing that lights up.
   ================================================================ */
.tbl{overflow-x:auto;border-top:1px solid var(--gold-hair);margin-top:2px}
table{width:100%;border-collapse:collapse;font-size:13px;min-width:730px}
/* No fill. It carried an opaque #191214 to cover rows scrolling under it, except
   nothing ever does: .tbl has overflow-x:auto, which computes overflow-y to auto
   too, so the sticky header is anchored to .tbl — a box that is exactly as tall as
   its content and therefore never scrolls. The header travels with the page like
   any other row. All that fill did was punch a flat black slab through the glass
   in front of a lit sky. The gold rule above and the hairline below already say
   "header"; the band itself can be the deck. */
thead th{position:sticky;top:0;z-index:2;background:transparent;text-align:left;padding:11px 12px;
  font-weight:500;font-size:9.5px;letter-spacing:1.7px;text-transform:uppercase;color:var(--dim);
  white-space:nowrap;border-bottom:1px solid var(--hair)}
tbody td{position:relative;padding:4px 8px;border-bottom:1px solid rgba(255,255,255,.045);vertical-align:middle;
  white-space:nowrap}
/* Name and class share the slack; every other column packs to its content.
   Class has to be one of them because an <input>'s intrinsic width is its
   min-width, not the width of the text inside it — so a column sized only by
   its content floor will clip a long class no matter how high that floor is. */
tbody td:nth-child(2){width:100%;white-space:normal;min-width:160px}
tbody tr:last-child td{border-bottom:0}
tbody tr{transition:background .16s}
tbody tr:hover{background:rgba(255,255,255,.028)}
/* the gold bar returns here, so a hovered row is marked the same way the active
   nav item is */
tbody tr td:first-child::before{content:'';position:absolute;left:0;top:0;bottom:0;width:2px;
  background:var(--gold);opacity:0;transition:opacity .16s}
tbody tr:hover td:first-child::before{opacity:.75}

/* the inputs set the row height, so they are what has to be tight — a coordinator
   with 60 students should not be scrolling through 3000px of form */
tbody td input,tbody td select{background:transparent;border:1px solid transparent;border-radius:7px;
  padding:5px 8px;font-size:13px;min-width:96px}
tbody td:nth-child(2) input{min-width:150px}
/* One shared 96px floor clipped the longest column: "3 BTCS AIML A" is thirteen
   characters and only eleven fit. Each column is sized to what actually goes in
   it instead — a class string is the longest thing in the roster, an attendance
   figure the shortest. */
tbody td.cls   input{min-width:158px}
tbody td.reg   input{min-width:106px}
tbody td.dept  input{min-width:92px}
tbody td.att   input{min-width:86px}
tbody td.team  input{min-width:98px}
/* "Graphic designing" is the long one and it is the value most often there */
tbody td.dom   input{min-width:152px}
/* A Christ address runs past 40 characters, so this column is the reason the
   roster table scrolls at all. It sits last for that reason: everything the
   yellow form reads is visible before you ever have to scroll. */
tbody td.email input{min-width:300px}
/* A floor only ever fits the class strings you thought of. An <input> has no
   intrinsic content width — the column cannot see the text inside it, so no
   amount of width:100% on the cell will stretch to fit a long one. Letting the
   field size itself is the only thing that does; the column follows it, and the
   floor above still holds for browsers without it. */
@supports (field-sizing:content){
  tbody td.cls input{field-sizing:content;width:auto;max-width:min(330px,30vw)}
  /* same problem, one column over: addresses vary by more than ten characters and
     the floor below only ever fits the shortest of them */
  tbody td.email input{field-sizing:content;width:auto;max-width:min(400px,34vw)}
}
tbody td input:hover{border-color:var(--hair);background:rgba(255,255,255,.03)}
tbody td input:focus{border-color:var(--gold);background:var(--field-on);box-shadow:none}
td.num{width:50px;color:#5c5c64;font-size:11px;text-align:right;padding-right:12px;font-variant-numeric:tabular-nums}
td.reg{font-variant-numeric:tabular-nums;letter-spacing:.3px}
td.act{width:44px;text-align:center}
.icon-btn{width:28px;height:28px;display:inline-grid;place-items:center;border:0;border-radius:8px;
  background:transparent;color:#5c5c64;cursor:pointer;transition:background .2s,color .2s}
.icon-btn:hover{background:rgba(224,104,90,.15);color:var(--bad)}
.icon-btn svg{width:15px;height:15px;stroke:currentColor;fill:none;stroke-width:1.7}

/* ================================================================
   empty states — a destination, not a gap
   ================================================================ */
/* Given real presence rather than left to float in whatever gap is left over —
   on an empty form this is the largest thing on the deck, so it has to look
   deliberate. */
.empty-state{min-height:min(44vh,360px);display:flex;flex-direction:column;align-items:center;
  justify-content:center;padding:44px 24px;text-align:center;border-top:1px solid var(--hair)}
.empty-state b{display:block;font-family:'Fraunces',Georgia,serif;font-weight:600;font-size:19px;
  letter-spacing:-.02em;color:var(--ink);margin-bottom:9px}
.empty-state span{display:block;color:var(--muted);font-size:12.5px;line-height:1.7;max-width:42ch;margin:0 auto}
.empty-state code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11.5px;color:var(--gold-2);
  background:rgba(210,147,80,.09);padding:2px 6px;border-radius:5px}

/* ================================================================
   form history — a filed record, so it is all readout and no inputs
   ================================================================ */
.hist{border-top:1px solid var(--hair)}
.hist:last-child{border-bottom:1px solid var(--hair)}
.hist-head{display:flex;align-items:center;gap:24px;width:100%;padding:17px 4px;border:0;
  background:transparent;color:var(--ink);font:inherit;text-align:left;cursor:pointer;
  transition:background .16s}
.hist-head:hover{background:rgba(255,255,255,.025)}
.hist.on .hist-head{background:rgba(210,147,80,.05)}

.hist-when{display:flex;flex-direction:column;gap:5px;min-width:186px;flex:none}
.hist-when b{font-size:13.5px;font-weight:500;font-variant-numeric:tabular-nums}
.hist-when i{font-style:normal;font-size:9.5px;letter-spacing:2.2px;text-transform:uppercase;color:var(--gold)}

.hist-meta{display:flex;gap:28px;flex:1;min-width:0;flex-wrap:wrap}
.hm{display:flex;flex-direction:column;gap:5px;font-size:13px;color:var(--muted);min-width:0}
.hm i{font-style:normal;font-size:9px;letter-spacing:1.6px;text-transform:uppercase;color:#55555d}

.hist-n{flex:none;font-size:13px;color:var(--muted);white-space:nowrap}
.hist-n b{font-family:'Fraunces',Georgia,serif;font-size:19px;font-weight:600;color:var(--gold-2);
  font-variant-numeric:tabular-nums;margin-right:3px}
.hist-chev{width:11px;height:7px;flex:none;color:var(--dim);
  transition:transform .28s cubic-bezier(.16,1,.3,1)}
.hist.on .hist-chev{transform:rotate(180deg);color:var(--gold)}

.hist-body{padding:0 0 22px;animation:deckIn .4s cubic-bezier(.16,1,.3,1) backwards}
.hist-bar{display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap;
  padding:2px 4px 14px}
.hist-lock{display:flex;align-items:center;gap:8px;font-size:11px;letter-spacing:1.5px;
  text-transform:uppercase;color:var(--dim)}
.hist-lock svg{width:13px;height:13px;flex:none}
.hist-body .tbl{border-top-color:var(--hair)}
.hist-body thead th{background:transparent}

@media(max-width:900px){
  .hist-head{flex-wrap:wrap;gap:14px}
  .hist-when{min-width:0;width:100%}
  .hist-meta{gap:18px}
  .hist-chev{margin-left:auto}
}

/* ================================================================
   home — the landing screen
   No fields, so none of the deck-head / band / stage furniture. Just a hail and
   the way into each tool, in the deck's own vocabulary: a Fraunces greeting, a
   gold kicker, and three hairline-divided rows that borrow the gold left-bar the
   rail and the table rows already use. No cards — the deck has none anywhere.
   ================================================================ */
.home{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;
  display:flex;flex-direction:column;padding:0 34px 40px}
.home::-webkit-scrollbar{width:10px}
.home::-webkit-scrollbar-thumb{background:rgba(255,255,255,.1);border-radius:99px;border:3px solid transparent;background-clip:content-box}

.home-hail{flex:none;padding:clamp(38px,7vh,92px) 0 clamp(28px,4.5vh,46px)}
.home-hail .kicker{margin-bottom:16px}
.home-greet{font-family:'Fraunces',Georgia,serif;font-weight:400;
  font-size:clamp(33px,5.4vw,62px);line-height:1.02;letter-spacing:-.032em;color:var(--ink)}
/* the name is the personal, memorable beat — it carries the gold and a touch more
   weight, and its own soft bloom the way the big rail numbers do */
.home-greet b{font-weight:600;color:var(--gold-2);text-shadow:0 0 38px rgba(210,147,80,.26)}
.home-ask{margin-top:18px;color:var(--muted);font-size:14.5px;letter-spacing:.2px}

.home-tools{flex:none;display:flex;flex-direction:column;border-top:1px solid var(--hair)}
.home-tool{position:relative;display:flex;align-items:center;gap:20px;width:100%;
  padding:24px 8px 24px 22px;border:0;border-bottom:1px solid var(--hair);
  background:transparent;color:var(--ink);font:inherit;text-align:left;cursor:pointer;
  transition:background .2s;
  animation:homeRow .5s cubic-bezier(.16,1,.3,1) backwards;animation-delay:calc(var(--i) * .07s + .12s)}
@keyframes homeRow{from{opacity:0;transform:translateY(9px)}to{opacity:1;transform:none}}
.home-tool:hover{background:rgba(255,255,255,.025)}
/* the recurring mark: the gold bar that says "this one", exactly as on the rail
   and on a hovered table row */
.home-tool::before{content:'';position:absolute;left:0;top:50%;width:2px;height:0;
  background:var(--gold);transform:translateY(-50%);transition:height .3s cubic-bezier(.16,1,.3,1)}
.home-tool:hover::before,.home-tool:focus-visible::before{height:34px}

.home-tool-ic{flex:none;display:grid;place-items:center;width:48px;height:48px;border-radius:13px;
  border:1px solid var(--gold-hair);color:var(--gold);
  transition:background .22s,border-color .22s,color .22s}
.home-tool-ic svg{width:21px;height:21px;stroke:currentColor;fill:none;stroke-width:1.6}
.home-tool:hover .home-tool-ic{background:rgba(210,147,80,.14);border-color:var(--gold);color:var(--gold-2)}

.home-tool-txt{flex:1;min-width:0;display:flex;flex-direction:column;gap:5px}
.home-tool-txt b{font-family:'Fraunces',Georgia,serif;font-weight:600;font-size:clamp(19px,1.9vw,23px);
  letter-spacing:-.02em;line-height:1.1;transition:color .22s}
.home-tool:hover .home-tool-txt b{color:var(--gold-2)}
.home-tool-txt em{font-style:normal;color:var(--muted);font-size:13px;line-height:1.5}

.home-tool-go{flex:none;width:20px;height:20px;stroke:var(--dim);fill:none;stroke-width:1.7;
  transition:transform .28s cubic-bezier(.16,1,.3,1),stroke .22s}
.home-tool:hover .home-tool-go{stroke:var(--gold);transform:translateX(5px)}

@media(max-width:900px){
  .home{overflow:visible;padding:0 16px calc(64px + env(safe-area-inset-bottom))}
  .home-hail{padding:34px 0 26px}
  .home-tool{gap:15px;padding:20px 4px 20px 16px}
  .home-tool-ic{width:42px;height:42px;border-radius:11px}
  .home-tool-ic svg{width:19px;height:19px}
}
@media(max-width:440px){
  .home-tool-ic{display:none}
}
@media(prefers-reduced-motion:reduce){
  .home-tool{animation:none}
}

/* ================================================================
   floating action capsule + toast
   ================================================================ */
/* The last row of the deck, not a thing floating on top of it. Floating cost it
   both ways: it covered the Add everyone / Paste a list row whenever the body was
   scrolled under it, and hanging over the deck's rounded corner read as escaping
   the panel it belongs to. Docked, the body simply stops above it.

   Docked, it also needs no fill of its own. It is a flex sibling of .deck-body,
   not something laid over it, so nothing ever passes beneath it — the dark
   gradient it used to carry was covering nothing and reading as a second, heavier
   panel stuck to the bottom of the first. The hairline is the whole separation;
   below it the deck simply continues. (The mobile block puts the fill back, and
   there it is load-bearing: on a phone this goes back to position:fixed and the
   page really does scroll underneath.) */
.bar{flex:none;display:flex;align-items:center;justify-content:flex-end;gap:18px;
  padding:13px 34px;border-top:1px solid var(--hair)}
.bar.hide{display:none}
.bar .sum{font-size:12.5px;color:var(--muted);white-space:nowrap}
.bar .sum b{color:var(--ink);font-weight:600;font-variant-numeric:tabular-nums}

.toast{position:fixed;bottom:100px;left:50%;transform:translate(-50%,18px);z-index:200;
  padding:12px 20px;border-radius:11px;background:#241b1c;border:1px solid var(--hair-2);font-size:13px;
  box-shadow:0 18px 44px rgba(0,0,0,.6);opacity:0;pointer-events:none;
  transition:opacity .3s,transform .3s;max-width:min(90vw,470px);text-align:center}
.toast.on{opacity:1;transform:translate(-50%,0)}
.toast.ok{border-color:rgba(95,191,135,.45)}
.toast.bad{border-color:rgba(224,104,90,.5)}

/* ---------------- the fault card ----------------
   Only ever seen when something has gone wrong, and written to be read by whoever
   is standing in front of it rather than by a developer: what broke, and the one
   thing worth trying. Sits above everything, including the capsule. */
#fault{position:fixed;left:50%;top:50%;transform:translate(-50%,-50%);z-index:900;
  display:none;flex-direction:column;gap:9px;width:min(92vw,440px);
  padding:24px 26px;border-radius:16px;
  background:#1b1416;border:1px solid rgba(224,104,90,.45);
  box-shadow:0 30px 80px rgba(0,0,0,.7)}
#fault.on{display:flex}
#fault b{font-size:16px;color:var(--ink)}
#fault span{font-size:13px;line-height:1.55;color:var(--muted);
  word-break:break-word;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
#fault a{color:var(--gold);text-decoration:underline;text-underline-offset:3px}
#fault small{margin-top:4px;font-size:11.5px;color:var(--dim);font-family:inherit}

/* ================================================================
   focus — visible, and gold, everywhere
   ================================================================ */
:focus-visible{outline:2px solid var(--gold);outline-offset:2px;border-radius:4px}
input:focus-visible,select:focus-visible,textarea:focus-visible{outline:0}

/* ================================================================
   responsive
   ================================================================ */
@media(max-width:1120px){
  .dash-wrap{grid-template-columns:222px 1fr}
  .deck-head,.deck-body{padding-left:26px;padding-right:26px}
  .stat-n{font-size:44px}
  .f.s2{grid-column:span 3}
  .f.s5{grid-column:span 6}
  /* narrow enough that labels start wrapping — reserve the second line for all of
     them so the inputs stay on one line */
  .f label{min-height:24px}
}

/* Below this the rail turns into a top bar and the page scrolls normally —
   an internally-scrolling deck fights the mobile address bar. */
@media(max-width:900px){
  html,body.dash{height:auto}
  body.dash{overflow:visible;overflow-x:clip}
  /* grid-template-rows pins the top bar to its own content height — leaving
     rows on their implicit "auto" sizing let the grid's default stretch
     alignment split the min-height evenly between the top bar and the deck,
     which inflated the top bar into a tall empty block and shoved the
     greeting down toward the middle of the screen. The 1fr row is what
     still lets the deck alone absorb the leftover space, so the no-gap fix
     below holds. */
  .dash-wrap{height:auto;min-height:100dvh;grid-template-columns:1fr;grid-template-rows:auto 1fr;gap:0;padding:0}

  /* The rail splits in two on a phone: a slim top bar keeps the brand and the
     account links, and the four tools drop to a fixed tab bar within thumb reach
     at the foot of the screen. The old single top row could not hold all four
     tabs at 375px — the last one ("Form history") and the account links were
     pushed clean off the right edge and could not be tapped. */
  /* No backdrop-filter here: it would make .rail the containing block for its
     position:fixed child (.rail-nav), pinning the tab bar to the bottom of this
     thin top bar instead of the viewport. A solid wash covers scroll-under. */
  .rail{flex-direction:row;align-items:center;gap:10px;min-height:0;
    padding:10px 14px;padding-top:max(10px,env(safe-area-inset-top));
    border-radius:0;border:0;border-bottom:1px solid var(--hair);box-shadow:none;
    position:sticky;top:0;z-index:60;
    /* Still solid enough to cover whatever scrolls under this sticky bar, but no
       longer opaque — an opaque top bar over the now-correctly-sized (post
       grid-template-rows fix) header still read as a flat black slab with the
       nebula snuffed out behind it. */
    background:linear-gradient(rgba(16,13,15,.55),rgba(11,9,10,.68));
    backdrop-filter:none;-webkit-backdrop-filter:none}
  .rail::after{display:none}
  .rail-brand{margin:0;flex:none}
  .rail-brand span{display:none}
  .rail-brand b{font-size:13px}

  /* account links ride the top bar's right edge, in a row rather than stacked.
     The rail readout stays hidden — not worth a row of its own on a phone. */
  .stat,.rail-foot small{display:none}
  .rail-foot{margin:0 0 0 auto;padding:0;border:0;flex:none;
    display:flex;align-items:center;gap:16px}
  .rail-foot .lnk{width:auto;padding:4px 0;font-size:12px}
  .lnk-back{margin:0}

  /* ---- bottom tab bar: the four tools, icon over label, equal columns ---- */
  .rail-nav{position:fixed;left:0;right:0;bottom:0;z-index:85;
    flex-direction:row;gap:0;margin:0;
    padding:6px 4px;padding-bottom:max(6px,env(safe-area-inset-bottom));
    border-top:1px solid var(--hair);
    background:linear-gradient(rgba(14,11,13,.86),rgba(9,7,8,.94));
    backdrop-filter:blur(13px) saturate(1.3);-webkit-backdrop-filter:blur(13px) saturate(1.3)}
  .rail-nav button{flex:1;flex-direction:column;align-items:center;justify-content:center;
    gap:5px;width:auto;min-height:48px;padding:5px 2px;
    font-size:10px;letter-spacing:.2px;line-height:1;text-align:center}
  .rail-nav svg{display:block;width:22px;height:22px}
  /* the recurring gold "this one" mark, here a short pip on the bar's top edge */
  .rail-nav button::before{left:50%;top:-7px;bottom:auto;width:0;height:2px;
    border-radius:0 0 2px 2px;transform:translateX(-50%);transition:width .22s}
  .rail-nav button.on::before,.rail-nav button.on:hover::before{width:24px;height:2px}

  /* Still no backdrop-filter here — a full-bleed pane blurring the whole screen on
     a phone GPU is not worth it — but no longer fully transparent either. With the
     sky this much more legible than it used to be, transparent meant field labels
     and placeholders sitting directly on the horizon glow, which on a bright phone
     screen outdoors is simply unreadable. A flat dark wash keeps the colour as
     atmosphere at the edges and hands the text a surface. */
  .deck{border-radius:0;border:0;box-shadow:none;
    /* Lighter than it was: with the grid stretch bug fixed above, this wash
       now covers the full remaining height on every view (that's the no-gap
       fix), not just the strip behind a form's fields — at the old .80/.88
       alpha that meant almost the whole screen read as flat black with the
       colour-cycling nebula barely showing through it. This still holds text
       legible at the bright end of the cycle, just no longer at the cost of
       hiding the sky everywhere else. */
    background:linear-gradient(rgba(11,9,10,.45),rgba(8,6,7,.60));
    backdrop-filter:none;-webkit-backdrop-filter:none}
  .deck::after,.deck::before{display:none}
  .view{min-height:0}
  .deck-head{padding:22px 16px 18px;gap:14px}
  /* tail long enough to scroll the last row clear of the fixed bottom tab bar,
     and of the floating action pill above it on the yellow form */
  .deck-body{overflow:visible;padding:0 16px calc(140px + env(safe-area-inset-bottom))}
  .deck-note{max-width:none}

  /* Matched specificity on purpose: the 1120px block above also uses .f.s5, and a
     bare .s5 here would lose to it. */
  .grid{gap:14px}
  .f,.f.s2,.f.s3,.f.s4,.f.s5{grid-column:span 12}
  .f.s2,.f.s3{grid-column:span 6}

  /* On a phone the deck is full-bleed and the page itself scrolls, so a docked
     footer would just scroll off the top of the thumb's reach. Here — and only
     here — it goes back to floating, with the body holding a tail open for it. */
  .bar{position:fixed;left:14px;right:14px;bottom:calc(66px + env(safe-area-inset-bottom));z-index:80;
    justify-content:space-between;padding:8px 8px 8px 18px;
    border:1px solid var(--hair-2);border-radius:99px;
    background:var(--glass-lift);
    backdrop-filter:blur(9px) saturate(1.3);-webkit-backdrop-filter:blur(9px) saturate(1.3);
    box-shadow:inset 0 1px 0 rgba(255,255,255,.09),0 20px 50px rgba(0,0,0,.55)}
  .picker-sm{width:100%}

  /* The roster table is wider than a phone, so it scrolls sideways inside its own
     box (Wing and e-mail live past the right edge). Momentum scroll plus a fade
     on the right edge so it reads as "more this way", not "cut off". */
  #rosterTableWrap{position:relative}
  #rosterTableWrap .tbl{-webkit-overflow-scrolling:touch}
  #rosterTableWrap::after{content:'';position:absolute;top:0;right:0;bottom:0;width:34px;
    pointer-events:none;background:linear-gradient(to right,rgba(8,6,7,0),rgba(8,6,7,.92))}

  .stage-head{gap:12px}
  .toast{bottom:calc(140px + env(safe-area-inset-bottom))}
}

/* Two exports plus a running count is one thing too many for a capsule 347px wide,
   and the gold pill is what gets pushed off the edge. The count goes: it is the
   least load-bearing of the three, and the Students section header two thumbs up
   the page is already saying "3 added". */
@media(max-width:620px){
  .bar .sum{display:none}
  .bar{padding:8px 8px 8px 14px}
}

@media(prefers-reduced-motion:reduce){
  .view.on,.bulk.on,.bar,.ticks i{animation:none}
  *{transition-duration:.01ms!important}
}
