/* ============================================================================
   nav.css -- styling for the navigation rail. Companion to nav.js.

   Extracted from 14 HTML files on 2026-07-29, after nav.js took the markup.
   The audit found exactly TWO variants and no drift to reconcile:
     * 10 pages carried an identical 3,550-char block;
     * the 4 study pages carried the same block plus 18 lines of player rules.
   This file is that superset, so one stylesheet serves both audiences. The
   player rules are scoped to .player-nav / .page-row.player-mode, which only
   exist once nav.js mounts a player -- inert everywhere else.

   ONE NEAR-MISS WORTH RECORDING: the first pass at finding the block matched
   the word "rail" and swallowed 15KB of game-day-canvas.html, because that
   page has a .gdc-rail -- the slide filmstrip, an entirely different thing.
   Extracting on that boundary would have deleted the Game Day Canvas
   filmstrip's styling. If you ever automate over this file again, match the
   rail's actual class names, not the substring.

   THE RULE, same as nav.js: on the builder and canvas pages this rail is
   position:fixed at every width and never enters the layout, because pushing
   content reflows hand-tuned canvas math. Only the PLAYER rail goes in-flow,
   only on the study pages, and only above 840px -- players do not carry the
   canvas-width constraint. selftest asserts the fixed rule.

   Colours come from the page's own :root tokens, so team branding still
   applies -- custom properties resolve at use, not at load.
   ============================================================================ */

  .nav-rail{
  position:fixed; top:0; left:0; height:100vh; height:100dvh; width:280px;
  background:var(--surface-container); border-right:1px solid var(--outline-variant);
  display:flex; flex-direction:column; z-index:500;
  transform:translateX(-100%); transition:transform .25s ease;
  overflow-y:auto; box-shadow:2px 0 8px rgba(0,0,0,0.16), 4px 0 20px rgba(0,0,0,0.12);
}
.nav-rail.open{ transform:translateX(0); }
.rail-backdrop{
  position:fixed; inset:0; background:rgba(0,0,0,0.4); z-index:490;
  opacity:0; pointer-events:none; transition:opacity .2s ease;
}
.rail-backdrop.open{ opacity:1; pointer-events:auto; }
.rail-menu-btn{
  position:fixed; top:18px; left:16px; z-index:450;
  width:38px;height:38px;border-radius:50%;
  border:1px solid var(--outline-variant); background:var(--surface-container);
  color:var(--on-background); cursor:pointer;
  display:flex; align-items:center; justify-content:center;
  box-shadow:var(--elevation-card);
}
.rail-header{
  display:flex; align-items:center; justify-content:space-between;
  padding:16px 12px; flex:0 0 auto;
}
.rail-brand{font-family:'Roboto Condensed',sans-serif;font-weight:700;font-size:14px;color:var(--on-background);text-decoration:none;}
.rail-close-btn{
  width:32px;height:32px;border-radius:50%;border:none;background:none;
  color:var(--on-background);cursor:pointer;display:flex;align-items:center;justify-content:center;
}
.rail-close-btn:hover{background:var(--surface-container-high);}
.rail-header-actions{display:flex;align-items:center;gap:2px;}
.rail-pin-btn{
  width:32px;height:32px;border-radius:50%;border:none;background:none;
  color:var(--on-background);cursor:pointer;display:flex;align-items:center;justify-content:center;
}
.rail-pin-btn:hover{background:var(--surface-container-high);}
.rail-pin-btn.pinned{color:var(--primary);}
.rail-items{flex:1;display:flex;flex-direction:column;padding:4px 8px;gap:2px;}
.rail-section{position:relative;}
.rail-item{
  display:flex;align-items:center;gap:14px;width:100%;text-align:left;
  text-decoration:none;background:none;border:none;cursor:pointer;
  padding:11px 12px;border-radius:12px;
  font-family:'Inter',sans-serif;font-size:13.5px;font-weight:500;
  color:var(--on-background);
  appearance:none;-webkit-appearance:none;
}
.rail-item:hover{background:var(--surface-container-high);}
.rail-item.active{background:color-mix(in srgb, var(--primary) 14%, transparent);color:var(--primary);font-weight:700;}
.rail-item .material-symbols-outlined{font-size:22px;flex:0 0 auto;}
.rail-item .rail-label{flex:1;}
.rail-item.rail-section-label{cursor:default;}
.rail-item.rail-section-label:hover{background:none;}
.rail-subsection{display:block;}
.rail-subitem{
  display:block;padding:9px 12px 9px 48px;text-decoration:none;
  font-family:'Inter',sans-serif;font-size:13px;color:var(--dim);border-radius:10px;
}
.rail-subitem:hover{background:var(--surface-container-high);color:var(--on-background);}
.rail-subitem.active{color:var(--primary);font-weight:700;}
.rail-subgroup-label{
  font-family:'Roboto Condensed',sans-serif;font-weight:700;font-size:10.5px;
  text-transform:uppercase;letter-spacing:0.04em;color:var(--dim);
  padding:8px 12px 2px 48px;margin-top:6px;
}
.rail-subgroup-label:first-child{margin-top:0;}
.rail-subitem-bullet{padding-left:40px;display:flex;align-items:center;gap:8px;}
.rail-subitem-bullet .bullet-dot{color:var(--dim);flex:0 0 auto;}
/* The player-only 840px block that used to live here is GONE (2026-07-29).
   It made the player rail position:sticky and in-flow above 840px while the
   coach rail stayed an overlay -- two systems for one job. The thin rail docks
   for everybody above 600px through body.pb3-nav-docked, so players get the
   same treatment as coaches and there is nothing left to fight. The pin button
   it also hid no longer exists. */


