/* app_shell.css — webapp-specific layout primitives.
   Pairs with colors_and_type.css + df.css. Not part of the design system
   itself; this file sits alongside the vendored brand files and composes
   them into the FastAPI/Jinja/HTMX webapp shell. Hand-edit freely. */

:root {
  --sidebar-w: 232px;
}

/* The `color-scheme` block that used to live here shipped upstream in v0.4 and
   is deleted, as its own note said it should be. Do not re-add it: it would do
   real harm now, not just duplicate.

   v0.4 declares `[data-theme="auto"] { color-scheme: light dark; }`, which is
   what makes light-dark() resolve per the OS with no script at all. The local
   block had no `auto` branch — it did not need one, because the resolver
   rewrites the attribute before first paint — but it did carry
   `:root { color-scheme: light }`. app_shell.css loads last and both selectors
   are (0,1,0), so that `:root` rule would out-order the upstream `auto` rule and
   pin light. The one case the new CSS exists to handle is exactly the one the
   stale local copy would break. */

/* ===== body / global ===== */
html, body { margin: 0; padding: 0; }
body.df-page {
  min-height: 100vh;
}
a { color: var(--link); text-decoration: none; }
a:hover { color: var(--link-hover); }

/* The vendored df.css sizes .df-input/.df-select/.df-textarea as
   width:100% + padding, which assumes a border-box world this shell
   never establishes. Native <select> is border-box per the UA sheet,
   but <input>/<textarea> default to content-box, so every full-width
   text/number input overflowed its parent by its own padding (draft
   "Total rounds", trade-name field, all wizard scoring inputs). */
.df-input, .df-select, .df-textarea { box-sizing: border-box; }

/* ===== shell layout ===== */
.df-app-shell {
  display: grid;
  grid-template-columns: var(--sidebar-w) 1fr;
  min-height: 100vh;
}

/* ===== sidebar (desktop) ===== */
.df-app-sidebar {
  background: var(--paper);
  border-right: 1px solid var(--rule);
  display: flex;
  flex-direction: column;
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
}
.df-app-sidebar-brand {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 18px 20px;
  border-bottom: 1px solid var(--rule);
  text-decoration: none;
  color: var(--fg);
}
.df-app-sidebar-brand img { border-radius: 3px; }
.df-app-sidebar-brand .df-wordmark {
  font-family: var(--font-display);
  font-size: 15px;
  letter-spacing: -0.015em;
  line-height: 1;
}
.df-wordmark b { font-weight: 700; color: var(--fg); }
.df-wordmark i { font-weight: 400; font-style: italic; color: var(--pigskin); }

.df-app-sidebar-section {
  font-family: var(--font-ui);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg-subtle);
  padding: 14px 20px 6px;
}

.df-app-sidebar-nav {
  list-style: none;
  padding: 4px 8px;
  margin: 0;
  flex: 1;
}
.df-app-sidebar-nav li { margin: 0; }
.df-app-sidebar-nav a {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 12px;
  border-radius: var(--r-md);
  color: var(--fg-muted);
  font-family: var(--font-ui);
  font-size: 14px;
  font-weight: 500;
  text-decoration: none;
  transition: background-color var(--dur-fast) var(--ease-standard),
              color var(--dur-fast) var(--ease-standard);
}
.df-app-sidebar-nav a:hover { background: var(--paper-3); color: var(--fg); }
.df-app-sidebar-nav a[aria-current="page"] {
  background: var(--field-soft);
  color: var(--fg);
  font-weight: 600;
}

.df-app-sidebar-footer {
  padding: 14px 20px;
  border-top: 1px solid var(--rule);
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--fg-subtle);
}

/* ===== main column ===== */
.df-app-main {
  display: flex;
  flex-direction: column;
  min-width: 0;          /* lets table-overflow scroll work in grid child */
}

/* topbar (sticky) */
.df-app-topbar {
  position: sticky;
  top: 0;
  z-index: 30;
  background: var(--paper);
  border-bottom: 1px solid var(--rule);
  display: flex;
  align-items: center;
  gap: var(--s-4);
  padding: 14px 28px;
  flex-wrap: wrap;
}
.df-app-topbar-title {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 18px;
  color: var(--fg);
  letter-spacing: -0.01em;
  flex: 1;
  min-width: 0;
}
.df-app-topbar-right {
  margin-left: auto;
  display: flex;
  gap: var(--s-2);
  align-items: center;
  flex-wrap: wrap;
  justify-content: flex-end;
}
/* The email span, Sign out button and week pill fought for the same row at
   375px, squeezing the pill's flex box until its own text wrapped ("Week" /
   "1" on two lines — QA 2026-08-26 §3). `.df-pill` now refuses to wrap its
   own text (design-system/css/df.css) and neither of these two shrinks, so
   the row wraps as a whole (email above, controls below) instead of
   squeezing any one piece illegibly. */
