/* ==========================================================================
   Rocket Dugout Club — site.css
   Shared page furniture for all nine pages.

   Pairs with sandlot-nights.css, which is the LOCKED brand system. This
   file defines LAYOUT ONLY. Every colour here must reference a
   sandlot-nights variable — a literal hex or rgb() in this file is a bug,
   and check.py fails the build on one.
   ========================================================================== */

body{ padding:0; margin:0; }
.wrap{ max-width:1100px; margin:0 auto; padding:0 24px; }
.center{ text-align:center; }
.stack-cta{ margin-top:28px; text-align:center; }

.skip-link{ position:absolute; left:-9999px; }
.skip-link:focus{
  left:12px; top:12px; z-index:50; background:var(--ignite);
  color:var(--btn-dark-text); padding:10px 16px; border-radius:var(--radius-sm);
}

/* --------------------------------------------------------------------------
   Header
   -------------------------------------------------------------------------- */
.site-header{
  background:var(--chalk);
  border-bottom:3px solid var(--maroon);
  position:sticky; top:0; z-index:10;
}
.nav{
  display:flex; align-items:center; justify-content:space-between;
  flex-wrap:wrap; gap:16px;
  padding:16px 24px; max-width:1100px; margin:0 auto;
  position:relative;
}
.logo{
  font-family:var(--font-display); font-size:24px; color:var(--dirt);
  display:flex; align-items:center; gap:9px; white-space:nowrap;
  text-decoration:none;
}
.logo .icon{ color:var(--ignite); width:24px; height:24px; }

/* --------------------------------------------------------------------------
   Navigation — CSS-only mobile menu, no JavaScript
   -------------------------------------------------------------------------- */
.nav-toggle{ position:absolute; opacity:0; width:1px; height:1px; }
.nav-toggle-label{
  display:flex; flex-direction:column; gap:5px; cursor:pointer;
  padding:10px; margin:-10px; border-radius:var(--radius-sm);
}
.nav-toggle-label span{
  display:block; width:24px; height:2px; background:var(--dirt);
  transition:transform .2s ease, opacity .2s ease;
}
.nav-toggle:focus-visible + .nav-toggle-label{
  outline:2px solid var(--ignite); outline-offset:2px;
}
.nav-toggle:checked + .nav-toggle-label span:nth-child(1){ transform:translateY(7px) rotate(45deg); }
.nav-toggle:checked + .nav-toggle-label span:nth-child(2){ opacity:0; }
.nav-toggle:checked + .nav-toggle-label span:nth-child(3){ transform:translateY(-7px) rotate(-45deg); }

.navlinks{
  display:none; flex-direction:column;
  width:100%; order:3; padding:8px 0 4px;
  border-top:1px solid var(--gray-light); margin-top:14px;
}
.nav-toggle:checked ~ .navlinks{ display:flex; }
.navlinks a{
  padding:13px 2px; border-bottom:1px solid var(--gray-light);
  color:var(--dirt); text-decoration:none;
  font-weight:700; font-size:13px; text-transform:uppercase; letter-spacing:.05em;
}
.navlinks a:last-child{ border-bottom:none; }
.navlinks a[aria-current="page"]{ color:var(--ignite); }
.navlinks .nav-tools{ color:var(--gray); }

@media(min-width:1100px){
  .nav-toggle-label{ display:none; }
  /* Site name at the band's left edge, menu centred on the band.
     Three columns, 1fr auto 1fr: the empty third balances the first, so
     the middle lands on the band's centre line whatever the name's width.
     That costs width -- true centring needs the empty column to MATCH the
     name, so the band has to hold 2x name + menu -- which is why the band
     runs wider than the 1100px content wrap below it, and why the columns
     are minmax(0,1fr) rather than 1fr: on a screen too narrow to centre
     exactly, the side columns compress and the menu drifts right instead
     of colliding with the name. It never stacks and never overlaps.
     Below 1100px the hamburger takes over, which reads better than a
     cramped row.
     Only .logo and .navlinks are in grid flow here -- .nav-toggle is
     position:absolute and .nav-toggle-label is display:none. */
  .nav{
    display:grid;
    /* 1fr, NOT minmax(0,1fr): a zero floor lets the first column shrink
       under the name, which then overflows it and the menu prints on top
       of "ROCKET DUGOUT CLUB". 1fr's default min is min-content, so the
       name always gets its width and the menu is pushed right of centre
       instead of into it. */
    grid-template-columns:1fr auto 1fr;
    align-items:center; max-width:1700px; flex-wrap:nowrap;
  }
  .logo{ grid-column:1; justify-self:start; }
  .navlinks{
    grid-column:2; justify-self:center;
    display:flex !important; flex-direction:row; width:auto; order:0;
    /* Trimmed from 20/12.5 so the menu clears the name at ordinary laptop
       widths; centring an eleven-item menu needs the band to hold twice
       the name's width plus the menu, which is a lot of pixels. */
    /* wrap, not nowrap: a nowrap row has a min-content width it cannot go
       under, so on a screen too narrow for the whole menu the grid column
       refuses to shrink and the last items run off the right edge of the
       page. Wrapping costs a second line at those widths and keeps one
       clean row everywhere there is room for it. */
    gap:14px; border-top:none; margin-top:0; padding:0; align-items:center;
    flex-wrap:wrap; justify-content:center;
  }
  .navlinks a{
    padding:4px 0; border-bottom:2px solid transparent; font-size:11.5px;
    /* One item, one line. "LINEUP CARD" breaking mid-item made the band
       two lines tall and ragged. */
    white-space:nowrap;
  }
  .navlinks a:hover{ border-bottom-color:var(--ignite); }
  .navlinks a[aria-current="page"]{ border-bottom-color:var(--ignite); }
  /* Tools links sit apart from the public pages */
  .navlinks .nav-tools:first-of-type{
    margin-left:8px; padding-left:20px; border-left:1px solid var(--gray-light);
  }
  .navlinks .nav-tools{ font-size:11.5px; }
  .navlinks .nav-tools:hover{ color:var(--dirt); }
}

/* --------------------------------------------------------------------------
   Hero (home page) and page head (interior pages)
   -------------------------------------------------------------------------- */
.hero{
  background:var(--maroon);
  background-image:linear-gradient(180deg, var(--maroon) 0%, var(--maroon-dark) 100%);
  padding:64px 24px 52px;
  text-align:center;
  position:relative; overflow:hidden;
}
.hero-stitch{
  position:absolute; opacity:0.12; pointer-events:none;
  color:var(--outline-stroke);
}
.hero-stitch.left{ top:10px; left:-40px; width:180px; height:180px; transform:rotate(-15deg); }
.hero-stitch.right{ bottom:-30px; right:-40px; width:220px; height:220px; transform:rotate(18deg); }
.hero .eyebrow{
  margin-bottom:14px;
  color:color-mix(in srgb, var(--dirt) 78%, var(--maroon));
}
.hero h1{ font-size:clamp(34px,6vw,58px); max-width:760px; margin:0 auto 16px; }
.hero p{ max-width:540px; margin:0 auto 28px; font-size:16px; color:var(--dirt); }
.hero-ctas{ display:flex; gap:14px; justify-content:center; flex-wrap:wrap; }

.page-head{
  background:var(--maroon);
  background-image:linear-gradient(180deg, var(--maroon) 0%, var(--maroon-dark) 100%);
  padding:44px 24px 38px; text-align:center;
}
.page-head .eyebrow{ color:color-mix(in srgb, var(--dirt) 78%, var(--maroon)); }
.page-head h1{ font-size:clamp(30px,5vw,44px); margin:8px 0 0; }
.page-head p{ max-width:600px; margin:14px auto 0; color:var(--dirt); font-size:15px; }

/* --------------------------------------------------------------------------
   Section shell
   -------------------------------------------------------------------------- */
section{ padding:60px 24px; }
.section-head{ text-align:center; max-width:640px; margin:0 auto 40px; }
.section-head h2{ font-size:32px; margin-top:8px; }
.section-head p{ color:var(--gray); font-size:14.5px; margin-top:10px; }

/* --------------------------------------------------------------------------
   Notice — honest empty states and season banners
   -------------------------------------------------------------------------- */
.notice{
  max-width:1100px; margin:0 auto 34px;
  background:var(--chalk-card); border:1px solid var(--gray-light);
  border-left:4px solid var(--ignite);
  border-radius:var(--radius-md); padding:18px 22px;
}
.notice p{ font-size:14.5px; color:var(--dirt); margin:0; }
.notice strong{
  font-family:var(--font-mono); color:var(--ignite);
  text-transform:uppercase; font-size:12px; letter-spacing:.06em;
  display:block; margin-bottom:6px;
}
.notice a{ color:var(--ignite); }

/* --------------------------------------------------------------------------
   Grids
   -------------------------------------------------------------------------- */
.grid-2, .grid-3{
  display:grid; grid-template-columns:1fr; gap:24px;
  max-width:1100px; margin:0 auto;
}
@media(min-width:700px){
  .grid-2{ grid-template-columns:repeat(2,1fr); }
  .grid-3{ grid-template-columns:repeat(2,1fr); }
}
@media(min-width:1000px){ .grid-3{ grid-template-columns:repeat(3,1fr); } }

/* --------------------------------------------------------------------------
   Feature cards (home page)
   -------------------------------------------------------------------------- */
.features{ display:grid; grid-template-columns:1fr; gap:28px; max-width:1100px; margin:0 auto; }
@media(min-width:820px){ .features{ grid-template-columns:1fr 1fr; } }
.feature-card{
  background:var(--chalk-card); border:1px solid var(--gray-light);
  border-top:4px solid var(--maroon);
  border-radius:var(--radius-md); overflow:hidden;
  display:flex; flex-direction:column;
}
.feature-photo{
  width:100%; aspect-ratio:2/3; object-fit:cover; display:block;
  background:var(--gray-light);
}
.feature-photo.placeholder-photo{ aspect-ratio:4/3; }