/* ============================================================================
   M3 ELEVATION, taken verbatim from m3.material.io's own tokens on 2026-07-29
   rather than hand-rolled -- Dave: "our shadow needs their elevation whatever
   it is." These are the values the page actually computes:

     --mio-theme-elevation-1  0px 1px 2px 0px rgb(0 0 0 / 30%),
                              0px 1px 3px 1px rgb(0 0 0 / 15%)
     --mio-theme-elevation-2  0px 1px 2px 0px rgb(0 0 0 / 30%),
                              0px 2px 6px 2px rgb(0 0 0 / 15%)

   Two things worth knowing before adjusting them:

   * They are IDENTICAL in light and dark. Google does not strengthen shadows
     for dark mode -- it raises the surface colour instead, which is why the
     hover lift here also steps the background up. Both halves are the effect.
   * They are DOWNWARD shadows with a spread (the second layer carries 1-2px),
     which is what makes them read on the side edges too. That spread is doing
     the work my hand-rolled 6px-to-the-right version was trying to do by
     brute force, and it is why theirs looks settled and mine looked pushed.
   ========================================================================== */
:root{
  --pb-elevation-1: 0px 1px 2px 0px rgb(0 0 0 / 30%), 0px 1px 3px 1px rgb(0 0 0 / 15%);
  --pb-elevation-2: 0px 1px 2px 0px rgb(0 0 0 / 30%), 0px 2px 6px 2px rgb(0 0 0 / 15%);

  /* ==========================================================================
     RAIL STATE LAYERS, read off m3.material.io's own drawer on 2026-08-11
     rather than invented. Dave: "hover and selected have the same color."
     They did, and these are the three values that fix it.

     Their rules, verbatim from the stylesheet:

       .focusable-topic-item:hover  background: on-surface-variant @ 8%
       .focusable-topic-item:active background: on-surface-variant @ 12%
       .article-item.active         secondary-container (#dcdaf5) + on-secondary-container text

     So the first two are WASHES OF THE TEXT COLOUR -- the same neutral, at two
     strengths -- and the third is a genuinely different COLOUR. That is the
     distinction that was missing: hover and selected were both
     surface-container-high, one flat grey doing two jobs, so the page you are
     on looked identical to the link under the cursor.

     Washes rather than fixed greys because a wash composites over whatever it
     sits on -- the same rule reads correctly on the rail, on a hovered week
     heading, and in either theme, which three hand-picked greys would not. */
  --rail-hover:  color-mix(in srgb, var(--on-background) 8%, transparent);
  --rail-press:  color-mix(in srgb, var(--on-background) 12%, transparent);
  /* The selected pill. M3's secondary-container is a low-chroma tint of the
     brand, not a grey -- which is exactly why their selected item reads as a
     different KIND of thing rather than a stronger hover. 20% of our primary
     over the rail's own surface is the same idea in this app's palette, and it
     stays distinct from the 8/12% neutral washes in both themes. */
  --rail-selected: color-mix(in srgb, var(--primary) 20%, var(--surface-container));
}