.df-app-topbar-right [data-testid="viewer-email"] {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
}
.df-app-topbar-right form,
.df-app-topbar-right .df-pill {
  flex-shrink: 0;
}

/* content body */
.df-app-content {
  flex: 1;
  padding: var(--s-6) 28px var(--s-7);
}

/* The two banners base.html renders ITSELF sit above content it does not own,
   so nothing between them and the page supplies separation. .df-banner carries
   padding and radius and no margin by design -- an in-page banner is spaced by
   the flow that authored it -- and these two have no such flow: whether they
   look attached or crashed into the page is a property of whatever element
   happens to follow. Measured on /audit 2026-08-29: banner bottom 150.69, the
   next element's top 150.69, a 0px gap that reads as a collision (QA §3.2).
   Scoped to the seam-rendered pair rather than to .df-banner, which would move
   every in-page banner on the site. */
.df-app-content > [data-testid="page-slate-notice"],
.df-app-content > [data-testid="board-provenance-notice"] {
  margin-bottom: var(--s-5);
}

/* footer (legal / cookie consent / contact) */
.df-app-footer {
  border-top: 1px solid var(--rule-2);
  padding: var(--s-4) 28px;
  font-family: var(--font-ui);
  font-size: 12px;
  color: var(--fg-subtle);
}
.df-app-footer ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 16px 18px;
  align-items: center;
}
.df-app-footer a { color: var(--fg-subtle); text-decoration: none; }
.df-app-footer a:hover { color: var(--fg); }

/* Theme picker sits at the end of the footer list, pushed right on wide
   viewports and falling back into the normal wrap on narrow ones. */
.df-theme-picker {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
}
.df-theme-picker label {
  font-size: 12px;
  color: var(--fg-subtle);
}
@media (max-width: 700px) {
  .df-theme-picker { margin-left: 0; }
}

/* ===== mobile drawer (CSS-only) ===== */
.df-drawer-toggle { display: none; }
.df-drawer-trigger {
  display: none;
  border: 0;
  background: transparent;
  /* 22px icon + 11px padding = 44px tap box (iOS HIG floor). Negative
     margins keep the icon's visual position and the topbar's flow
     height the same as the old 6px-padding box. */
  padding: 11px;
  margin: -5px 3px -5px -5px;
  cursor: pointer;
  color: var(--fg);
}
.df-drawer-trigger svg { display: block; width: 22px; height: 22px; }
.df-drawer-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(13, 59, 31, 0.35);
  z-index: 40;
}

@media (max-width: 960px) {
  .df-app-shell { grid-template-columns: 1fr; }
  .df-app-sidebar {
    position: fixed;
    top: 0; left: 0;
    width: var(--sidebar-w);
    transform: translateX(-100%);
    transition: transform var(--dur-base) var(--ease-standard);
    z-index: 50;
    box-shadow: var(--shadow-md);
  }
  .df-drawer-toggle:checked ~ .df-app-shell .df-app-sidebar {
    transform: translateX(0);
  }
  .df-drawer-toggle:checked ~ .df-app-shell .df-drawer-overlay {
    display: block;
  }
  .df-drawer-trigger { display: inline-flex; }
  /* Drawer is the primary nav at these widths — 44px rows. */
  .df-app-sidebar-nav a { padding: 12px; }
}

/* ===== main + aside split =====
   Asymmetric two-column content layout (wide main + ~280px sidebar) that
   stacks to a single column on mobile. Used by the draft board (player pool +
   pick log) and the waiver player pool (add/drop + sidebar).

   `grid-template-columns` MUST live here, not inline on the element: an inline
   style out-specifies a stylesheet rule, so a `@media` override can only win if
   the base value is also in the stylesheet. Page-specific `gap` stays inline.
   720px = the design-system mobile breakpoint (see df-mobile.css). Without the
   stack, the 280px aside floor crushes the main column to ~40px at 375px. */
.df-split-main-aside {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(280px, 1fr);
}
@media (max-width: 720px) {
  /* minmax(0, 1fr), not bare 1fr: a bare 1fr track's min size is the
     content's min-content, so a wide child table (draft-board player
     pool) inflates the track past the container and the whole page
     scrolls sideways — which also breaks the sticky topbar on iOS
     Safari. Zeroing the track minimum keeps the page at viewport
     width; wide tables scroll inside their own overflow-x wrapper. */
  .df-split-main-aside { grid-template-columns: minmax(0, 1fr); }
}