/* A real supplied image, as opposed to the placeholder above. The two the
   club provided are POSTERS, not photographs: their text runs right to the
   edges, so `object-fit:cover` cropped words off both sides -- "$30" became
   "$3", "LIMITED AMOUNTS" became "MITED OUNTS", and the golf flyer lost its
   entire left column. Cover is correct for a photo, where trimming an edge
   costs nothing, and wrong for anything carrying text. Show these whole and
   let the height follow the image's own proportions. */
img.feature-photo{
  aspect-ratio:auto; height:auto; object-fit:contain;
}
.feature-body{ padding:26px 26px 28px; flex:1; display:flex; flex-direction:column; }
.feature-body .eyebrow{ margin-bottom:8px; }
.feature-body h3{ font-size:26px; margin-bottom:12px; }
.feature-price{
  font-family:var(--font-mono); font-size:28px; color:var(--ignite); margin-bottom:4px;
}
.feature-price span{
  font-size:13px; color:var(--gray); text-transform:uppercase;
  letter-spacing:0.06em; margin-left:6px;
}
.feature-meta{
  font-family:var(--font-mono); font-size:13px; color:var(--dirt);
  margin-bottom:16px; line-height:1.9;
}
.feature-meta b{
  color:var(--gray); font-weight:400; display:inline-block; min-width:112px;
  text-transform:uppercase; font-size:11px; letter-spacing:0.05em;
}
.feature-swatches{ display:flex; gap:10px; margin-bottom:16px; }
.swatch-pill{
  display:flex; align-items:center; gap:7px;
  font-family:var(--font-mono); font-size:12px; color:var(--dirt);
  border:1px solid var(--gray-light); border-radius:var(--radius-pill);
  padding:5px 12px 5px 6px;
}
.swatch-dot{
  width:16px; height:16px; border-radius:50%;
  border:1px solid color-mix(in srgb, var(--dirt) 25%, transparent);
  flex-shrink:0;
}
.feature-list{ list-style:none; font-size:14px; color:var(--dirt); margin-bottom:18px; padding:0; }
.feature-list li{ padding:6px 0 6px 20px; position:relative; border-bottom:1px solid var(--gray-light); }
.feature-list li:last-child{ border-bottom:none; }
.feature-list li::before{ content:"\203A"; position:absolute; left:0; color:var(--ignite); font-weight:700; }
.feature-note{ font-size:13px; color:var(--gray); font-style:italic; margin-bottom:18px; }
.feature-body .btn{ align-self:flex-start; margin-top:auto; }

/* --------------------------------------------------------------------------
   Schedule table — restacks into cards below 640px
   -------------------------------------------------------------------------- */
.table-wrap{ max-width:1100px; margin:0 auto; overflow-x:auto; }
.sched-table caption,
.roster-table caption,
.data-table caption{
  font-family:var(--font-mono); font-size:12px; letter-spacing:.06em;
  text-transform:uppercase; color:var(--gray); text-align:left; padding-bottom:10px;
}
.sched-table td .tag{ margin-left:6px; }
.sched-note td{
  font-family:var(--font-mono); font-size:12.5px; color:var(--ignite);
  text-transform:uppercase; letter-spacing:.06em; text-align:center;
}
.sched-annot{
  font-family:var(--font-mono); font-size:11px; color:var(--ignite);
  text-transform:uppercase; letter-spacing:.06em; display:block; margin-top:3px;
}
.legend{
  max-width:1100px; margin:26px auto 0; display:flex; flex-wrap:wrap;
  gap:20px; align-items:center;
  font-family:var(--font-mono); font-size:12px; color:var(--gray);
}
.legend .bar{
  display:inline-block; width:14px; height:14px; background:var(--ignite);
  border-radius:2px; vertical-align:-2px; margin-right:7px;
}

@media(max-width:640px){
  .sched-table thead{
    position:absolute; width:1px; height:1px; overflow:hidden; clip:rect(0 0 0 0);
  }
  .sched-table tr{
    display:block; margin-bottom:14px;
    border:1px solid var(--gray-light); border-top:3px solid var(--maroon);
    border-radius:var(--radius-md); overflow:hidden;
  }
  .sched-table tr.is-home{ border-top-color:var(--ignite); }
  .sched-table td{
    display:flex; justify-content:space-between; gap:16px;
    padding:9px 14px; border-bottom:1px solid var(--gray-light);
  }
  .sched-table td:last-child{ border-bottom:none; }
  .sched-table td::before{
    content:attr(data-label); font-family:var(--font-mono); font-size:10.5px;
    text-transform:uppercase; letter-spacing:.06em; color:var(--gray);
    flex-shrink:0; align-self:center;
  }
  .sched-table tr.is-home td:first-child{ border-left:none; }
  .sched-table tr.sched-note td{ display:block; }
  .sched-table tr.sched-note td::before{ content:none; }
}

/* --------------------------------------------------------------------------
   Stat tiles — class counts, records
   -------------------------------------------------------------------------- */
.stat-row{
  display:grid; grid-template-columns:repeat(2,1fr); gap:14px;
  max-width:1100px; margin:0 auto 30px;
}
@media(min-width:700px){ .stat-row{ grid-template-columns:repeat(4,1fr); } }
.stat{
  background:var(--chalk-card); border:1px solid var(--gray-light);
  border-top:3px solid var(--maroon); border-radius:var(--radius-md);
  padding:16px 18px; text-align:center;
}
.stat b{
  display:block; font-family:var(--font-mono); font-size:30px;
  color:var(--ignite); font-weight:400; line-height:1.2;
}
.stat span{
  font-family:var(--font-mono); font-size:11px; color:var(--gray);
  text-transform:uppercase; letter-spacing:.07em;
}

/* Roster table — two columns, no restack needed */
.roster-table td:last-child,
.roster-table th:last-child{
  text-align:right; white-space:nowrap;
}
.roster-table td:last-child{
  font-family:var(--font-mono); font-size:13px; color:var(--gray);
}
.roster-table td:first-child{ font-size:15px; }

/* Historical data tables — records, coaching lineage */
.data-table td:first-child,
.data-table th:first-child{ white-space:nowrap; }
.data-table .yr{
  font-family:var(--font-mono); font-size:13px; color:var(--ignite);
}
.data-table .rec{ font-family:var(--font-mono); font-size:13.5px; }
.data-table .muted{ color:var(--gray); font-family:var(--font-mono); font-size:12.5px; }

/* Source citation block */
.sources{
  max-width:1100px; margin:40px auto 0; padding-top:20px;
  border-top:1px solid var(--gray-light);
}
.sources h4{
  font-family:var(--font-mono); font-size:11px; text-transform:uppercase;
  letter-spacing:.07em; color:var(--gray); margin:0 0 10px;
}
.sources p{ font-size:12.5px; color:var(--gray); margin:0 0 8px; line-height:1.8; }
.sources a{ color:var(--gray); }
.sources a:hover{ color:var(--ignite); }

/* --------------------------------------------------------------------------
   People — coaches, roster, former staff
   -------------------------------------------------------------------------- */
.person-card{
  background:var(--chalk-card); border:1px solid var(--gray-light);
  border-top:4px solid var(--maroon); border-radius:var(--radius-md);
  overflow:hidden; display:flex; flex-direction:column;
}
.person-photo{
  width:100%; aspect-ratio:4/5; object-fit:cover; object-position:top center;
  display:block; background:var(--gray-light);
}
.person-card .placeholder-photo{ aspect-ratio:4/5; min-height:0; }
.person-body{ padding:18px 20px 22px; }
.person-body h3{ font-size:21px; margin:0 0 6px; }
.person-role{
  font-family:var(--font-mono); font-size:12px; text-transform:uppercase;
  letter-spacing:.06em; color:var(--gray); margin:0;
}

/* --------------------------------------------------------------------------
   Concessions menu
   -------------------------------------------------------------------------- */
.menu-group{
  background:var(--chalk-card); border:1px solid var(--gray-light);
  border-top:4px solid var(--maroon); border-radius:var(--radius-md);
  padding:22px 24px 24px;
}
.menu-group h3{ font-size:22px; margin:0 0 4px; }
.menu-group .eyebrow{ margin-bottom:14px; display:block; }
.menu-list{ list-style:none; margin:0; padding:0; }
.menu-row{
  display:flex; justify-content:space-between; align-items:baseline; gap:12px;
  padding:9px 0; border-bottom:1px solid var(--gray-light); font-size:14.5px;
}
.menu-row:last-child{ border-bottom:none; }
.menu-row .price{
  font-family:var(--font-mono); color:var(--ignite); font-size:15px; white-space:nowrap;
}
.menu-row .combo{ font-family:var(--font-mono); font-size:12px; color:var(--gray); }
.menu-inline{ font-size:14.5px; line-height:2; color:var(--dirt); margin:0; }
.menu-sub{
  font-family:var(--font-mono); font-size:11px; text-transform:uppercase;
  letter-spacing:.06em; color:var(--gray); margin:16px 0 2px;
}
.menu-sub:first-child{ margin-top:0; }

/* --------------------------------------------------------------------------
   Stub pages
   -------------------------------------------------------------------------- */
.stub{
  max-width:760px; margin:0 auto; background:var(--chalk-card);
  border:1px solid var(--gray-light); border-top:4px solid var(--ignite);
  border-radius:var(--radius-md); padding:34px 30px 36px;
}
.stub h2{ font-size:26px; margin:0 0 12px; }
.stub > p{ font-size:15px; color:var(--dirt); margin:0 0 22px; }
.stub-list{ list-style:none; margin:0 0 24px; padding:0; }
.stub-list li{
  position:relative; padding:10px 0 10px 26px;
  border-bottom:1px solid var(--gray-light); font-size:14.5px;
}
.stub-list li:last-child{ border-bottom:none; }
.stub-list li::before{
  content:"\203A"; position:absolute; left:6px; color:var(--ignite); font-weight:700;
}