/* ============================================================================
   THIN RAIL + SECONDARY (2026-07-29 redesign)
   ----------------------------------------------------------------------------
   MD3 navigation rail: a narrow column of destinations, each opening a
   secondary pane rather than swapping the page under you. Metrics follow the
   spec -- 80dp rail, 56dp target per destination, icon over a short label.

   The rules that matter more than the styling:

   * The rail is position:fixed ALWAYS. Docking insets the page with
     body.pb3-nav-docked, which no canvas page ever gets (see NO_DOCK in
     nav.js). Nothing here may make the rail participate in a canvas page's
     layout -- that reflows hand-tuned math and is the reason the floating
     rail exists at all.
   * Nothing docks below 600px. On a 390px phone a 76px rail is a wall, and
     MD3 puts the rail at medium widths and up.
   ========================================================================== */
/* flex-direction:row, and it must be stated: the original .nav-rail rule above
   sets column (the old rail stacked header over items). Without this the
   secondary pane renders BELOW the thin rail instead of beside it -- which is
   exactly what the first screenshot showed. */
.nav-rail{
  display:flex; flex-direction:row; align-items:stretch;
  /* overflow:visible, and it is the whole reason the hover shadow appeared on
     the WRONG SIDE. The original .nav-rail carries overflow-y:auto for a long
     item list, and any non-visible overflow CLIPS a child's box-shadow -- so
     the pane's right-cast shadow was being cut off exactly at the rail's right
     edge, which is exactly where it was supposed to show. Scrolling moves to
     the two children, which is where the long content actually lives. */
  overflow:visible;
}
.rail-thin{ overflow-y:auto; }
/* The thin rail paints ABOVE the pane, so the leftward half of the pane's blur
   (a 16px blur against a 6px offset still reaches 10px to the left) is hidden
   behind it instead of reading as depth on the wrong edge. The pane is a later
   sibling, so without this it paints over the rail and the bleed shows. */
.rail-thin{ position:relative; z-index:1; }
.rail-thin{
  width:80px; flex:0 0 80px; height:100%;
  display:flex; flex-direction:column; align-items:stretch;
  background:var(--surface-container); border-right:1px solid var(--outline-variant);
  padding:10px 0 12px;
}
.rail-thin-brand{
  font-family:'Archivo Black','Roboto Condensed',sans-serif; font-size:17px; line-height:1;
  color:var(--on-background); text-decoration:none; text-align:center;
  padding:6px 0 14px; letter-spacing:-0.02em;
}
.rail-thin-brand span{ color:var(--primary); }
.rail-thin-items{ flex:1; display:flex; flex-direction:column; gap:2px; }
.rail-thin-bottom{
  display:flex; flex-direction:column; gap:2px;
  padding-top:10px; margin-top:6px; border-top:1px solid var(--outline-variant);
}
/* The theme toggle, at the rail's foot -- the m3.material.io treatment,
   measured off their site 2026-08-16 (Dave: "use the same animation and
   treatment"): a 48px circular window over a vertical track carrying BOTH
   glyphs; switching slides the track with the standard easing so the other
   icon rolls into view. The track position is driven purely by the html
   data-theme attribute, so the animation needs no JS of its own. */
.rail-theme-toggle{
  width:48px; height:48px; border-radius:50%; overflow:hidden;
  border:none; background:none; cursor:pointer; padding:0;
  margin:8px auto 2px; display:block; position:relative;
  color:var(--on-background);
}
.rail-theme-toggle:hover{ background:var(--surface-container-high); }
.rail-theme-track{
  display:flex; flex-direction:column;
  transition:transform .3s cubic-bezier(0.2, 0, 0, 1);
}
html[data-theme="light"] .rail-theme-track{ transform:translateY(-48px); }
.rail-theme-cell{ width:48px; height:48px; display:flex; align-items:center; justify-content:center; flex:0 0 auto; }
.rail-theme-cell .material-symbols-outlined{ font-size:24px; }
.rail-thin-item{
  appearance:none; background:none; border:0; cursor:pointer; text-decoration:none;
  display:flex; flex-direction:column; align-items:center; justify-content:center; gap:4px;
  min-height:56px; padding:6px 4px; color:var(--dim);
  font-family:'Roboto Condensed',sans-serif; font-size:11px; font-weight:600;
  letter-spacing:0.02em;
  transition:color .15s ease;
}