/* ===== wrapping tab filter (.df-tabs-wrap) =====
   The vendored .df-tabs is a single non-wrapping segmented control
   (display: inline-flex; width: fit-content). That's right for 2-3 tabs,
   but the Rankings position filter has 9 pills (QB…DB) — on a phone that
   row is wider than the viewport, so the whole page scrolls sideways:
   player names clip on the left, a band of dead space opens to the right
   of the table, and the body's horizontal overflow breaks the sticky
   topbar on iOS Safari. The .df-tabs-wrap modifier lets the filter wrap
   onto multiple lines on mobile so every position stays visible and the
   page never exceeds the viewport width. Desktop (>720px) is untouched. */
@media (max-width: 720px) {
  .df-tabs-wrap {
    display: flex;
    flex-wrap: wrap;
    width: auto;
  }
}

/* ===== controls carrying a USER-SUPPLIED name (mobile-Safari QA 2026-09-05)
   `.df-tabs-wrap` above lets a tab ROW wrap BETWEEN its items. That is not
   enough when one item's own text is a league name: flex-wrap has no wrap
   opportunity inside a single unbreakable token, so the item stays at its
   min-content width and the DOCUMENT goes wider than the viewport — which is
   the same whole-page sideways scroll the league-name <h1> had before #1544,
   arriving through a control instead of a heading.

   Measured in mobile Safari 17.2 on an iPhone SE at 375 against an 84-char
   league name whose longest token is 51 chars: the player page's league tab
   took the document to 389, and the Kicker Streamer banner's "League -> Team"
   button took it to 863. Renaming the league to "Short League" returned both
   to 375, so the name is the whole cause. The kicker button ALSO overflowed by
   38px at an ordinary league name, because it concatenates league AND team.

   Both selectors are deliberately narrow. `.df-btn` carries
   `white-space: nowrap` from the vendored df.css for good reason — a button
   label is normally short and fixed — so this relaxes it only inside
   `.df-banner`, where the label is user data. Neither rule moves anything that
   already fits, and desktop (>720px) is untouched.

   NOT fixed here, deliberately: /draft's league <select> takes the document to
   797 at the same name. No element reports an overflowing rect there — WebKit
   sizes the select's scroll extent from its widest <option> even though
   `.df-select` is already `width: 100%` — so `max-width: 100%` does nothing,
   and the only rule measured to fix it was `overflow-x: hidden` on `.df-card`,
   which would silently clip every card that legitimately scrolls. Operator
   action 392. */
@media (max-width: 720px) {
  .df-tabs-wrap .df-tab {
    min-width: 0;
    overflow-wrap: anywhere;
  }
  .df-banner .df-btn {
    min-width: 0;
    white-space: normal;
    overflow-wrap: anywhere;
  }
}

/* ===== compact player rows (trade builder rosters + FA pool) =====
   Row anatomy: [pos pill | name | team | status pills] [num | PI bar | +].
   The right cluster is fixed-width (40px num + 6rem bar + button); at
   375px inside a padded card the leftover is thinner than the pill
   cluster, so the ellipsizing name span collapsed to ~1 letter and the
   non-shrinkable status pills painted over the projection number. On
   phones: drop the PI bar — the sparkline, and ONLY the sparkline — and
   let the lead cluster wrap.

   Operator action 358 changed what this rule costs, and the selector is
   load-bearing for that. `.df-pi-bar` is now the track alone; the two
   endpoints live in a sibling `.df-pi-values` span inside the `.df-pi`
   wrapper. So the phone keeps the interval's NUMBERS (61px of mono) and
   loses the 96px shared-axis comparison, which is a desktop reading
   anyway. Do NOT widen this to `.df-pi` — that restores exactly the
   defect 358 was filed for, on the surface with the least room to
   recover it. (This comment used to call the bar a "tooltip-only
   sparkline" and cite the dashboard's .df-hide-on-mobile and the lineup
   table's data-priority="3" as the same call. It was an accurate
   description of a defect: on all three, the interval's values existed
   only in a `title=`.) */
.trade-add-btn { padding: 2px 8px; min-width: 0; }
@media (max-width: 720px) {
  .trade-player-row .df-pi-bar { display: none; }
  .trade-player-lead { flex-wrap: wrap; }
  .trade-add-btn { min-width: 44px; padding: 6px 10px; }
}