/* --------------------------------------------------------------------------
   Document / archive lists
   -------------------------------------------------------------------------- */
.doc-list{ list-style:none; margin:0; padding:0; }
.doc-list li{ border-bottom:1px solid var(--gray-light); }
.doc-list li:last-child{ border-bottom:none; }
.doc-list a{
  display:flex; justify-content:space-between; gap:14px;
  padding:13px 2px; font-size:14.5px; color:var(--dirt); text-decoration:none;
}
.doc-list a:hover{ color:var(--ignite); }
.doc-list .kind{
  font-family:var(--font-mono); font-size:11px; color:var(--gray);
  text-transform:uppercase; letter-spacing:.06em; flex-shrink:0; align-self:center;
}

/* --------------------------------------------------------------------------
   Photo figures
   -------------------------------------------------------------------------- */
.photo-figure{ margin:0; }
.photo-figure img{
  width:100%; display:block; border-radius:var(--radius-md);
  border:1px solid var(--gray-light);
}
.photo-figure figcaption{
  font-family:var(--font-mono); font-size:11.5px; color:var(--gray);
  text-transform:uppercase; letter-spacing:.05em; margin-top:9px;
}

/* --------------------------------------------------------------------------
   Photo strip (home page) -- one row of photos at a fixed height that
   scrolls sideways. A CSS overflow scroller with scroll-snap: it works with
   no script; www/gallery.js adds the auto-drift only. Each photo keeps its
   own aspect ratio (height fixed, width auto), which is why this beat a
   slideshow for a portrait/landscape/square mix -- nothing is cropped.
   -------------------------------------------------------------------------- */
.photo-strip{ display:flex; gap:14px; overflow-x:auto; scroll-snap-type:x mandatory;
  padding:4px 0 14px; scrollbar-width:thin; scrollbar-color:var(--gray-light) transparent; }
.photo-strip:focus-visible{ outline:2px solid var(--ignite); outline-offset:6px; }
.photo-strip figure{ flex:0 0 auto; margin:0; scroll-snap-align:start; }
.photo-strip img{
  height:360px; width:auto; display:block; border-radius:var(--radius-md);
  border:1px solid var(--gray-light);
}
.photo-strip figcaption{
  font-family:var(--font-mono); font-size:11.5px; color:var(--gray);
  text-transform:uppercase; letter-spacing:.05em; margin-top:9px;
}
/* Phones. Found on a real iPhone on 2026-09-30: at a fixed 240px the two
   landscape photos (343px and 359px wide) were WIDER than the 297px
   scrollport, and scroll-snap-type:x mandatory pins the view to each
   photo's left edge, so their right side could never be seen and no slice
   of the next photo showed to say the strip scrolls at all -- a phone saw
   one cropped photo that changed every 4s. Reproduced in Chrome emulation:
   a 120px finger drag settled straight back at scrollLeft 0.
   So on a phone the row bleeds to the screen edges (the .wrap and section
   gutters are 24px each, hence -48px), each photo's height is derived from
   the viewport so the WIDEST photo (ratio 1.5, the huddle) fits whole with
   ~26px of the next one showing, and a 24px inner gutter plus matching
   scroll-padding keeps a snapped photo off the screen edge. Change the
   ratio here if a wider photo is ever added. docs/browser-verification/
   strip.py asserts all of this at iPhone metrics. */
@media (max-width:640px){
  .photo-strip{ margin:0 -48px; padding:4px 24px 14px; scroll-padding:0 24px; }
  .photo-strip img{ height:min(240px, calc((100vw - 64px) / 1.5)); }
}

/* --------------------------------------------------------------------------
   Coach tools — tool hub grid, tool cards, and coach forms.
   Phones in a dugout: every form field is single column (the shared
   input/select/textarea/label rules already are), and every wide table
   sits in .table-wrap, which scrolls its own container instead of the page.
   -------------------------------------------------------------------------- */
.tool-grid{
  display:grid; grid-template-columns:repeat(auto-fit,minmax(220px,1fr));
  gap:20px; max-width:1100px; margin:0 auto;
}
.tool-card{
  display:block; text-decoration:none;
}
.tool-card:hover{ border-top-color:var(--ignite); }
.tool-card.is-future{
  opacity:.6; cursor:default;
}
.tool-card .tag{ margin-top:10px; }

.tool-form{ display:grid; gap:16px; max-width:32rem; }
.tool-form label{ display:grid; gap:6px; }
.tool-form-photo{
  width:120px; aspect-ratio:4/5; object-fit:cover; object-position:top center;
  display:block; border-radius:var(--radius-md); border:1px solid var(--gray-light);
}
.tool-form fieldset{
  border:1px solid var(--gray-light); border-radius:var(--radius-md);
  padding:14px 16px; display:grid; gap:12px;
}
.tool-form legend{
  font-family:var(--font-mono); font-size:11px; text-transform:uppercase;
  letter-spacing:.06em; color:var(--gray); padding:0 4px;
}

.tool-tabs{
  display:flex; flex-wrap:wrap; gap:10px;
  max-width:1100px; margin:0 auto 24px;
}

/* --------------------------------------------------------------------------
   Schedule tool — draft/published status pill, per-row actions, and the
   two-step delete confirmation (a server-rendered panel rather than a
   confirm(), which cannot name what is being deleted --
   see www/coach/schedule.php). .row-actions wraps rather than overflows
   the table's own .table-wrap scroller, so it never widens the page.
   -------------------------------------------------------------------------- */
.status-draft,.status-published{
  font-family:var(--font-mono); font-size:12px; padding:2px 8px; border-radius:3px;
}
.status-draft{ color:var(--dirt); border:1px solid var(--gray); }
.status-published{ color:var(--ignite); border:1px solid var(--ignite); }

/* Inventory tool -- ok/low/out status pill. Three deliberately distinct
   tiers using only brand variables: a calm neutral for "ok", the same
   amber-attention accent already used elsewhere for a notable-but-not-
   urgent state (.tag-ignite's "full — spares still welcome") for "low",
   and a maroon FILL for "out" -- maroon is a fill/border colour only,
   never text, per the locked brand rules -- matching the same
   maroon-fill-plus-dirt-text pattern .btn-danger already uses. */
.status-ok,.status-low,.status-out{
  font-family:var(--font-mono); font-size:12px; padding:2px 8px; border-radius:3px;
}
.status-ok{ color:var(--dirt); border:1px solid var(--gray-light); }
.status-low{ color:var(--ignite); border:1px solid var(--ignite); }
.status-out{ color:var(--dirt); background:var(--maroon); border:1px solid var(--maroon-dark); }

.row-actions{
  display:flex; flex-wrap:wrap; align-items:center; gap:10px;
}
.row-actions form{ display:inline-flex; }
.confirm-actions{ margin-top:12px; }

.link-danger{
  font-family:var(--font-mono); font-size:12px; color:var(--gray);
  text-decoration:underline;
}
.link-danger:hover{ color:var(--dirt); }

.notice-danger{ border-left-color:var(--maroon); }

.btn-danger{ background:var(--maroon); color:var(--dirt); border-color:var(--maroon-dark); }
.btn-danger:hover{ background:var(--maroon-dark); }

/* --------------------------------------------------------------------------
   Lineup Cards picker — one card per eligible game, one action link per
   team. Reuses .status-draft/.status-published and .row-actions above.
   -------------------------------------------------------------------------- */
.lineup-grid{
  display:grid; gap:20px; grid-template-columns:repeat(auto-fill, minmax(260px, 1fr));
}
.lineup-grid .row-actions{ flex-wrap:wrap; gap:8px; margin-top:10px; }

/* --------------------------------------------------------------------------
   Lineup editor -- nine batting-order rows plus the optional DH row, all in
   one no-JS form. Built for a coach filling this in on a phone in a
   dugout: every row wraps to its own lines rather than shrinking its
   selects to the point of being unreadable. .lineup-form widens past
   .tool-form's normal 32rem cap because two selects need the room; it is
   still a single column at every width, matching the rest of the site's
   forms.
   -------------------------------------------------------------------------- */
.lineup-form{ max-width:34rem; }
.lineup-row{
  display:flex; flex-wrap:wrap; align-items:center; gap:8px 10px;
  padding:10px 0; border-bottom:1px solid var(--gray-light);
}
.lineup-row:last-child{ border-bottom:none; }
.lineup-num{
  flex:0 0 2.6em; color:var(--ignite); font-size:13px;
}
.lineup-row select.lineup-player{ flex:1 1 11rem; min-width:0; width:auto; }
.lineup-row select.lineup-position{ flex:1 1 5.5rem; min-width:4.5rem; width:auto; }
/* Specificity note: the shared `.tool-form label{ display:grid; gap:6px; }`
   rule (site.css, above) has specificity 0,1,1 and would otherwise stack
   the checkbox above its own text on its own line -- exactly the kind of
   thing that only shows up in a screenshot, never in markup review. The
   selector below is qualified with `.lineup-form` to reach 0,2,1 and
   reliably win regardless of source order. */
.lineup-form label.lineup-dh-toggle{
  display:flex; align-items:center; gap:8px;
  font-family:var(--font-mono); font-size:13px; text-transform:none;
  color:var(--dirt); margin-bottom:0;
}
.lineup-form label.lineup-dh-toggle input{ width:auto; margin:0; }