/* ==========================================================================
   DESTINATION STATES -- the mechanics measured off m3.material.io on
   2026-07-29, in PB3000-NAV-RAIL-M3-SPEC-2026-07-29.md. Their colours are
   deliberately NOT copied: --primary is the team's own colour and the app is
   themed from it, so Google's purple would sit as a foreign accent next to a
   school's branding. Same behaviour, our palette.

   Everything happens on the icon and the label. The button itself never gets a
   background -- that was the giveaway that ours looked flat next to theirs.
   ========================================================================== */
.rail-thin-item .material-symbols-outlined{
  position:relative; z-index:0;
  display:flex; align-items:center; justify-content:center;
  width:56px; height:32px; border-radius:16px;
  font-size:23px;
  font-variation-settings:'FILL' 0, 'wght' 300, 'opsz' 24;
  transition:background-color .15s ease, font-variation-settings .15s ease, color .15s ease;
}
/* The indicator pill is ALWAYS present at scaleX(0.32) and grows to full width
   over 0.2s. It does not appear -- it expands from the middle, which is the
   single most recognisable thing about an M3 rail. */
.rail-thin-item .material-symbols-outlined::before{
  content:''; position:absolute; inset:0; z-index:-1;
  border-radius:100px;
  background:color-mix(in srgb, var(--primary) 20%, transparent);
  opacity:0; transform:scaleX(0.32);
  transition:transform .2s linear, opacity .2s linear;
}
/* State layer behind the ICON only, at M3's 8% / 12% of the foreground. As a
   percentage of the text colour it works in both themes with one rule. */
.rail-thin-item:hover .material-symbols-outlined{
  background:color-mix(in srgb, currentColor 8%, transparent);
  font-variation-settings:'FILL' 0, 'wght' 600, 'opsz' 24;
}
.rail-thin-item:active .material-symbols-outlined{
  background:color-mix(in srgb, currentColor 12%, transparent);
  /* Lighter on press, not heavier -- this is what makes a click feel pressed. */
  font-variation-settings:'FILL' 0, 'wght' 300, 'opsz' 24;
}
.rail-thin-item:hover{ color:var(--on-background); }
/* GRAD, not font-weight: optical grade darkens the text without changing letter
   widths, so nothing shifts as you move along the rail. */
.rail-thin-item:hover .rail-thin-label{ font-variation-settings:'GRAD' 50; }
.rail-thin-item:active .rail-thin-label{ font-variation-settings:'GRAD' -50; }

.rail-thin-item.active{ color:var(--primary); }
.rail-thin-item.active .material-symbols-outlined{
  color:var(--primary);
  font-variation-settings:'FILL' 1, 'wght' 400, 'opsz' 24;   /* selected FILLS */
}
.rail-thin-item.active .material-symbols-outlined::before{ opacity:1; transform:scaleX(1); }
.rail-thin-item.active .rail-thin-label{ font-variation-settings:'GRAD' 125; }
.rail-thin-item.active:hover .material-symbols-outlined{
  font-variation-settings:'FILL' 1, 'wght' 600, 'opsz' 24;
}

/* Its own state, not a browser outline: a ring that reads on any background. */
.rail-thin-item:focus-visible{
  outline:0; border-radius:18px;
  box-shadow:0 0 0 2px var(--on-background), inset 0 0 0 2px var(--surface-container);
}

/* An open section is held down, distinct from hover and from selected. */
.rail-thin-item[aria-expanded="true"]{ color:var(--on-background); }
.rail-thin-item[aria-expanded="true"] .material-symbols-outlined{
  background:color-mix(in srgb, currentColor 8%, transparent);
}

/* ROLLS OUT, rather than appearing -- Dave, 2026-07-29, comparing against
   m3.material.io. display:none cannot animate, so the pane is always in the
   DOM and animates its WIDTH from 0. 0.25s on M3's standard easing curve
   (0.2, 0, 0, 1): quick out of the gate, settling at the end.

   visibility is switched with a delayed transition rather than being animated:
   instantly visible on the way out, and only hidden once the roll-back has
   finished. That keeps the links out of the tab order while closed without
   making them vanish mid-animation. */
