/* ===== specials_analytics_html (HTML form, scoped) =====
   The Specials Analytics shell's styling. Loaded eagerly from
   anvil.yaml native_deps.head_html, which is what styles the designer canvas
   as well as the running app.

   THIS FILE SORTS LAST AMONG THE SIX SPECIALS LINKS - "_" (0x5F) sorts before
   "e" (0x65), so specials.css follows every sp_*.css.

   THAT ORDER WAS CHECKED RATHER THAN ASSUMED. This file declares
   .sp-dashboard, .sp-content, #sp-panels, .sp-page-header, .sp-title,
   .sp-subtitle, .sp-freshness, .sp-table-host, .sp-pill*, .sp-state*,
   .sp-retry*, the tone classes and the two @container blocks. The five child
   assets declare .sp-filters*, .sp-rebate*, .sp-group, .sp-flow, .sp-stage*,
   .sp-arrow, .sp-tiles, .sp-tile*, .sp-ledger*, .sp-row-nodata, .sp-modal*,
   .sp-batch*, .sp-unlinked* and .sp-breakdown*. No selector is declared on
   both sides, so no child rule changes which shell declaration wins, and vice
   versa. The one near-miss is the month modal's body, which carries both
   .sp-modal-body (ledger asset: padding, overflow-y) and .sp-table-host (this
   file: container, overflow-x) - no property appears in both.

   SCOPING, ONE RULE, NO CASE-BY-CASE JUDGEMENT: every selector that already
   begins .sp-dashboard stays exactly as written, and every other selector is
   wrapped :where(.sp-dashboard), which adds zero specificity. The rules kept
   bare are beating theme.css and the dependency's runtime-injected sheet on
   specificity, and a head link can only ever win those on specificity: the
   injected <style> is appended to <head> at startup, i.e. after every head
   link. .sp-dashboard .px-card-header { margin: 0 } beats the injected
   .px-card-header; the .px-table th/td rules beat theme.css's; the tone
   classes beat .sp-stage-value and .px-kpi-value on the very element they land
   on; the two @container blocks must keep beating the size rules above them.

   ROOT RULE, BARE CLASS, ON ITS OWN LINE, so it outranks the :where()-scoped
   rules and so the dark restatement stays readable as
   [data-theme="dark"] .sp-dashboard {.

   :root AND [data-theme="dark"] ARE MERGED INTO IT. The six --px-spec-* tokens
   move onto .sp-dashboard and the six dark ones onto the existing
   [data-theme="dark"] .sp-dashboard rule. Every child root is a DOM descendant
   of .sp-dashboard - including the position: fixed month modal - so the tokens
   still reach .sp-batch-on-time, .sp-stage:last-child and
   .sp-ledger-row:focus-visible td by inheritance. Specificity is unchanged in
   relative terms: [data-theme="dark"] .sp-dashboard (0,2,0) beats
   .sp-dashboard (0,1,0), where today [data-theme="dark"] beat :root on order.

   min-height: calc(100dvh - 16px) IS KEPT BYTE FOR BYTE, and the root carries
   no overflow, no height and no position: fixed.

   NO DESIGNER GATE IS NEEDED. Two rules hide content until JavaScript runs.
   .sp-state { display: none } hides the two empty-state blocks on the canvas
   as at runtime. .px-section-reveal { opacity: 0 }
   lives in the dependency's runtime-injected sheet, which
   ensure_theme_injected() appends at startup and which is therefore absent
   from the designer canvas entirely.

   NO @keyframes, NO url() AND NO @font-face in this file or in the five child
   assets, so there is nothing to prefix, rewrite or drop. */

/* =============================================
   SPECIALS ANALYTICS — page tokens
   Declared locally, in the --px-* namespace the estate reads. The
   management accent and the "good" green are the only colours this page
   invents: warn, bad and info are the estate's own --px-warning /
   --px-danger / --px-info, and everything else is a dependency token.
   ============================================= */
.sp-dashboard, .sp-dashboard *, .sp-dashboard *::before, .sp-dashboard *::after {
  box-sizing: border-box;
}