/* THE LIVE AVAILABILITY FLAG, created by lineup.js when a slot holds an
   injured or absent player. It replaces the notice the "Place anyway"
   checkbox used to carry, and it is deliberately quieter than that was: a
   checkbox asked for a decision, this only reports a fact the coach is
   free to ignore -- the save succeeds either way and the server warns
   regardless.

   Gold on chalk, not maroon: --maroon is a fill/border colour only and
   check.py fails the build on `color: var(--maroon)`. Full-width on its
   own line here, the way the override label used to be, so a phone-width
   row does not squeeze the two selects to fit it. */
.lineup-form .lineup-avail-flag{
  flex:1 1 100%; font-family:var(--font-mono); font-size:11px;
  letter-spacing:.06em; color:var(--ignite);
}
/* One-line rows on a laptop: the flag rides along at the end of the row
   rather than opening a second line for every injured player. 0,3,0
   against the 0,2,0 above, so source order is not doing the work. */
@media(min-width:1100px){
  .lineup-form .lf-order .lineup-avail-flag{
    flex:none; font-size:10.5px; letter-spacing:.02em; white-space:nowrap;
  }
}

/* --------------------------------------------------------------------------
   Lineup editor: FIT THE JOB IN ONE SCREEN.

   Measured at 1440x900 (757px of viewport) before this block existed:
     139px  site header
     193px  maroon page-head
    2128px  the form
    1741px  the roster panel
    3906px  document
   -- five screens of scrolling to fill in a batting order, which a coach
   does minutes before first pitch on a laptop at a field.

   The fix is horizontal, not a modal: three columns across (batting order |
   roster | everything else), one-line rows, and a roster list that scrolls
   INSIDE ITS OWN BOX rather than pushing the page down. A modal would have
   solved the space problem by hiding the roster behind a click, which is
   the opposite of what a coach dragging players between two lists needs --
   both lists have to be on screen at once for a drag to be possible at all.
   -------------------------------------------------------------------------- */

/* A tool's hero is a label, not a landing band. */
.page-head-tool{ padding-top:14px; padding-bottom:13px; }
.page-head-tool h1{ font-size:26px; margin:1px 0 0; }
.tool-section{ padding-top:18px; padding-bottom:18px; }
.page-head-tool .ph-date{
  display:inline-block; font-family:var(--font-mono); font-size:13px;
  letter-spacing:.04em; margin-left:10px; vertical-align:middle;
}

@media(min-width:1100px){
  /* form (which is itself two columns) + roster panel */
  /* The BATTING ORDER takes the slack, and the two side columns are fixed.
     Two minmax(0,Nrem) columns shrink together when the container is
     narrower than their sum -- .wrap is 1100px minus 48px of padding, so
     42rem + 21rem overflowed it and squeezed the order column to 266px,
     which put every row back over 70px. 1fr for the order fixes it. */
  /* .wrap prefix for SPECIFICITY: the @media(min-width:900px) block further
     down sets `.lineup-workspace { grid-template-columns:minmax(0,34rem)
     minmax(0,22rem) }` at the same 0,1,0 and sits LATER, so at 1440px both
     queries matched and that one won on source order -- the grid resolved
     to 544px 352px and no amount of trimming inside the order column could
     help, because the column itself was 284px. */
  /* ROSTER ON THE LEFT. It is the source you drag FROM, so it reads first
     in a left-to-right language, and it is now first in the DOM too --
     placing it visually first with `order` or an explicit grid-column
     while leaving it last in the markup would have put the tab order out
     of step with the screen. */
  /* 24rem, not 21: the roster list runs two players per row now, and at
     21rem each cell was 162px -- a name plus a jersey number with nothing
     left over. The 3rem comes out of the batting order, which had about
     100px of slack (its longest name used 175px of a 320px field). */
  .wrap .lineup-workspace{ grid-template-columns:24rem minmax(0,1fr); gap:22px; }
  /* .lineup-workspace prefix for SPECIFICITY: `.tool-form { max-width:32rem }`
     and `.lineup-form { max-width:34rem }` are both 0,1,0 and sit later in
     this file, so a bare `.lineup-form` here lost on source order and the
     form stayed capped at 32rem -- which squeezed the order column to 284px
     and kept every row two lines tall however much else was trimmed. */
  .lineup-workspace .lineup-form{ max-width:none; display:grid; gap:0 20px;
    grid-template-columns:minmax(0,1fr) 15rem; align-items:start; }
  .lf-order{ grid-column:1; }
  /* THE THREE COLUMNS EACH GET THE SAME HEIGHT BUDGET, and only the two
     side columns are allowed to scroll inside themselves. 100vh minus the
     site header, the compact hero and the section padding is what is left
     for the tool; measured at 1440x900 that is about 460px.

     The batting order is never in a scroller -- it is 393px and fits. The
     roster list scrolls because 33 names cannot fit beside nine slots at
     any readable size. The extras column scrolls its CONTENT but keeps the
     Save button outside the scroller, pinned to the bottom of the column:
     the one control a coach must be able to reach is never the one that
     scrolled away. */
  .lf-extras{ grid-column:2; display:grid; gap:10px; align-content:start;
    grid-template-rows:minmax(0,1fr) auto; max-height:calc(100vh - 300px); }
  .lf-extras-scroll{ display:grid; gap:8px; align-content:start;
    overflow-y:auto; min-height:0; padding-right:4px; }
  /* A dozen names would otherwise make one of these zones as tall as the
     whole column -- and .lf-order has no scroller of its own, so an
     uncapped bench there would push the ninth batting slot off screen.
     Tall enough for two rows of names before it scrolls, so a scrolling
     zone never shows a card sliced in half at the boundary.
     .lf-order is matched on .drop-zone-multi ONLY: the nine batting-slot
     zones are plain .drop-zone and must never grow a scrollbar. */
  .lf-extras .drop-zone,
  .lf-order .drop-zone-multi{ max-height:5.2rem; overflow-y:auto; align-content:start; }
  /* csrf_field() and the hidden action input are the form's other direct
     children; they render nothing, but as grid items they would still open
     a row. Take them out of the flow entirely. */
  .lineup-form > input[type="hidden"]{ position:absolute; }
}

/* One-line rows. 93px each x 10 was 925px on its own; this is ~40px. */
@media(min-width:1100px){
  .lf-order .lineup-row{ padding:1px 0; gap:3px 7px; }
  .lf-order .lineup-num{ flex:none; width:1.4rem; text-align:right; }
}
/* COMPACT FIELDS ARE DESKTOP-ONLY. Left unscoped, this rule took the
   editor's selects from 44px to 32px at every width -- including a phone,
   where 44px is the minimum comfortable tap target and the user is a coach
   standing in a dugout. The height budget this is serving only exists on a
   laptop; a phone scrolls and always did. */
@media(min-width:1100px){
  .lineup-form select, .lineup-form input[type="text"], .lineup-form input[type="time"]{
    min-height:0; padding-top:5px; padding-bottom:5px; font-size:14px;
  }
}
@media(min-width:1100px){
  /* Give the name zone the room that is actually left after the number
     and the position select. 11rem was a basis borrowed from the
     full-width layout and forced a wrap. */
  .dnd-on .lf-order .drop-zone{ flex:1 1 6rem; min-height:1.55rem; padding:1px 7px; }
  .lf-order .dz-card{ padding:2px 8px; font-size:13.5px; }
  .lf-order .lineup-row select.lineup-position{ flex:0 0 4.6rem; min-width:0; }
}
.lineup-form h2{ font-size:20px; margin:0 0 8px; }
.lineup-form .lf-extras h2{ margin-top:6px; }
/* The Bench heading needs air above it. .lf-order's first h2 is "Batting
   order" and sits flush at the top of the column; the bench one follows
   the ninth slot, whose row has no bottom border, so without this the
   heading collides with the last batter's name. :not(:first-child) is
   what keeps the two apart without a class. */
.lineup-form .lf-order h2:not(:first-child){ margin-top:16px; }

/* The DH explainer, folded away. Native <details>, no script. */
.lf-hint{ margin:0; }
.lf-hint > summary{
  cursor:pointer; font-family:var(--font-mono); font-size:11px;
  letter-spacing:.06em; text-transform:uppercase; color:var(--gray);
}
.lf-hint p{ margin:6px 0 0; font-size:12.5px; }

/* Bench and available pitchers: the drop zones do the work at desktop, so
   the size="6" multi-selects (138px each, 276px together) are clipped the
   same way the player selects are -- present, focusable, readable to a
   screen reader, back in view on focus. Below 1200px they are the control
   and are left alone. */
@media(min-width:1100px){
  /* BOTH COLUMNS. The bench moved to .lf-order on 2026-09-08 and the
     pitcher list stayed in .lf-extras; these rules follow the CONTROL, not
     the column it happens to sit in, so moving one again does not silently
     un-clip its select and put every name on screen twice. */
  .lineup-form .lf-extras select[multiple],
  .lineup-form .lf-order select[multiple]{
    position:absolute; width:1px; height:1px; padding:0; margin:0;
    overflow:hidden; clip-path:inset(50%);
  }
  .lineup-form .lf-extras select[multiple]:focus,
  .lineup-form .lf-order select[multiple]:focus{
    position:static; width:auto; height:auto; padding:revert; margin:revert;
    overflow:auto; clip-path:none;
  }
  .lf-extras .drop-zone{ flex:1 1 100%; min-height:3.4rem; align-items:flex-start; }
  /* SOURCE ORDER MATTERS HERE. `.dnd-on .lf-order .drop-zone` above sets
     flex:1 1 6rem and min-height:1.55rem to make the nine slot rows one
     line tall; it is 0,3,0 and this is 0,3,0, so this rule wins only by
     sitting later -- do not move it above that one. The bench is a list,
     not a row: it takes the full width of the column and starts at the
     top rather than centring one card vertically. */
  .dnd-on .lf-order .drop-zone-multi{
    flex:1 1 100%; min-height:3.4rem; align-items:flex-start; padding:5px 8px;
  }
}