.rail-secondary{
  width:0; flex:0 0 auto; height:100%; overflow:hidden;
  visibility:hidden;
  background:var(--surface-container); border-right:1px solid var(--outline-variant);
  transition:width .25s cubic-bezier(0.2, 0, 0, 1),
             visibility 0s linear .25s,
             box-shadow .18s ease;
}
/* 260 HERE MUST MATCH .rail-secondary-inner AND .nav-rail.secondary-open,
   which is 80 (the icon rail) + this. Three numbers, one measurement -- change
   one alone and the rail either widens around a pane that did not, or clips a
   pane wider than itself. Widened from 220 on 2026-08-11 so a heading like
   "South Glens Falls, Sept 18" (172px of label) fits on one line. */
.rail-secondary.open{
  width:260px; visibility:visible; overflow-y:auto;
  transition:width .25s cubic-bezier(0.2, 0, 0, 1),
             visibility 0s linear 0s,
             box-shadow .18s ease;
}
/* Fixed width so the contents do not reflow on every frame of the roll. */
/* Matches .rail-secondary.open exactly -- see the note there. */
.rail-secondary-inner{ width:260px; box-sizing:border-box; padding:14px 10px; }
/* Depth on hover, the way m3.material.io lifts its secondary pane -- Dave,
   2026-07-29. The pane is a surface floating over the page, and the shadow is
   what says so; flat, it reads as part of the rail. --elevation-hover is the
   app's own token so it stays right in both themes (it is a darker, heavier
   shadow in dark mode than a naive rgba would give). focus-within is included
   so the lift also happens for a keyboard user tabbing into the pane, who
   would otherwise get no feedback at all. */
/* (The standalone box-shadow transition that used to be here is folded into
   the .rail-secondary rule above. As a shorthand it came LATER in the file and
   silently replaced the whole transition list, so the roll-out animated on the
   way open -- where a forced reflow masked it -- and snapped shut on the way
   back. Two rules setting `transition` on one selector is all it takes.) */
/* Cast to the RIGHT, not downward -- Dave, 2026-07-29. --elevation-hover is a
   0 2px 6px shadow meant for a card sitting on the page; used here it lit the
   pane's bottom edge, which is not the edge doing the floating. The pane is a
   vertical surface whose free edge faces the PAGE to its right, so that is
   where the depth belongs. Same direction and house style as .nav-rail's own
   overlay shadow (2px 0 8px / 4px 0 20px), a step stronger because this pane
   sits above the rail as well as the page. */
/* ELEVATION ON THE WHOLE ASSEMBLY, not just the pane -- Dave, 2026-07-29:
   "the hover elevation that the entire primary and secondary rail have when
   the mouse is over it." Hovering anywhere lifts rail and pane together,
   because they are one surface as far as the eye is concerned.

   SHADOW ONLY. This used to raise the background to surface-container-high as
   well, on the reasoning that M3 lifts a dark surface by lightening it. Two
   things were wrong with that here. It made the docked rail visibly darker
   than the overlay rail on Multi Mode -- the same component, two colours,
   depending on the page. And surface-container-high is exactly what
   .rail-sub:hover uses, so once the whole pane was that colour the per-link
   hover had nothing left to say: on Multi Mode the links responded to the
   pointer, on the docked pages they looked dead. Both of Dave's complaints,
   one cause.

   The elevation alone carries the lift, which is what he asked for: "the
   elevation animation is enough for when the mouse is over the rail, we do
   not need to darken the color too." */
.nav-rail.docked:hover,
.nav-rail.docked:focus-within{ box-shadow:var(--pb-elevation-2); }
/* CURVED EDGES. The rail is a surface floating over the page, and a surface
   with square corners reads as a page region instead. Only the edges that face
   the page are rounded; the ones against the viewport stay flush. */
/* The radius and the border must live on the SAME element, or you get a curve
   on one and a straight edge on the other running past it -- which is exactly
   what Dave photographed. The nav-rail owns both, and its children give up
   their own right-hand borders so nothing draws through the corner. */
.nav-rail.docked{
  border-top-right-radius:16px; border-bottom-right-radius:16px;
  border-right:1px solid var(--outline-variant);
}
.nav-rail.docked .rail-thin,
.nav-rail.docked .rail-secondary{ border-right:0; }
/* THE CHILDREN MUST CARRY THE RADIUS, not just the rail. The rail is
   overflow:visible -- it has to be, or it clips its own hover shadow -- so a
   radius on the parent alone is painted straight over by a square child. The
   rail has had 16px set on it since the corners went in and it has looked 90°
   the entire time, which is what Dave spotted: an elevation shaped for a
   rounded corner sitting on a sharp one.

   Whichever child is rightmost is the one that shows: the thin rail on its
   own, or the pane once it is open. */