/* The shell paints the page background. min-height (never a fixed
   height) so the dep layout keeps ownership of scrolling.
   The paper texture and the position lift that keeps content above it
   come from the shared .px-noise class, which already excludes the
   loading overlay (theme.css) — hand-rolling either is how the estate
   ended up with three copies of the same twelve declarations.
   --accent is the estate's hook: the shared cards, pills and inputs all
   read it, so naming it here is what makes them wear this page's
   orange rather than the default cyan. */
.sp-dashboard {
  --px-spec-accent: #f68b1f;
  --px-spec-good-faint: #047857;
  --px-spec-good-soft: rgba(5, 150, 105, 0.10);
  --px-spec-warn-soft: rgba(217, 119, 6, 0.12);
  --px-spec-bad-soft: rgba(220, 38, 38, 0.10);
  --accent: var(--px-spec-accent);
  --accent-text: var(--px-ink-on-bright);
  --accent-subtle: rgba(246, 139, 31, 0.12);
  font-family: var(--px-font);
  min-height: calc(100dvh - 16px);
  background:
    radial-gradient(circle at top left, rgba(246, 139, 31, 0.08), transparent 40%),
    linear-gradient(180deg, #fff7ed 0%, #f1f5f9 100%);
  color: var(--px-text-primary);
  isolation: isolate;
  z-index: 0;
  /* The root IS the flex item of the layout's scroller, .px-nav-content.
     position: relative restates
     what .px-noise already gives it (theme.css) and is stated anyway, so this
     reads as a complete root rule. No overflow, no height and no
     position: fixed - any of them stops the port scrolling. */
  position: relative;
  flex: 1 0 auto;
}

/* Screen only, so printing while dark keeps the light page palette. */
@media not print {
  [data-theme="dark"] .sp-dashboard {
    --px-spec-accent: #f9a94f;
    --px-spec-good-faint: #6ee7b7;
    --px-spec-good-soft: rgba(52, 211, 153, 0.12);
    --px-spec-warn-soft: rgba(251, 191, 36, 0.14);
    --px-spec-bad-soft: rgba(255, 107, 107, 0.12);
    --accent-subtle: rgba(249, 169, 79, 0.16);
    background:
      radial-gradient(circle at top left, rgba(249, 169, 79, 0.12), transparent 40%),
      linear-gradient(180deg, #1c1206 0%, #0f172a 100%);
  }
}

:where(.sp-dashboard) .sp-content {
  padding: 1.5rem;
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
}

/* The state switcher toggles #sp-panels between '' and 'none', so the
   stylesheet owns its resting display — and it must repeat the column
   gap because its children are the sections, not .sp-content's. */
:where(.sp-dashboard) #sp-panels {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
}

/* The page header is plain rather than a card: the flow band under it is
   the first thing the reader is meant to look at. Its sizes match
   gp_header_html's — pinned by the screen's contract test. */
:where(.sp-dashboard) .sp-page-header { padding: 0 0.25rem; }

:where(.sp-dashboard) .sp-title {
  font-size: 1.25rem;
  font-weight: 700;
  margin: 0;
  color: var(--px-text-primary);
}
:where(.sp-dashboard) .sp-subtitle {
  font-size: 0.75rem;
  color: var(--px-text-muted);
  margin-top: 0.125rem;
}
:where(.sp-dashboard) .sp-freshness {
  font-size: 0.72rem;
  color: var(--px-text-muted);
  margin-top: 0.35rem;
}

/* =============================================
   PANELS AND TABLES — the shared .px-card / .px-table look,
   with only the handful of things this page's tables do differently.
   ============================================= */
/* The panel titles are headings, and a heading arrives with margins the
   shared header does not expect. */
.sp-dashboard .px-card-header { margin: 0; }

/* Tighter cell padding and wrapping headers keep the desktop tables
   readable before their rows switch to cards. */
.sp-dashboard .px-table th,
.sp-dashboard .px-table td { padding: 0.6rem 0.55rem; }
.sp-dashboard .px-table th { white-space: normal; }
/* Headers may wrap; money figures may not. */
.sp-dashboard .px-table td.num { white-space: nowrap; }

/* EVERY TABLE ON THIS PAGE SIZES ITSELF AGAINST ITS OWN WRAP. The wrap
   names itself the `sp-table` query container, and each table switches
   all of its columns into cards at its own width.

   overflow-x is hidden rather than left to scroll, as the gross-profit
   monthly table does: those rules are sized so that nothing is ever
   actually cut off, and a scrollbar inside a card is the fault this
   replaces. */
:where(.sp-dashboard) .sp-table-host {
  container: sp-table / inline-size;
  overflow-x: hidden;
}

.sp-dashboard .px-table tfoot tr {
  border-top: 2px solid var(--px-border);
}
.sp-dashboard .px-table tfoot td {
  border-bottom: none;
  border-top: none;
  font-weight: 700;
  color: var(--px-text-primary);
}

/* Scoped to the wrapper, not bare, so they out-specify the component
   rules that paint a neutral colour on the very element they land on:
   a bare .sp-good ties with .sp-stage-value and .px-kpi-value and then
   loses on source order, which is how the Profit headline stayed grey.
   Every slot lives inside .sp-dashboard, and the ledger's no-data rows
   are a class deeper still, so they keep overriding these. */
.sp-dashboard .sp-good { color: var(--px-success-ink); }
.sp-dashboard .sp-good-faint { color: var(--px-spec-good-faint); }
.sp-dashboard .sp-warn { color: var(--px-warning-ink); }
.sp-dashboard .sp-bad { color: var(--px-danger); }
.sp-dashboard .sp-muted { color: var(--px-text-muted); }
.sp-dashboard .sp-forecast { white-space: nowrap; }
.sp-dashboard .sp-spread {
  color: var(--px-text-muted);
  cursor: help;
}

:where(.sp-dashboard) .sp-pill {
  display: inline-block;
  border-radius: 999px;
  padding: 0.15rem 0.6rem;
  margin: 0.1rem 0.25rem 0.1rem 0;
  font-size: 0.7rem;
  font-weight: 700;
  white-space: nowrap;
}
:where(.sp-dashboard) .sp-pill-bad { background: var(--px-spec-bad-soft); color: var(--px-danger); }
:where(.sp-dashboard) .sp-pill-warn { background: var(--px-spec-warn-soft); color: var(--px-warning-text); }
:where(.sp-dashboard) .sp-pill-good { background: var(--px-spec-good-soft); color: var(--px-success-text); }
/* Neither good nor bad: a fact the reader needs in order to read the
   other pills correctly. */
:where(.sp-dashboard) .sp-pill-muted { background: var(--px-bg-elevated); color: var(--px-text-secondary); }

/* =============================================
   EMPTY / ERROR STATES
   ============================================= */
:where(.sp-dashboard) .sp-state {
  display: none;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
  text-align: center;
  padding: 3rem 1.5rem;
}
:where(.sp-dashboard) .sp-state.sp-state-on { display: flex; }
:where(.sp-dashboard) .sp-state .px-empty-icon { font-size: 2.2rem; }
:where(.sp-dashboard) .sp-state .px-empty-text {
  font-size: 0.9rem;
  color: var(--px-text-secondary);
  max-width: 34rem;
  line-height: 1.6;
}
:where(.sp-dashboard) .sp-retry {
  font-family: var(--px-font);
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--px-ink-on-bright);
  background: var(--px-spec-accent);
  border: none;
  border-radius: 10px;
  padding: 0.7rem 1.6rem;
  min-height: 44px;
  cursor: pointer;
}
:where(.sp-dashboard) .sp-retry:hover { filter: brightness(1.08); }
:where(.sp-dashboard) .sp-retry:focus-visible {
  outline: 2px solid var(--px-focus-ring);
  outline-offset: 2px;
}