/* The roster list scrolls in its own box. A 33-name list cannot fit beside
   a nine-slot order at any font size worth reading; scrolling the LIST is
   not scrolling the page, and it keeps both drag endpoints on screen. */
@media(min-width:1100px){
  .roster-panel{ display:grid; gap:8px; align-content:stretch;
    max-height:calc(100vh - 300px); grid-template-rows:auto auto auto minmax(0,1fr); }
  .roster-list{ overflow-y:auto; min-height:0; padding-right:4px; }
  /* The filters are FOLDED AWAY by default (see the <details> in
     _roster_panel.php). Measured at 290px of a 547px panel, which left the
     roster list -- the thing a coach actually drags from -- just 131px.
     Filtering is occasional; the list is constant, so the list gets the
     space and the filters get a click. */
  /* SEARCH AND GROUPING SHARE ONE ROW. Stacked they were two controls and
     a wrapped two-line "GROUP BY" label; the label is gone (it moved into
     the options) and the two sit side by side. The search takes the slack
     because a name is longer than "by availability".

     GATED ON .dnd-on -- the class lineup.js adds -- because the row only
     fits once the script has hidden Find. With no script Find is still
     there, and squeezing it plus a search box plus the group select into
     336px left the search 120px wide and its placeholder cut to "Type a
     na...". So the row is itself an enhancement: script live, one row;
     script dead, the stacked full-width controls that have room for the
     button. Measured, not assumed -- see the no-script render in the
     session that made this change. */
  .roster-panel.dnd-on .roster-toolbar{
    grid-template-columns:minmax(0,1fr) 9.5rem; gap:8px; align-items:center;
  }

  /* THE PANEL'S CONTROLS ARE COMPACT TOO. The batting order beside this
     was compacted at this breakpoint and the panel never was, so the two
     columns of the same screen ran different field heights -- 44px here
     against 32px there. Same declaration as the .lineup-form rule above,
     for the same reason: the height budget only exists on a laptop. */
  .roster-panel select,
  .roster-panel input[type="search"],
  .roster-panel input[type="text"]{
    min-height:0; padding-top:5px; padding-bottom:5px; font-size:14px;
  }

  .roster-filters{ grid-template-columns:1fr 1fr; gap:8px; margin-bottom:0; }
  /* Apply/Clear RIDE BESIDE "Class of" rather than spanning a row of their
     own. Five fields in a two-column grid leaves an odd one out; putting
     the actions in that hole removes both the dead gap and a whole row.
     align-self:end so the buttons sit on the input's baseline, not
     floating at the top of a taller cell. */
  .roster-filters .row-actions{ align-self:end; gap:8px; }
  /* Compact to the height of the "Class of" input beside them -- .btn's
     13px/24px padding is sized for a page's primary action, not for a
     pair sitting in a filter grid cell.

     text-decoration:none because Clear is an <a class="btn">: the locked
     brand stylesheet's .btn sets no decoration, so an anchor button keeps
     the default underline and stops matching the real <button> next to
     it. SCOPED TO THIS PANEL ON PURPOSE -- the same thing is true of
     every a.btn on the site (Back, Cancel), and fixing those is a
     site-wide visual change nobody asked for. */
  .roster-filters .row-actions .btn{ flex:1 1 0; min-width:0;
    padding:6px 8px; font-size:12px; text-align:center; text-decoration:none; }
  .roster-filters label{ font-size:11px; }
  .roster-filter-wrap{ min-height:0; }
  .roster-panel h2{ font-size:20px; margin:0; }
  .roster-card{ padding:4px 6px; }
}

/* --------------------------------------------------------------------------
   Lineup editor drag-and-drop zones (created by www/coach/lineup.js).

   THESE ARE AN ADDITION, NOT A REPLACEMENT. The <select> elements stay
   visible and stay the source of truth; a zone is a second way to set the
   same value. See lineup.js's header for the two reasons: HTML5
   drag-and-drop does not work on touch without a library, and it is
   mouse-only for anyone on a keyboard or a screen reader.

   Hence the two guards below, and they are the point of this block:
     - the zones only exist at all if lineup.js ran, and
     - they are only SHOWN at >=900px, so a coach on a phone in a dugout
       gets today's form and never a drop target he cannot use.
   `.dnd-on` is added last by the script, so a zone cannot appear before
   its listeners are attached.
   -------------------------------------------------------------------------- */
.drop-zone{ display:none; }

@media(min-width:900px){
  .dnd-on .drop-zone{
    display:flex; flex-wrap:wrap; align-items:center; gap:6px;
    flex:1 1 11rem; min-width:0; min-height:2.4rem; padding:5px 8px;
    border:1px dashed var(--gray-light); border-radius:var(--radius-md);
  }
  .dnd-on .drop-zone-multi{ flex:1 1 100%; }

  /* The player <select> is VISUALLY hidden once the zone is showing the
     same name -- rendering both put every player's name on screen twice
     and tripled the height of all nine rows (caught in a screenshot; the
     markup looked fine). It is hidden by CLIPPING, never display:none, so
     it stays in the tab order, stays readable to a screen reader, and
     remains the control that actually holds the value. It springs back
     into view on focus, so a sighted keyboard user sees what they are
     changing. Below 900px this whole block does not apply: the zones are
     display:none and the select is simply the visible control, which is
     what a coach on a phone gets. */
  .dnd-on .lineup-row select.lineup-player{
    position:absolute; width:1px; height:1px; padding:0; margin:0;
    overflow:hidden; clip-path:inset(50%); white-space:nowrap;
  }
  .dnd-on .lineup-row select.lineup-player:focus{
    position:static; width:auto; height:auto; padding:revert; margin:revert;
    overflow:visible; clip-path:none; flex:1 1 11rem;
  }
  .dnd-on .drop-zone.dz-over{ border-style:solid; border-color:var(--ignite); }
  /* text-transform and font-family are RESET, not inherited: the bench and
     pitcher zones live inside a <label>, which this site styles as
     uppercase mono, so player names rendered as "COHEN CORBELL". A name is
     not a field label. */
  .dz-card{
    display:inline-block; padding:4px 9px; cursor:grab;
    background:var(--chalk-card); border:1px solid var(--gray-light);
    border-radius:var(--radius-md); font-size:14px; color:var(--dirt);
    font-family:var(--font-body); text-transform:none; letter-spacing:normal;
  }
  .dz-hint{
    font-family:var(--font-mono); font-size:11px; letter-spacing:.06em;
    text-transform:uppercase; color:var(--gray);
  }
  .dnd-on .roster-card{ cursor:grab; }
}

/* Coaching-staff tick list in the Teams tool. Same specificity trap and
   same fix as .lineup-dh-toggle: the shared `.tool-form label` rule would
   stack the checkbox above its own text. */
.tool-form label.staff-tick{
  display:flex; align-items:center; gap:8px;
  font-family:var(--font-mono); font-size:13px; text-transform:none;
  color:var(--dirt); margin:0;
}
.tool-form label.staff-tick input{ width:auto; margin:0; }

/* The coaching staff strip at the foot of the card. Quiet -- it is
   attribution, not a headline -- and separated from the bench above by the
   same 2px rule the bench uses, so the card reads as three bands: order,
   bench, staff. */
.lc-staff{ padding:12px 18px 14px; border-top:2px solid var(--gray-light); }
.lc-staff-names{ margin:0; font-size:14px; color:var(--dirt); }
.lc-staff-none{ margin:0; font-size:13.5px; color:var(--gray); font-style:italic; }

/* --------------------------------------------------------------------------
   Lineup editor workspace -- the batting order and the roster panel side by
   side (www/coach/lineup.php).

   The panel is a SIBLING of the post form, never nested in it: its filter
   form is a GET, and HTML forbids a form inside a form. This grid is what
   puts them next to each other anyway. Below 900px they stack, panel last,
   so a coach on a phone scrolls order-then-roster rather than sideways.
   -------------------------------------------------------------------------- */
.lineup-workspace{ display:grid; gap:28px; align-items:start; }
@media(min-width:900px){
  .lineup-workspace{ grid-template-columns:minmax(0,22rem) minmax(0,34rem); }
}
/* The three-column layout starts at 1100px rather than 1200: a 1152- or
   1280-wide window is a real laptop and gains the whole benefit. */
@media(min-width:900px){
  /* Sticky so the roster stays reachable while dragging down the order. */
  .roster-panel{ position:sticky; top:16px; }
}
.roster-panel h2{ margin-top:0; }

/* Type-to-filter. A real GET form with a submit, so it works with no
   script; lineup.js filters as you type with no round trip. */
/* For a label that must exist for a screen reader but would be noise on
   screen -- the search input already says what it is with a placeholder
   and an obvious Find button beside it. */
.visually-hidden{
  position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip-path:inset(50%); white-space:nowrap; border:0;
}
.roster-tools{ display:grid; gap:8px; }

/* THE TOOLBAR. Stacked by default -- a phone has vertical room and wants
   44px tap targets -- and one row only at desktop, where the column is
   336px wide and every pixel the tools take is a pixel the drag list does
   not get. See the @media(min-width:1100px) block further down. */
.roster-toolbar{ display:grid; gap:8px; }
.roster-search{ display:flex; gap:6px; align-items:stretch; margin:0; }
.roster-search input[type="search"]{ flex:1 1 auto; min-width:0; }
.roster-search .btn{ flex:none; padding-left:12px; padding-right:12px; }

/* THE SCRIPT HIDES ITS OWN FALLBACK. .dnd-on is added by lineup.js only
   after its listeners are attached, so Find disappears exactly when
   type-to-filter starts working and STAYS VISIBLE if the script never
   ran. Never hide this unconditionally -- with no script it is the whole
   server-side search. */