.nav-rail.docked .rail-thin{
  border-top-right-radius:16px; border-bottom-right-radius:16px;
}
.nav-rail.docked.secondary-open .rail-thin{
  border-top-right-radius:0; border-bottom-right-radius:0;
}
.nav-rail.docked .rail-secondary{
  border-top-right-radius:16px; border-bottom-right-radius:16px;
}
/* A LIGHT DIVIDER between the two rails once the pane is out -- m3.material.io
   keeps one and Dave spotted its absence. I had removed it so the pane would
   read as a separate lifted surface, but on their rail the line and the
   elevation do different jobs: the line says these are two panes, the shadow
   says the pair floats above the page.

   Only while the pane is open. Closed, the thin rail is the outer edge and
   .nav-rail's own border draws it -- keeping both would stack two 1px lines
   into a 2px one. */
.nav-rail.docked.secondary-open .rail-thin{
  border-right:1px solid var(--outline-variant);
}
.rail-secondary-title{
  font-family:'Roboto Condensed',sans-serif; font-size:11px; font-weight:700;
  letter-spacing:0.08em; text-transform:uppercase; color:var(--dim);
  padding:2px 10px 10px;
}
/* Re-measured against m3.material.io's live drawer, 2026-08-11: 14px, weight
   500, no letter-spacing, no uppercase, a 44px row and a 16px pill. Their
   resting label is rgb(77,66,86) -- MUTED, not full-strength -- and the
   selected one darkens to rgb(33,24,43) on a neutral fill.

   Three things here were heavier than the model and it showed: 13.5px text,
   and a selected state that went both bold (700) AND brand-coloured AND
   filled. M3 changes exactly two things between resting and selected -- the
   label darkens and a neutral pill appears -- and never touches the weight,
   because reflowing the text on selection is what makes a menu feel like it
   is shouting. */
/* 80%, NOT var(--dim). --dim is 0.55 alpha, which measures 3.69:1 against the
   rail's own surface in the light theme -- under the 4.5:1 floor, on a menu a
   coach reads outdoors in daylight. "Muted" was right, that particular muted
   was not: m3.material.io's resting label measures 8.52:1, nowhere near as
   faint as it looks next to their selected one. 80% lands at ~8.3:1 light and
   ~8.6:1 dark, so it matches the model and clears AA in both themes. */
.rail-sub{
  display:flex; align-items:center; min-height:44px;
  padding:9px 16px; border-radius:16px; text-decoration:none;
  color:color-mix(in srgb, var(--on-background) 80%, transparent);
  font-size:14px; font-weight:500; line-height:1.25;
}
.rail-sub:hover{ background:var(--rail-hover); color:var(--on-background); }
.rail-sub:active{ background:var(--rail-press); }
/* THE PAGE YOU ARE ON. A tinted container rather than a stronger grey, so it
   differs from hover in KIND and not merely in amount -- M3 uses
   secondary-container here for the same reason. Weight stays 500: reflowing
   the text on selection is what made the old menu feel like it was shouting,
   and the colour carries it on its own. */
.rail-sub.active{ color:var(--on-background); font-weight:500;
  background:var(--rail-selected); }
/* Hovering the page you are already on must not look like leaving it. */
.rail-sub.active:hover{ background:var(--rail-selected); }
.rail-sub-group{
  font-family:'Roboto Condensed',sans-serif; font-size:10px; font-weight:700;
  letter-spacing:0.08em; text-transform:uppercase; color:var(--dim);
  padding:12px 12px 4px;
}

/* ===========================================================================
   GAME WEEK -- one expanding heading per opponent (2026-08-11).

   The shape is m3.material.io's expanding drawer item, which Dave named as
   the model, MEASURED off their live drawer rather than described: a 44px
   row, a 16px pill radius, a 14px medium-weight label, a trailing chevron,
   and children indented 10px. The colours are ours -- their lavender is their
   brand, and the app already has an "this one is chosen" treatment in
   .rail-sub.active that these must not disagree with.
   =========================================================================== */