/* =============================================
   RESPONSIVE — the page is used on phones (explicit requirement).
   Tiles and the flow band stack, tables fall back to cards, and every
   tap target keeps a 44px minimum.
   ============================================= */
@media (max-width: 900px) {
  :where(.sp-dashboard) .sp-content { padding: 1rem; gap: 1rem; }
  :where(.sp-dashboard) #sp-panels { gap: 1rem; }
  .sp-dashboard .px-card-body { padding: 1rem; }
}

@media (max-width: 640px) {
  :where(.sp-dashboard) .sp-content { padding: 0.65rem; }
  :where(.sp-dashboard) .sp-title { font-size: 1.02rem; }
  .sp-dashboard .px-card-header { padding: 0.75rem 0.85rem; }
  .sp-dashboard .px-card-body { padding: 0.85rem; }
  .sp-dashboard .px-table { font-size: 0.78rem; }
  .sp-dashboard .px-table th { padding: 0.45rem 0.4rem; font-size: 0.58rem; }
  .sp-dashboard .px-table td { padding: 0.45rem 0.4rem; }
}

/* CARD COLLAPSE: each row becomes a stacked block whose cells carry
   their own header text, so no horizontal scrubbing is needed.

   Keyed to the TABLE'S OWN WIDTH, not the window's. Every table on
   this page sits in a wrap that names itself the `sp-table` query
   container, so the same rules serve the ledger, the two profit
   tables, the possibly-missed list and the month modal, each answering
   for the width it actually got. A window query would get this wrong
   the moment the nav drawer is open: a 1200px window with a drawer
   leaves the ledger about 700px, which is card territory while the
   window still reads as a desktop.

   The widest table carries eleven columns, so the card layout starts
   before those columns stop fitting. Every column stays present; there
   is no narrower intermediate table with selected fields removed.

   Scoped the same depth as the size rules above, so these keep
   winning. */