/* ===== position-filter chips (waiver wire) =====
   Inline-styled 27px-tall <a role="tab"> chips; padding lives here so
   the mobile block can win. Colors stay inline (state-driven). */
.df-filter-chip { box-sizing: border-box; padding: 4px 12px; }
@media (max-width: 720px) {
  .df-filter-chip {
    display: inline-flex;
    align-items: center;
    min-height: 40px;
    padding: 8px 14px;
  }
}

/* ===== <summary> disclosure rows (triage "N clear calls") ===== */
.df-disclosure-summary {
  cursor: pointer;
  font-size: 12px;
  color: var(--fg-subtle);
}
@media (max-width: 720px) {
  .df-disclosure-summary { padding: 14px 0; }
}

/* ===== radio / checkbox label rows (league wizard) ===== */
.df-choice-row {
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 14px;
}
@media (max-width: 720px) {
  .df-choice-row { min-height: 44px; padding: 2px 0; }
  .df-choice-row input[type="radio"],
  .df-choice-row input[type="checkbox"] { width: 18px; height: 18px; }
}

/* ===== label + control pairs in filter bars =====
   Keeps "Sort:"/"Tier:" glued to their <select> when the bar wraps —
   without this the bar wraps between a label and its control. */
.df-filter-group {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

/* The AA stopgap for .df-banner-warn / .df-banner-pos that used to live here
   was deleted once v0.3 fixed the ramp upstream, exactly as its own comment
   said it should be. --warn #B8860B -> #8A6508 (2.78:1 -> 4.55:1 on
   --warn-soft) and --pos #2E7D4F -> #2B7449 (4.09:1 -> 4.61:1 on --pos-soft)
   both clear AA at the token level, so df.css's `color: var(--warn)` needs no
   help and every banner inherits the fix globally rather than by class.

   Do not restore it. Beyond being redundant it pinned a stale #876308 that
   shadowed the corrected --warn (app_shell.css loads last, at equal
   specificity), and its `@media (prefers-color-scheme: dark)
   { [data-theme="auto"] }` branch is the pattern the design system
   deliberately removed — data-theme is resolved before first paint now, so
   that selector matches nothing anyway, and
   test_no_css_still_defines_the_deleted_auto_branch fails on it. */

/* ===== wizard stepper + pagination — mostly retired =====
   Both were built here as upstream candidates and v0.3.2b took them into
   df.css, credited. The local copies of .df-pagination, .df-wizard-steps,
   .df-wizard-step, .df-wizard-step-dot and .df-wizard-step-active are
   deleted: app_shell.css loads last, so keeping them would shadow the
   canonical rules and pin raw literals over the tokens the design system
   now uses (4px over var(--s-1), 999px over var(--r-pill), and so on).
   Verified every one of those rewrites resolves identically in both
   themes before deleting — --fg==--ink, --fg-subtle==--ink-4,
   --border==--rule, --bg-sunken==--paper-3, --fg-on-dark==--paper-2.

   Two things df.css does not cover, so they stay here.

   .df-wizard-step-label has no upstream rule at all; leagues/setup.html
   uses it, so deleting the block wholesale would silently drop the
   label weight.

   The dot's transition used to live here too, as an additive rule. v0.3.3
   ships it in df.css, so it is gone from here — one local rule, retired
   the moment upstream covered it. */
.df-wizard-step-label { font-weight: 500; }

/* ===== [hidden] must actually hide =====
   Production, 2026-08-26, first live subscription: /billing rendered a bare
   amber bar above the subscription card. It was #checkout-error -- the
   htmx-failure banner, which carries the `hidden` attribute and is empty
   until JS fills it in. `hidden` maps to `display: none` in the UA
   stylesheet ONLY, and ANY author rule setting `display` outranks the whole
   UA sheet. df.css's `.df-banner { display: flex }` does exactly that, so
   the attribute was inert and the element rendered at full padding with no
   text in it.

   The reset is written for the class of bug, not the one element: every
   utility that sets `display` defeats `hidden` the same way, and the next
   one will be written by someone who has no reason to know that. `!important`
   is load-bearing -- plain `display: none` here would win against .df-banner
   only by source order, and lose to any higher-specificity or inline
   `display`. Nothing legitimately wants a `hidden` element painted; the JS
   that reveals the banner clears the attribute (`box.hidden = false`) rather
   than fighting the cascade.

   Guarded by packages/webapp/tests/test_hidden_attribute_is_not_defeated.py,
   which resolves the real stylesheets in <link> order against every bare
   `hidden` element in the templates. */
[hidden] { display: none !important; }