.rail-week{ margin-bottom:2px; }
/* THE OPPONENT IS THE HEADING, so it now reads like one -- full strength and
   600 rather than the same 80%/500 as the four links beneath it. Dave,
   2026-08-12: "can we make the sub menu text a touch differentiated."

   Two levels of one menu were rendering identically apart from a 10px indent,
   which is not enough to say "these four belong to that one" -- so the fix is a
   pair: the parent gets stronger here, the children get quieter below. Doing
   only one of the two would have moved the whole menu's weight rather than
   separating its levels. */
.rail-week-head{
  display:flex; align-items:center; gap:6px; width:100%; min-height:44px;
  padding:9px 8px 9px 16px; border:0; background:none; cursor:pointer;
  border-radius:16px; text-align:left; font-family:inherit;
  color:var(--on-background);
  font-size:14px; font-weight:600; line-height:1.25;
}
.rail-week-head:hover{ background:var(--rail-hover); color:var(--on-background); }
.rail-week-head:active{ background:var(--rail-press); }
.rail-week-title{ flex:1 1 auto; }
/* Rotates rather than swapping the glyph. One element, no second font
   request, and the turn is what reads as "this opened" on a slow connection
   where a swapped icon would pop. */
.rail-week-chev{
  flex:0 0 auto; font-size:20px;
  transition:transform .22s cubic-bezier(0.2, 0, 0, 1);
}
/* AN OPEN WEEK IS NOT A SELECTED ONE. It used to take the brand colour AND a
   12% brand fill the moment it expanded, so the loudest thing in the rail was
   a heading, competing with the destination underneath it.

   But it is not nothing either, which is the correction from Dave's second
   screenshot. m3.material.io gives an expanded parent the SAME 8% neutral wash
   as hover -- their rule is literally

     .child-article-drawer:not(:has(.collapsed)) .collapsible-topic-container
       { background: THEIR on-surface-variant token at 8 percent }

   Their token name is deliberately NOT written here in var() form. The suite
   greps this file for the custom properties it consumes and checks each one is
   defined in both themes -- a comment quoting a foreign token reads as a
   dependency on it, and the check went red naming a Google variable this app
   has never had. Describe another system's tokens; do not spell them.

   NOTE TO WHOEVER EDITS THIS COMMENT: do not write a nested comment in here.
   CSS comments DO NOT NEST -- an inner close sequence ends the outer one at
   that point, everything after it is parsed as CSS, and the parser throws away
   the next rule while it recovers. That is not hypothetical: quoting M3's rule
   with a trailing percentage comment silently deleted the open-week wash below
   for a day. It looked like the rule was not working; it was never there.

   -- which is the faint pill under "Content design" in that shot. So an open
   week gets the quiet wash, the page you are on gets the tinted pill, and the
   two can no longer be mistaken for each other. The label colour still does
   not change; the chevron and this wash carry "open" between them. */
/* TWO TRIGGERS, because m3.material.io has two and they are not the same
   question. Dave, 2026-08-11: "the MD3 rail keeps the secondary menu item in
   hover state when a sub item is active."

     .rail-week.open                    -- this week is expanded
     .rail-week:has(.rail-sub.active)   -- the page you are on lives in it

   Their rules, verbatim:
     :has(.focusable-topic-item.active) .collapsible-topic-container { 8% }
     .child-article-drawer:not(:has(.collapsed)) .collapsible-topic-container { 8% }

   The second condition is the one that was missing, and it matters where the
   two disagree: COLLAPSE the week you are currently working in and the heading
   still carries the wash, so the rail keeps telling you which opponent you are
   inside even with its links hidden. Without it, collapsing loses your place.

   `:has()` is safe here -- it is in every browser this app supports, and if it
   ever were not, the open rule alone still covers the common case rather than
   leaving the menu broken. */
.rail-week.open > .rail-week-head,
.rail-week:has(.rail-sub.active) > .rail-week-head{ background:var(--rail-hover); }
.rail-week.open > .rail-week-head .rail-week-chev{ transform:rotate(180deg); }

/* THE COLLAPSE, without measuring anything. grid-template-rows animating
   0fr -> 1fr lets the browser work out the open height itself, so a week with
   two destinations and one with three both animate correctly and neither
   needs a magic max-height that would clip the day a fourth is added.

   The inner wrapper is REQUIRED, not tidiness: grid-template-rows sizes the
   first row only, and three sibling links would create three implicit rows
   that the transition never touches.

   visibility does what m3.material.io does with tabindex="-1" -- a closed
   week's links leave the tab order -- in one rule that cannot fall out of
   step with the open class. Delayed on the way closed so they do not vanish
   mid-animation, instant on the way open. */