/* Identical twin of the 860px card block; edit both together. */
@container sp-table (max-width: 1300px) {
  .sp-dashboard .sp-wide-table-host .sp-cards { table-layout: auto; }
  .sp-dashboard .sp-wide-table-host .sp-cards thead { display: none; }
  .sp-dashboard .sp-wide-table-host .sp-cards tbody tr,
  .sp-dashboard .sp-wide-table-host .sp-cards tfoot tr {
    display: block;
    margin-bottom: 0.75rem;
    padding: 0.75rem;
    background: var(--px-bg-elevated);
    border: 1px solid var(--px-border);
    border-radius: 10px;
  }
  .sp-dashboard .sp-wide-table-host .sp-cards tfoot tr {
    border-top: 2px solid var(--px-border);
  }
  /* Attention pills must wrap so they cannot widen every card. */
  .sp-dashboard .sp-wide-table-host .sp-cards tbody td,
  .sp-dashboard .sp-wide-table-host .sp-cards tfoot td {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
    gap: 0.75rem;
    padding: 0.25rem 0;
    border-bottom: none;
    border-top: none;
    text-align: right;
  }
  .sp-dashboard .sp-wide-table-host .sp-cards td:not(:first-child)::before {
    content: attr(data-label);
    font-size: 0.6rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--px-text-muted);
    text-align: left;
    flex: 0 0 auto;
  }
  .sp-dashboard .sp-wide-table-host .sp-cards td:first-child {
    display: block;
    padding-bottom: 0.4rem;
    margin-bottom: 0.25rem;
    border-bottom: 1px solid var(--px-border);
    font-weight: 700;
    color: var(--px-text-primary);
    text-align: left;
  }
}

/* Identical twin of the 1300px card block; edit both together. */
@container sp-table (max-width: 860px) {
  .sp-dashboard .sp-cards { table-layout: auto; }
  .sp-dashboard .sp-cards thead { display: none; }
  .sp-dashboard .sp-cards tbody tr,
  .sp-dashboard .sp-cards tfoot tr {
    display: block;
    margin-bottom: 0.75rem;
    padding: 0.75rem;
    background: var(--px-bg-elevated);
    border: 1px solid var(--px-border);
    border-radius: 10px;
  }
  .sp-dashboard .sp-cards tfoot tr {
    border-top: 2px solid var(--px-border);
  }
  /* Attention pills must wrap so they cannot widen every card. */
  .sp-dashboard .sp-cards tbody td,
  .sp-dashboard .sp-cards tfoot td {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
    gap: 0.75rem;
    padding: 0.25rem 0;
    border-bottom: none;
    border-top: none;
    text-align: right;
  }
  .sp-dashboard .sp-cards td:not(:first-child)::before {
    content: attr(data-label);
    font-size: 0.6rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--px-text-muted);
    text-align: left;
    flex: 0 0 auto;
  }
  .sp-dashboard .sp-cards td:first-child {
    display: block;
    padding-bottom: 0.4rem;
    margin-bottom: 0.25rem;
    border-bottom: 1px solid var(--px-border);
    font-weight: 700;
    color: var(--px-text-primary);
    text-align: left;
  }
}