.dnd-on .roster-find{ display:none; }

/* No visible label: the words live in the options ("by position"), which
   is what let this control go full width and stop wrapping. The <span>
   inside is .visually-hidden for screen readers. */
.roster-groupby{ display:block; margin:0; }
.roster-groupby select{ width:100%; min-width:0; text-transform:none;
  letter-spacing:normal; }

/* The "N of M players" line is a CAPTION for the list under it, not a
   paragraph of prose, and it had no rule of its own -- so it carried the
   default <p> margin at both ends and put ~32px of nothing between the
   filters and the first draggable name. In this column that is a name and
   a half. lineup.js rewrites its text as you filter; only the spacing
   changes here. */
.roster-count{ margin:0; }

/* INLINE COLLAPSIBLE GROUPS. Native <details>, no script needed. */
.roster-group{ border-top:1px solid var(--gray-light); }
.roster-group:first-child{ border-top:0; }
.roster-group > summary{
  cursor:pointer; display:flex; align-items:center; gap:8px;
  padding:6px 6px; font-family:var(--font-mono); font-size:11px;
  letter-spacing:.07em; text-transform:uppercase; color:var(--ignite);
}
.roster-group[hidden]{ display:none; }
.rg-count{
  margin-left:auto; font-size:10px; color:var(--gray);
}
.roster-group > .roster-rows{ padding-bottom:4px; }

.roster-filter-wrap > summary{
  cursor:pointer; font-family:var(--font-mono); font-size:11px;
  letter-spacing:.06em; text-transform:uppercase; color:var(--gray);
  margin-bottom:10px;
}
.roster-filters{ display:grid; gap:10px; margin-bottom:18px; }
.roster-filters label{ display:grid; gap:4px; }

/* ROWS, NOT CARDS -- matching the Batting order rows in the next column.
   As padded cards these were 62px each and eight names filled the panel;
   as ruled rows they are ~30px and the whole squad is one short scroll.
   The two columns also now read as one tool rather than two designs. */