.rail-week-body{
  display:grid; grid-template-rows:0fr; visibility:hidden;
  margin-left:10px;
  transition:grid-template-rows .22s cubic-bezier(0.2, 0, 0, 1),
             visibility 0s linear .22s;
}
.rail-week-body-inner{ overflow:hidden; min-height:0; }
/* THE FOUR DESTINATIONS, quieter than the opponent above them.
   ---------------------------------------------------------------------------
   68% rather than the 80% every other link in this pane uses, and a hair
   smaller. MEASURED against the rail surface in both themes rather than
   estimated -- 6.53:1 dark and 5.52:1 light, against AA's 4.5:1. (The first
   pass guessed "around 7:1" for both; light is the tighter of the two and is
   the number that would go first if anyone dims this further.) The opponent
   above it sits at 12.57:1 dark and 14.73:1 light.

   It buys the one thing indentation alone could not: these read as belonging
   to the heading rather than sitting beside it.

   NOT TINTED TOWARD --primary, which would have been the obvious way to
   "differentiate" and is already spoken for -- primary is how this rail says
   "the page you are on", and a whole submenu wearing it would make every week
   look half-selected.

   :not(.active) so the current page keeps its full-strength treatment. Written
   as an exclusion rather than relying on source order, because .rail-sub.active
   carries the same specificity and would otherwise win or lose on position. */
.rail-week-body .rail-sub:not(.active){
  color:color-mix(in srgb, var(--on-background) 68%, transparent);
  font-size:13.5px;
}
.rail-week.open > .rail-week-body{
  grid-template-rows:1fr; visibility:visible;
  transition:grid-template-rows .22s cubic-bezier(0.2, 0, 0, 1),
             visibility 0s linear 0s;
}
/* The way out of an empty menu, for the coach who can act on it. */
.rail-week-setup{ color:var(--primary); font-weight:700; margin-top:6px; }

/* Width is driven by what is open, so the closed rail costs 80px and not 300. */
.nav-rail{ width:80px; transition:width .25s cubic-bezier(0.2, 0, 0, 1); }
/* 340, not 300, and the 40px is measured rather than eyeballed. The icon rail
   takes 80 of this, so at 300 the secondary pane was 220 and a heading's label
   slot only 146 -- "South Glens Falls, Sept 18" needs 172 and wrapped onto a
   second line, growing that one row to 53px while its neighbours sat at 44.
   340 leaves a 186px slot, which clears the longest name on Dave's real
   schedule with headroom.

   Wrapping is still ALLOWED rather than clipped or ellipsised, because school
   names have no upper bound -- a Burnt Hills-Ballston Lake will exceed any
   width worth giving a navigation rail. A wrapped heading grows its row and
   keeps its chevron centred, which stays readable; a truncated opponent does
   not. This buys the common case, it does not pretend to solve every case. */
.nav-rail.secondary-open{ width:340px; }

/* DOCKED. The page is inset to clear the rail; the rail itself stays fixed.
   body.pb3-nav-docked is applied by nav.js and never on a canvas page. */
body.pb3-nav-docked{ padding-left:80px; box-sizing:border-box; }
/* The header spans the viewport with the classic width:100vw + negative-margin
   trick. Inside a padded body that is 100vw NEXT TO an 80px inset, so the page
   scrolls sideways -- the same failure mode as the July 2026 "settings spilling
   off the page" bug, arrived at from the other direction. Letting the header be
   an ordinary block inside the inset content box is both the fix and simpler
   than the trick it replaces. Measured, not assumed: 0 horizontal overflow at
   390, 820 and 1100. */
/* The header override that used to live here is GONE. It existed to re-do the
   hero's width:100vw breakout against the docked rail's inset; the hero no
   longer breaks out at all, so there is nothing to compensate for. Two CSS
   hacks cancelled each other out and both could be deleted. */
.nav-rail.docked{ transform:none; box-shadow:none; }
.nav-rail.docked ~ .rail-backdrop{ display:none; }

@media (max-width: 599px){
  /* Belt and braces: even if a page asked to dock, it cannot below 600px. */
  body.pb3-nav-docked{ padding-left:0; }
}