.roster-rows{ list-style:none; margin:0; padding:0; }
.roster-card{
  display:grid; grid-template-columns:2.2rem minmax(0,1fr) auto;
  align-items:center; gap:0 8px; margin:0;
  padding:5px 6px; border-bottom:1px solid var(--gray-light);
}
.roster-card:last-child{ border-bottom:0; }
.rc-jersey{
  font-family:var(--font-mono); font-size:13px; color:var(--gray);
  text-align:right;
}
.rc-name{ font-size:14.5px; color:var(--dirt); min-width:0;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.rc-meta{
  font-family:var(--font-mono); font-size:10px; color:var(--gray);
  text-align:right; white-space:nowrap;
}
/* THIS RULE IS LOAD-BEARING. lineup.js hides a player who has been placed
   in the order by setting `card.hidden = true`, and the HTML `hidden`
   attribute only works through the user-agent stylesheet's
   `[hidden] { display:none }` -- which the `.roster-card { display:grid }`
   above outranks, because an author rule beats a UA rule at any
   specificity. Without this line, setting .hidden changed the
   accessibility tree and NOTHING ELSE: every placed player stayed on
   screen at full height while the property said otherwise. Shipped once
   exactly that way, and the test that "proved" it worked had asserted
   `card.hidden === true` rather than measuring whether the card had
   actually gone. Do not remove this rule, and do not give .roster-card a
   display value in a more specific selector without repeating it. */
.roster-card[hidden]{ display:none; }
.roster-rows[hidden]{ display:none; }

/* --------------------------------------------------------------------------
   TWO PLAYERS PER ROW, plus the hover card that pays for it.

   The cell is compact -- jersey and name only -- because the meta text
   (position, bats, throws, class year) moves to a card that appears on
   hover. That trade is only sound while the card exists, so BOTH are gated
   on .dnd-on, the class lineup.js adds once its listeners are attached:
   script live, two columns and a hover card; script dead, one column with
   the meta text visible, exactly as before.

   NOTE WHAT IS *NOT* HERE: no display rule on .roster-card. The grid goes
   on the <ul>, so `.roster-card[hidden] { display:none }` above still wins
   and a placed player still actually disappears. Adding `display` to a
   more specific .roster-card selector would silently undo that -- see the
   load-bearing comment above it.
   -------------------------------------------------------------------------- */
@media(min-width:1100px){
  .roster-panel.dnd-on .roster-rows{
    display:grid; grid-template-columns:1fr 1fr; gap:0 10px; align-content:start;
  }
  /* AND IMMEDIATELY PUT [hidden] BACK ON TOP. The rule above is 0,3,0 and
     `.roster-rows[hidden] { display:none }` is 0,2,0, so the grid would
     have outranked it and a hidden list would have kept rendering -- the
     exact failure the load-bearing comment above describes, reintroduced
     one specificity step higher. Nothing sets .roster-rows[hidden] today;
     this is here so that the day something does, it works. */
  .roster-panel.dnd-on .roster-rows[hidden]{ display:none; }
  /* CLIPPED, NOT display:none. The hover card is a sighted-mouse
     affordance and gives a screen reader nothing, so the row's own
     attributes stay in the accessibility tree and are only removed from
     the screen -- the same treatment the drop-zone'd multi-selects get. */
  .roster-panel.dnd-on .rc-meta{
    position:absolute; width:1px; height:1px; padding:0; margin:0;
    overflow:hidden; clip-path:inset(50%); white-space:nowrap;
  }
  /* The name gets the room the meta gave up. */
  .roster-panel.dnd-on .roster-card{ grid-template-columns:2rem minmax(0,1fr); }
  .roster-panel.dnd-on .rc-name{ font-size:13.5px; }
}

/* THE HOVER CARD. Appended to <body> by lineup.js and positioned fixed --
   it has to escape .roster-list's own overflow:auto, which would otherwise
   clip it to the scrolling box the moment it extended past an edge.

   pointer-events:none IS LOAD-BEARING. This element tracks the pointer
   across a list whose entire purpose is being dragged from. If it could
   take pointer events it would sit between the cursor and the row,
   swallow dragover, and drop the player nowhere. */
.rc-hovercard{
  position:fixed; z-index:60; pointer-events:none;
  width:15rem; padding:10px 12px 11px;
  background:var(--chalk-card); border:1px solid var(--gray-light);
  border-left:3px solid var(--ignite);
  border-radius:var(--radius-md);
  /* --chalk, not an rgba(): site.css may contain NO literal colour, and
     check.py fails the build on one (it caught exactly this line). The
     page ground is --chalk, so an opaque chalk shadow is invisible against
     the background and reads only where the card overlaps the batting
     order -- which is the separation a floating card actually needs. */
  box-shadow:0 8px 24px var(--chalk);
}
.rc-hovercard[hidden]{ display:none; }
.rc-hc-head{ display:flex; align-items:baseline; gap:8px; margin-bottom:7px; }
.rc-hc-jersey{
  font-family:var(--font-mono); font-size:19px; color:var(--ignite);
  line-height:1;
}
.rc-hc-name{
  font-family:var(--font-display); font-size:16px; color:var(--outline-stroke);
  line-height:1.15; min-width:0;
}
.rc-hc-stats{ display:grid; grid-template-columns:auto 1fr; gap:3px 10px; margin:0; }
.rc-hc-stats dt{
  font-family:var(--font-mono); font-size:9.5px; letter-spacing:.08em;
  text-transform:uppercase; color:var(--gray);
}
.rc-hc-stats dd{
  margin:0; font-family:var(--font-mono); font-size:12px; color:var(--dirt);
}
.rc-hc-out dd.rc-hc-avail{ color:var(--ignite); }

/* Unavailable is SHOWN AND LABELLED, never hidden -- the editor's standing
   rule. A coach may still place an injured player; the tool warns, it does
   not refuse. */
.roster-card-out{ border-style:dashed; }
.roster-card-out .rc-name{ color:var(--gray); }

/* --------------------------------------------------------------------------
   Public lineup card (www/lineup.php) -- the thing an umpire gets handed,
   not a data table.

   This was a .roster-table and read like a spreadsheet. A lineup card has a
   shape people recognise: a header block naming the team and the fixture,
   then ruled rows carrying the batting number, the jersey, the name, and the
   position boxed hard right. The grid below is that shape; the fixed columns
   total 8rem so the name column keeps the rest at any width, down to 390px,
   with no horizontal scroll.

   Two cards sit side by side once there is room for them (a night with a
   Varsity and a JV card published), and stack below that.
   -------------------------------------------------------------------------- */
.lineup-cards{
  display:grid; gap:24px; max-width:1100px; margin:0 auto;
  grid-template-columns:repeat(auto-fit,minmax(20rem,34rem));
  justify-content:center;
}
/* A card is capped rather than stretched. Left to fill 1100px, ONE published
   card pushed its position box the width of the page away from the name and
   stopped looking like a card at all -- a lineup card is a portrait-ish
   object, and two of them (a V and a JV night) sit side by side. */
.lineup-card{
  background:var(--chalk-card); border:1px solid var(--gray-light);
  border-radius:var(--radius-md); overflow:hidden; max-width:34rem;
  /* So the bench's two-column layout can respond to the CARD's width
     rather than the viewport's -- two cards side by side are each about
     half the page. A browser without container queries simply keeps two
     columns, which is the desktop layout anyway. */
  container-type:inline-size;
}
/* The head is a solid MAROON BAND, so the headline rule applies: the team
   name carries .h-white in the markup (never .h-outline, which is for a
   headline on chalk), and every other line here is --dirt, the palette's
   light-on-dark body colour. --chalk is the near-black PAGE BACKGROUND, not
   a text colour -- using it here put near-black text on maroon, which is
   the sort of thing that only ever shows up in a screenshot. */
.lc-head{ background:var(--maroon); padding:14px 18px; }
/* The club mark and name above the team. Small and quiet: it identifies the
   card, it is not the headline -- the team and the fixture are. */
.lc-club{
  display:flex; align-items:center; gap:7px; margin:0 0 6px;
  font-family:var(--font-mono); font-size:10.5px; letter-spacing:.1em;
  text-transform:uppercase; color:var(--dirt); opacity:.85;
}
.lc-club .icon{ flex:none; }
.lc-team{
  font-family:var(--font-display); font-size:24px; line-height:1.1; margin:0;
}
.lc-vs{
  font-family:var(--font-mono); font-size:12px; letter-spacing:.08em;
  text-transform:uppercase; color:var(--dirt); margin:4px 0 0;
}
.lc-meta{
  display:flex; flex-wrap:wrap; gap:6px 22px; margin:12px 0 0;
  font-family:var(--font-mono); font-size:11px;
}
.lc-meta dt{ letter-spacing:.08em; text-transform:uppercase; color:var(--dirt); opacity:.75; }
.lc-meta dd{ margin:2px 0 0; color:var(--dirt); font-size:13px; }

.lc-order{ list-style:none; margin:0; padding:0; counter-reset:none; }
.lc-order li{
  display:grid; grid-template-columns:2.2rem 2.6rem 1fr 3.2rem;
  align-items:baseline; gap:8px;
  padding:11px 18px; border-top:1px solid var(--gray-light); margin:0;
}
.lc-order li:first-child{ border-top:0; }
.lc-num{
  font-family:var(--font-mono); font-size:15px; color:var(--gray);
}
.lc-jersey{
  font-family:var(--font-mono); font-size:17px; color:var(--dirt); text-align:right;
}
.lc-name{ font-size:16px; color:var(--dirt); }
.lc-pos{
  font-family:var(--font-mono); font-size:12px; letter-spacing:.04em;
  text-align:center; color:var(--dirt);
  border:1px solid var(--gray-light); border-radius:3px; padding:3px 0;
}
/* A slot with nobody in it is a blank ruled line on a real card, so it reads
   as "not filled in yet" rather than as data. */
.lc-empty .lc-name{ color:var(--gray); font-style:italic; }
.lc-empty .lc-pos{ border-style:dashed; }

/* The DH's defence-only row: 'full' scope only -- see www/lineup.php. */
.lc-dh li{ border-top:2px solid var(--gray-light); }
.lc-dh .lc-num{ font-size:11px; letter-spacing:.06em; }
.lc-note{
  display:block; font-family:var(--font-mono); font-size:11px; color:var(--gray);
}

.lc-extras{ padding:14px 18px 16px; border-top:2px solid var(--gray-light); }
.lc-extras p{ margin:0 0 8px; font-size:14px; }
.lc-extras p:last-child{ margin-bottom:0; }
.lc-extras-label{
  display:block; font-family:var(--font-mono); font-size:11px;
  letter-spacing:.08em; text-transform:uppercase; color:var(--gray);
  text-align:left; padding-bottom:6px;
}

/* The bench, as a table rather than a comma-joined sentence -- it is a list
   of people with numbers, and run together you could not pick out a name or
   read a jersey off it. Column widths match the batting order's above so the
   card reads as one object rather than two unrelated blocks. */
.lc-bench{ width:100%; border-collapse:collapse; background:none; }
.lc-bench td{
  text-align:left; padding:8px 0; border-top:1px solid var(--gray-light);
  font-size:15px; color:var(--dirt);
}
.lc-bench tr:first-child td{ border-top:0; }
/* Same first-column width as the batting order's jersey column, so the two
   halves of the card line up instead of nearly lining up. */
.lc-bench td:first-child{ width:4.8rem; }

/* Two players per row. The jersey columns are fixed so the two halves line
   up with each other and with the batting order's jersey column above;
   the name columns share what is left. */
.lc-bench-2col td.lc-bench-jersey{ width:3.2rem; }
.lc-bench-2col td.lc-bench-name{ width:auto; padding-right:14px; }
.lc-bench-2col td:first-child{ width:3.2rem; }
/* Below 30rem of card width two columns stop being readable, so the pairs
   stack back into one. Applied on the card, not the viewport: two cards side
   by side are each narrower than the page. */
@container (max-width: 30rem){
  .lc-bench-2col tr{ display:grid; grid-template-columns:3.2rem 1fr; }
}
.lc-bench-jersey{ font-family:var(--font-mono); }
.lc-bench-empty{ color:var(--gray); font-style:italic; }

/* --------------------------------------------------------------------------
   Bulk shift creation -- one fieldset per shift the schedule implies, each
   holding a tick, a label field and a time field
   (www/concessions/concessions/bulk-shifts.php).

   These rows started life in a .roster-table and the 1280px screenshot
   showed why they could not stay there: inside .tool-form's 32rem column,
   the browser's own <input type="time"> takes its intrinsic width first
   and the label input collapsed to one character, with the row overflowing
   its scroller. Same failure and same answer as the lineup editor above --
   the row wraps onto its own lines rather than shrinking its fields to
   nothing. The form widens past .tool-form's 32rem cap for the same reason
   .lineup-form does.

   Specificity note, exactly as for .lineup-dh-toggle: the shared
   `.tool-form label { display:grid; gap:6px; }` rule would stack the
   checkbox above its own text, so .shift-tick is qualified with
   .shift-derive-form to reach 0,2,1 and win regardless of source order.
   -------------------------------------------------------------------------- */
.shift-derive-form{ max-width:34rem; }
.shift-derive-form fieldset{ gap:10px; }
.shift-derive-form label.shift-tick{
  display:flex; align-items:center; gap:8px;
  font-family:var(--font-mono); font-size:13px; text-transform:none;
  color:var(--dirt); margin:0;
}
.shift-derive-form label.shift-tick input{ width:auto; margin:0; }

/* --------------------------------------------------------------------------
   Footer
   -------------------------------------------------------------------------- */
.site-footer{
  background:var(--chalk-card); padding:44px 24px 26px;
  border-top:1px solid var(--gray-light);
}
.footer-grid{
  max-width:1100px; margin:0 auto; display:grid;
  grid-template-columns:1fr; gap:28px;
}
@media(min-width:760px){ .footer-grid{ grid-template-columns:2fr 1fr 1fr; } }
.site-footer h4{
  font-family:var(--font-display); color:var(--dirt); font-size:19px; margin:0 0 10px;
}
.site-footer h4.spaced{ margin-top:18px; }
.site-footer p, .site-footer a{ font-size:13.5px; color:var(--gray); }
.site-footer a{ text-decoration:none; }
.site-footer a:hover{ color:var(--ignite); }
.site-footer ul{ list-style:none; margin:0; padding:0; }
.site-footer li{ margin-bottom:7px; }
.footer-bottom{
  max-width:1100px; margin:30px auto 0; padding-top:18px;
  border-top:1px solid var(--gray-light);
  font-family:var(--font-mono); font-size:11.5px; color:var(--gray);
  display:flex; justify-content:space-between; flex-wrap:wrap; gap:10px;
}

/* ==========================================================================
   DEV STATUS WIDGET — BEGIN
   Build-time-only scaffolding. See www/_dev_status.php for what renders
   this and the full removal steps. To remove: delete everything between
   this comment and the matching "DEV STATUS WIDGET — END" comment below,
   plus www/_dev_status.php, app/dev_status.php, tests/DevStatusTest.php,
   and the one call site in www/_footer.php, restoring the string
   "Built on the Sandlot Nights system" that the widget replaced.

   Deliberately utilitarian, not brand-styled: a diagonal hazard stripe and
   monospace data so it reads as scaffolding, never as real site content.
   All colours are brand variables per the rest of this file; --maroon is
   used only as a fill (bug-severity chip), never as text colour.
   ========================================================================== */
.rdc-dev-status{
  border-top:3px solid var(--gray-light);
  border-bottom:1px solid var(--gray-light);
  background:
    repeating-linear-gradient(135deg,
      var(--ignite) 0 10px, var(--chalk) 10px 20px) top left / 100% 4px no-repeat,
    var(--chalk-card);
}
.rdc-dev-status__block{ display:block; }
.rdc-dev-status__bar{
  display:flex; align-items:center; flex-wrap:wrap; gap:10px 18px;
  max-width:1100px; margin:0 auto; padding:10px 24px;
  font-family:var(--font-mono); font-size:12.5px; color:var(--dirt);
  cursor:pointer; list-style:none;
}
.rdc-dev-status__bar::-webkit-details-marker{ display:none; }
.rdc-dev-status__bar::marker{ content:""; }

.rdc-dev-status__tag{
  background:var(--ignite); color:var(--btn-dark-text);
  font-weight:700; letter-spacing:.04em;
  padding:2px 9px; border-radius:var(--radius-pill); white-space:nowrap;
}
.rdc-dev-status__metric,
.rdc-dev-status__now,
.rdc-dev-status__counts{
  color:var(--dirt);
}
.rdc-dev-status__now{ color:var(--gray); overflow-wrap:anywhere; }
.rdc-dev-status__toggle{
  margin-left:auto; color:var(--gray); white-space:nowrap;
}
.rdc-dev-status__toggle-open{ display:none; }
.rdc-dev-status__block[open] .rdc-dev-status__toggle-closed{ display:none; }
.rdc-dev-status__block[open] .rdc-dev-status__toggle-open{ display:inline; }

.rdc-dev-status__panel{
  max-width:1100px; margin:0 auto; padding:4px 24px 22px;
  font-family:var(--font-mono); font-size:13px; color:var(--dirt);
  border-top:1px dashed var(--gray-light);
}
.rdc-dev-status__panel p{ margin:6px 0; line-height:1.5; overflow-wrap:anywhere; }
.rdc-dev-status__meta{ color:var(--gray); font-size:11.5px; }

.rdc-dev-status__section{ margin-top:18px; }
.rdc-dev-status__section h3{
  font-family:var(--font-mono); font-size:11px; text-transform:uppercase;
  letter-spacing:.08em; color:var(--gray); margin:0 0 8px;
}

.rdc-dev-status__list{ list-style:none; margin:0; padding:0; }
.rdc-dev-status__list li{
  padding:8px 0; border-bottom:1px solid var(--gray-light);
}
.rdc-dev-status__list li:last-child{ border-bottom:none; }
.rdc-dev-status__note{ color:var(--gray); font-size:12px; }
.rdc-dev-status__progress{ color:var(--gray); margin-left:6px; }
.rdc-dev-status__severity{ color:var(--gray); font-weight:700; }

.rdc-dev-status__badge{
  display:inline-block; font-weight:700; padding:1px 7px;
  border-radius:var(--radius-sm); border:1px solid var(--gray-light);
  margin-right:6px; white-space:nowrap;
}
.rdc-dev-status__badge.is-good{ color:var(--ignite); border-color:var(--ignite); }
.rdc-dev-status__badge.is-warn{ color:var(--dirt); border-color:var(--gray); }
.rdc-dev-status__badge.is-muted{ color:var(--gray); }
.rdc-dev-status__badge.is-bad{
  background:var(--maroon); color:var(--dirt); border-color:var(--maroon);
}

/* --- Compact in-footer placement ---------------------------------------
   The widget lives in .footer-bottom, in the slot that held "Built on the
   Sandlot Nights system" (restore that string when removing this). That row
   is flex with space-between, so the widget lands right-justified opposite
   the copyright without needing a margin. Two consequences are deliberate:
     - the compact bar shows only the badge, the percentage and the two
       counts; the "now" sentence is verbose and moves into the panel.
     - the expanded panel opens UPWARD and is absolutely positioned. It is
       at the very bottom of the page, so a downward panel would open off
       the end of the document and force a scroll to read it.
   Still no JavaScript: this is a native <details> element. */
.rdc-dev-status{
  position:relative;
  flex:0 0 auto;                 /* .footer-bottom is space-between already */
  border:1px solid var(--gray-light);
  border-top:2px solid var(--ignite);
  border-radius:4px;
  background:var(--chalk-card);
  align-self:center;
}
.rdc-dev-status__bar{
  display:flex; align-items:center; gap:8px;
  max-width:none; margin:0; padding:4px 9px;
  font-family:var(--font-mono); font-size:11px; line-height:1.25;
  white-space:nowrap; color:var(--dirt);
}
/* Verbose in a banner-sized badge — it is still in the panel. */
.rdc-dev-status__bar .rdc-dev-status__now{ display:none; }
.rdc-dev-status__bar .rdc-dev-status__meta{ display:none; }
/* The full phase name is too long for a banner badge; it stays in the panel. */
.rdc-dev-status__bar .rdc-dev-status__phasename{ display:none; }
.rdc-dev-status__badge{ font-size:10px; padding:1px 5px; }
.rdc-dev-status__metric{ font-size:11px; }
.rdc-dev-status__counts{ font-size:11px; }

/* The panel hangs below the badge, anchored to the banner's right edge. */
.rdc-dev-status[open] > .rdc-dev-status__panel,
.rdc-dev-status__panel{
  position:absolute; bottom:calc(100% + 6px); right:0; z-index:60;
  width:min(760px, calc(100vw - 32px));
  max-height:min(70vh, 620px); overflow-y:auto;
  margin:0; padding:16px 18px;
  border:1px solid var(--gray-light);
  border-top:3px solid var(--ignite);
  border-radius:4px;
  background:var(--chalk-card);
  box-shadow:0 12px 32px var(--chalk);
  text-align:left;
}

/* Phones: the footer row wraps, so shrink the badge and let the panel use
   the full width. */
@media (max-width:760px){
  .rdc-dev-status{ order:3; }
  .rdc-dev-status__bar{ padding:3px 7px; gap:6px; }
  .rdc-dev-status__bar .rdc-dev-status__counts{ display:none; }
  .rdc-dev-status__panel{
    right:auto; left:0; width:calc(100vw - 24px);
  }
}

/* ==========================================================================
   DEV STATUS WIDGET — END
   ========================================================================== */

/* The unpublished-preview bar. Deliberately loud: a preview that could be
   mistaken for the live page is worse than no preview at all, because the
   whole point of unpublishing is being certain the public cannot see it. */
.unpublished-bar{
  background:var(--maroon); color:var(--dirt);
  font-family:var(--font-display); letter-spacing:0.06em;
  padding:10px 18px; text-align:center;
}
.unpublished-bar strong{ color:var(--ignite); }
/* The Home confirm tick-box, which sits inline beside its Unpublish button.
   Home is the one page whose removal takes the site's front door with it,
   so its control is deliberately two actions rather than one.

   Specificity note, and the THIRD instance of this same collision -- see
   .lineup-dh-toggle (line ~590) and .staff-tick (line ~913) for the first
   two. A bare `.confirm-inline` is 0,1,0 and LOSES to the shared
   `.tool-form label{ display:grid }` at 0,1,1 whatever the source order.
   The label then became a one-column grid, sandlot-nights.css's
   `input,textarea,select{ width:100% }` stretched the checkbox to the full
   421px column, and the control overflowed its 235px actions cell. So the
   selector below is qualified to 0,2,1 to win outright.

   white-space is reset because it is INHERITED here, not set here:
   `.roster-table td:last-child{ white-space:nowrap }` is right for a row of
   buttons and wrong for a sentence -- it pinned the column's min-content to
   the whole unwrapped sentence, which is what pushed it out of the cell.
   Nothing rescued that: the overflow is inside the cell, so .table-wrap's
   scroller never triggers and the text is simply clipped.

   text-align is reset for the same reason -- the cell is right-aligned for
   its buttons, which left the wrapped sentence ragged down its left edge. */
.tool-form label.confirm-inline{
  display:flex; align-items:flex-start; gap:8px;
  max-width:22ch; margin:0 0 6px;
  font-size:0.85rem; line-height:1.3;
  white-space:normal; text-align:left;
}
/* flex:none, or the checkbox is a flex item that the width:100% above still
   gets to shrink and grow from. */
.tool-form label.confirm-inline input[type="checkbox"]{
  width:auto; flex:none; margin:0;
}

/* --------------------------------------------------------------------------
   Admin sign-in
   One card on the chalk ground, carrying the same maroon top-rule as every
   other .card on the site -- no hero band. A band is a page-opening device,
   and this page is a single object with one control on it; wrapping it in
   full-width chrome made a one-field form look like a section header with
   an input fallen underneath.
   -------------------------------------------------------------------------- */
.signin{
  display:grid; place-items:center;
  padding:clamp(32px,8vh,88px) 24px;
}
.signin-card{
  width:100%; max-width:26rem;
}
.signin-h{
  font-size:clamp(26px,4vw,34px);
  margin:0;
}
.signin-lede{
  margin:10px 0 22px;
  color:var(--gray);
  font-size:14.5px;
}
/* The form's own label supplies the field's spacing (.tool-form label is a
   6px grid), so the form only has to space label from button. */
.signin-form{ gap:20px; }
.signin-form button{ width:100%; }
/* The sign-in CTA is an <a class="btn">, not a <button>, because it
   navigates to Google rather than submitting anything. The locked brand
   stylesheet's .btn sets no text-decoration, so an anchor button keeps the
   browser's default underline and stops looking like every other button on
   the site -- the same trap already documented on
   .roster-filters .row-actions .btn. Scoped here for the same reason it was
   scoped there: every a.btn on the site has this problem, and fixing all of
   them is a site-wide visual change that is not what was asked for. */
.signin-card .btn{ text-decoration:none; }

/* Errors state what happened and what to do, so they read as direction
   rather than alarm: a left rule in gold, not a red panel. Gold is the
   accent this site already uses for anything that wants attention. */
.signin-error{
  margin:0 0 18px;
  padding:10px 14px;
  border-left:3px solid var(--ignite);
  background:var(--chalk);
  border-radius:var(--radius-sm);
  font-size:14px;
  color:var(--dirt);
}
.signin-foot{
  margin:22px 0 0; padding-top:16px;
  border-top:1px solid var(--gray-light);
  font-size:13px; color:var(--gray);
}
/* The focus ring the brand's border-colour change alone does not give a
   keyboard user on a dark field. */
.signin-form input:focus-visible{
  outline:2px solid var(--ignite); outline-offset:2px;
}

/* --------------------------------------------------------------------------
   Admin file editor (build-time tooling, prod-sim only)
   Deliberately plain: this is a tool for one operator during deployment
   trials, not part of the public brand surface.
   -------------------------------------------------------------------------- */
.editor-area{
  width:100%; font-family:var(--font-mono); font-size:13px; line-height:1.45;
  white-space:pre; overflow-wrap:normal; overflow-x:auto; tab-size:4;
}
.editor-actions{ display:flex; gap:10px; }
.editor-run{ margin-bottom:22px; }
/* A visible edge, or a contenteditable region is indistinguishable from the
   page around it and people type into the wrong place. */
.editor-rich{
  border:1px dashed var(--gray-light); border-radius:var(--radius-sm);
  padding:12px 14px; min-height:3em;
}
.editor-rich:focus{ outline:2px solid var(--ignite); outline-offset:2px; }
.editor-readonly{
  border-left:3px solid var(--gray-light); padding:8px 12px; margin:0;
  font-family:var(--font-mono); font-size:12px; overflow-x:auto;
  color:var(--gray);
}
