/* ══════════════════════════════════════════════════════════════════════════
   calendar.css — styles for /calendar.

   RULES THIS FILE OBEYS, ENFORCED BY tests/calendar-frontend.test.js:

   1. NO token declarations. Every colour, shape and elevation comes from
      tokens.css. A rebrand must not be able to leave this page behind.
   2. NO `left`, `right` or `float`. Logical properties only — this document
      is RTL and must stay correct if a subtree flips to LTR.
   3. NO `flex-direction: row-reverse` as a layout device. In an RTL document
      `row` already runs right-to-left; reversing it double-reverses and
      breaks inside any dir="ltr" island.
   4. Mobile-first.
   ══════════════════════════════════════════════════════════════════════════ */

*, *::before, *::after { box-sizing: border-box; }

/* `hidden` must actually hide, whatever else is styled.
   The UA stylesheet's `[hidden] { display: none }` is a type-selector-strength
   rule, so ANY class that sets `display` beats it: `.btn { display:inline-flex }`
   made the intake's back button visible on the first card, where there is
   nothing to go back to. This is the whole show/hide mechanism on this page, so
   it is stated once, globally, rather than patched per component. */
[hidden] { display: none !important; }

html { color-scheme: dark light; }

body {
  margin: 0;
  min-block-size: 100svb;
  background: var(--bg);
  color: var(--text);
  font-family: 'Cairo', -apple-system, BlinkMacSystemFont, sans-serif;
  font-size: 16px;
  line-height: 1.75;
  -webkit-font-smoothing: antialiased;
}

/* ── Focus: a 2px --text ring, never a border-colour change ────────────────
   The inherited --border is 1.77:1 against --surface, well under the 3:1 a
   non-text boundary needs, so focus must not depend on it (AC7). */
:where(a, button, [tabindex]):focus-visible {
  outline: 2px solid var(--text);
  outline-offset: 2px;
  border-radius: var(--shape-s);
}

/* ── Static background orbs ────────────────────────────────────────────────
   Deliberately not animated (AC4). They are decorative and aria-hidden. */
.orbs {
  position: fixed;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 0;
}
.orb {
  position: absolute;
  border-radius: 50%;
  filter: blur(80px);
  opacity: 0.20;
}
.orb-1 {
  inline-size: 320px; block-size: 320px;
  background: var(--red);
  inset-block-start: -80px;
  inset-inline-end: -60px;
}
.orb-2 {
  inline-size: 280px; block-size: 280px;
  background: var(--purple);
  inset-block-end: 10%;
  inset-inline-start: -90px;
}
.orb-3 {
  inline-size: 220px; block-size: 220px;
  background: var(--blue);
  inset-block-end: -60px;
  inset-inline-end: 20%;
  opacity: 0.14;
}
[data-theme="light"] .orb-1 { opacity: 0.12; }
[data-theme="light"] .orb-2 { opacity: 0.10; }
[data-theme="light"] .orb-3 { opacity: 0.08; }

/* ── Top bar ───────────────────────────────────────────────────────────── */
.topbar {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 20px;
  max-inline-size: 720px;
  margin-inline: auto;
}
.logo { display: inline-flex; align-items: center; min-block-size: 44px; }
.logo img { block-size: 28px; inline-size: auto; display: block; }

.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-inline-size: 44px;
  min-block-size: 44px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 50%;
  background: var(--surface2);
  color: var(--text);
  cursor: pointer;
  transition: background-color 0.18s ease;
}
.icon-btn:hover { background: var(--surface3); }

.ms {
  font-family: 'Material Symbols Rounded';
  font-weight: normal;
  font-style: normal;
  line-height: 1;
  letter-spacing: normal;
  text-transform: none;
  display: inline-block;
  white-space: nowrap;
  direction: ltr;
  vertical-align: middle;
  font-size: 22px;
  -webkit-font-feature-settings: 'liga';
  -webkit-font-smoothing: antialiased;
}

/* ── Layout ────────────────────────────────────────────────────────────── */
.wrap {
  position: relative;
  z-index: 1;
  max-inline-size: 720px;
  margin-inline: auto;
  padding: 8px 20px 64px;
}

/* ── The invitation ────────────────────────────────────────────────────── */
.invite {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--shape-l);
  box-shadow: var(--md-elev-2);
  padding: 28px 22px;
}

.kicker {
  margin: 0 0 10px;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.08em;
  color: var(--red);
  text-transform: uppercase;
}

.invite-title {
  margin: 0 0 12px;
  font-size: 27px;
  font-weight: 900;
  line-height: 1.35;
  letter-spacing: -0.01em;
}

.invite-lede {
  margin: 0 0 26px;
  color: var(--text-muted);
  font-size: 16px;
}

/* Numerals, dates and URLs are LTR runs inside RTL text and reorder without
   isolation. Western numerals per D5. */
.num {
  unicode-bidi: isolate;
  font-variant-numeric: tabular-nums;
}

/* ── The three beats ───────────────────────────────────────────────────── */
.beats {
  list-style: none;
  margin: 0 0 28px;
  padding: 0;
  display: grid;
  gap: 18px;
}
.beat {
  display: flex;
  align-items: flex-start;
  gap: 14px;
}
.beat-num {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 30px;
  block-size: 30px;
  border-radius: 50%;
  background: var(--red-muted);
  color: var(--red);
  font-weight: 800;
  font-size: 14px;
}
.beat-title {
  margin: 0 0 2px;
  font-size: 16px;
  font-weight: 700;
  line-height: 1.5;
}
.beat-body {
  margin: 0;
  color: var(--text-muted);
  font-size: 15px;
}

/* ── Calls to action ───────────────────────────────────────────────────── */
.cta-row {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: center;
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-block-size: 48px;
  min-inline-size: 44px;
  padding-inline: 26px;
  padding-block: 12px;
  border: none;
  border-radius: 100px;
  font-family: 'Cairo', sans-serif;
  font-size: 15px;
  font-weight: 800;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 0.18s ease, box-shadow 0.18s ease;
}

/* Red is for action, never for alarm (UX spec §Colour). */
.btn-primary {
  background: var(--red);
  color: #fff;
  box-shadow: var(--md-elev-1);
}
.btn-primary:hover { background: var(--red-dark); }

/* A disabled action must LOOK disabled. The intake's next button is disabled
   until the required question is answered, and at full strength it reads as a
   working button that does nothing — the same "broken product" impression D3
   warns about for greyed-out pills, arriving from the other direction. */
.btn:disabled {
  opacity: 0.42;
  box-shadow: none;
  cursor: not-allowed;
}
.btn-primary:disabled:hover { background: var(--red); }

/* Mid-weight action: outlined surface button. Sits between primary (solid red)
   and quiet (transparent underline). Used for "Start New Season" (Story 4.7). */
.btn-secondary {
  background: var(--surface);
  border: 1.5px solid var(--border);
  color: var(--text);
  font-weight: 700;
}
.btn-secondary:hover { background: var(--surface2); border-color: var(--text-muted); }
.btn-secondary:disabled:hover { background: var(--surface); border-color: var(--border); }

/* Tertiary action. Deliberately lighter than .btn-primary and .btn-secondary:
   at the shared 800 weight the two read as equal choices, and this one is the
   way out, not the way forward. */
.btn-quiet {
  background: transparent;
  color: var(--text-muted);
  padding-inline: 8px;
  font-weight: 600;
  font-size: 14px;
  text-decoration: underline;
  text-underline-offset: 4px;
  text-decoration-color: var(--text-dim);
}
.btn-quiet:hover { color: var(--text); text-decoration-color: var(--text); }

.reassure {
  margin: 16px 0 0;
  color: var(--text-dim);
  font-size: 13px;
}

/* ── The intake ────────────────────────────────────────────────────────── */
/* The question heading takes focus on each card change, so it must not paint a
   ring when moved to programmatically — only :focus-visible does, and a
   programmatic focus is not visible-focus. */
.intake .invite-title:focus { outline: none; }

.pill-group {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-block-end: 18px;
}

.pill {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-block-size: 44px;
  min-inline-size: 44px;
  padding-inline: 18px;
  padding-block: 10px;
  border: 1px solid var(--border);
  border-radius: 100px;
  background: var(--surface2);
  color: var(--text);
  font-family: 'Cairo', sans-serif;
  font-size: 15px;
  font-weight: 600;
  cursor: pointer;
  transition: background-color 0.18s ease, border-color 0.18s ease;
}
.pill:hover { border-color: var(--text-dim); }

/* Selected state. Purple is this system's established "active/selected"
   accent, which keeps red exclusively for actions (UX spec §Colour). */
.pill-active {
  background: var(--md-secondary-container);
  border-color: transparent;
  color: var(--md-on-secondary-container);
  font-weight: 800;
}

/* A suggestion is an offer, not an answer. Lighter than a real option so the
   two rows are not mistaken for each other. */
.pill-suggest {
  background: transparent;
  border-style: dashed;
  color: var(--text-muted);
  font-weight: 600;
  font-size: 14px;
}

.field { margin-block-end: 18px; }

.intake-input {
  inline-size: 100%;
  box-sizing: border-box;
  min-block-size: 88px;
  padding: 14px 16px;
  border: 1px solid var(--border);
  border-radius: var(--shape-m);
  background: var(--surface2);
  color: var(--text);
  font-family: 'Cairo', sans-serif;
  font-size: 16px;   /* 16px or iOS zooms the page on focus */
  line-height: 1.7;
  resize: vertical;
}
.intake-input:focus {
  border-color: var(--red);
  outline: none;
}

/* AC7 — content, not a warning. No icon, no colour, no border, no background. */
.cost-note {
  margin: 0 0 16px;
  color: var(--text-muted);
  font-size: 13px;
  line-height: 1.8;
}

/* ── Arc roles ─────────────────────────────────────────────────────────── */
/* Colour encodes STRATEGY; slot state is encoded structurally and carries no
   colour (UX spec §Semantic mapping).

   Colour is never the sole carrier. Every arc role presents colour PLUS a text
   label PLUS a plain-Arabic gloss — both of which come from the backend, on the
   slot itself (backend/calendar_api/arc_templates.py). The pairing is what
   makes the skeleton teach its own structure with no tour and no coach marks.

   A role defined in Python with no class here renders colourless, which breaks
   one of those three carriers silently. tests/calendar-frontend.test.js asserts
   every role has one. */
.arc-attract { color: var(--blue); }
.arc-nurture { color: var(--green); }
.arc-convert { color: var(--purple); }

/* ── The skeleton: "The Spine" (Story 2.5) ─────────────────────────────── */
/* A rail on the LEADING edge with one node per slot. The rail makes the arc a
   visual fact — one continuous journey rather than three buckets — which is
   what lets the text stay prose instead of being demoted to metadata.

   Every dimension here is written with logical properties, so `dir="rtl"` on
   <html> is the only thing that decides which side the rail sits on. There is
   deliberately no `row-reverse` anywhere in this file (see rule 3 at the top):
   it reverses what the eye sees without reversing the DOM, and on this screen
   the DOM order IS the season's order. */

.season {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--shape-l);
  box-shadow: var(--md-elev-2);
  padding: 28px 22px;
}

/* Her sentence, verbatim. Sized as the page's title because that is what it
   is — the plan is hers, and the ten slots below are in service of this line. */
.season-goal {
  margin: 0 0 10px;
  font-size: 24px;
  font-weight: 900;
  line-height: 1.4;
  /* No clamp, no ellipsis: a long goal sentence wraps. Truncating the one line
     she wrote herself would be the exact opposite of this screen's point. */
}
.season-goal:focus { outline: none; }   /* focused programmatically (AC7) */

.season-lede {
  margin: 0 0 24px;
  color: var(--text-muted);
  font-size: 15px;
}

.spine {
  list-style: none;
  margin: 0;
  padding: 0;
  position: relative;
}

/* The rail itself. Decorative — it draws a relationship the numbers and the
   ordered list already state. */
.spine::before {
  content: "";
  position: absolute;
  inset-block: 8px;
  inset-inline-start: 5px;
  inline-size: 2px;
  background: var(--border);
  border-radius: 2px;
}

.node {
  position: relative;
  display: flex;
  gap: 14px;
  padding-block: 14px;
}
.node + .node { border-block-start: 1px solid var(--border); }

/* The arc colour fallback, for a slot whose role has no `.arc-*` class.
   ⚠️ `:where()` is load-bearing, not stylistic. `.arc-attract` and `.node-dot`
   are both single-class selectors, and these rules come LATER in the file — so
   a plain `.node-dot { color: … }` here beats the arc colour on source order
   and silently greys out every dot and every role name. That is exactly what
   happened, and no test caught it: Story 2.3's guard proves the `.arc-*` rules
   exist, not that they apply. `:where()` contributes zero specificity, so the
   arc class always wins. skeleton_check.py now measures the computed colours. */
:where(.node-dot, .node-role) { color: var(--text-dim); }

/* The dot sits ON the rail. aria-hidden in the DOM: it colours a role that is
   already written out in words beside it. */
.node-dot {
  flex: 0 0 auto;
  inline-size: 12px;
  block-size: 12px;
  margin-block-start: 5px;
  border-radius: 50%;
  background: currentColor;
  box-shadow: 0 0 0 4px var(--surface);
}

.node-body { min-inline-size: 0; flex: 1 1 auto; }

.node-meta {
  margin: 0 0 6px;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 8px;
  font-size: 12px;
}

.node-num {
  font-weight: 800;
  color: var(--text-dim);
}

.node-role {
  font-size: 13px;
  font-weight: 700;
}

/* The gloss. Mandatory, not decorative: "تقريب" alone teaches Nour nothing,
   and this is how the screen explains its own structure with no tour. */
.node-gloss {
  color: var(--text-muted);
  font-size: 12px;
  line-height: 1.5;
}

.node-title {
  margin: 0 0 4px;
  font-size: 16px;
  font-weight: 700;
  line-height: 1.45;
}

.node-hook {
  margin: 0 0 6px;
  font-size: 14px;
  color: var(--text-muted);
  line-height: 1.55;
}

/* The why-layer (FR19). `--text-muted`, NOT `--text-dim`: the first draft used
   dim and the rendered screen showed why that is wrong — it made the slot's
   stated purpose the faintest thing on the card, fainter even than the gloss
   above it. The UX spec's rejection of The Ledger direction is precisely that
   "rationale that renders as metadata is rationale that gets skipped", and this
   line is the one the product's anti-abandonment thesis rests on. It stays
   prose, at prose size, in a colour that reads as text. */
/* ── Self-report (Story 6.2) ────────────────────────────────────────────────
   🔴 44×44 IS AN AC, AND IT HAS ALREADY BEEN FAILED ONCE. Story 2.6 shipped an
   edit button with an ~18px tap target past a fully green suite. It is harder to
   catch here because the AC deliberately allows the GLYPH to be smaller than the
   hit area — so the render looks fine either way and only a measured bounding
   box tells the truth. tests/frontend/skeleton_check.py measures all three. */
.node-report {
  display: flex;
  gap: 4px;
  margin-block-start: 8px;
}
.report-opt {
  appearance: none;
  background: none;
  border: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-inline-size: 44px;
  min-block-size: 44px;
  padding: 0;
  cursor: pointer;
  border-radius: var(--shape-s);
  opacity: 0.45;
  transition: opacity 0.18s ease, background-color 0.18s ease;
}
.report-opt:hover { opacity: 0.8; }

/* The chosen one. Opacity and a surface, never a colour that means anything —
   this screen does not let colour carry state anywhere else either, and a green
   🔥 versus a red 💀 would be exactly the alarm register Story 3.5 refused. */
.report-opt.is-set {
  opacity: 1;
  background: var(--surface3);
}

/* The glyph may be smaller than its target. That is the point of the rule and
   the reason eyeballing the render cannot verify it. */
.report-glyph {
  font-size: 20px;
  line-height: 1;
}

/* Story 3.6, AC2 — the count, above the list it counts.
   No percentage, no bar, no estimate: a bar implies a rate this product cannot
   know, and an estimate would be a promise about a provider's latency. The
   numerals are Western and `dir="ltr"`-isolated by the .num rule (D5). */
.season-progress {
  margin: 0 0 12px;
  font-size: 13px;
  font-weight: 700;
  color: var(--text-muted);
}

/* Story 3.6, AC5 — the why-layer carries the product's differentiator, so it is
   typeset as prose and given a leading edge, not tucked in as metadata.

   ⚠️ 15px is a FLOOR set by the AC, not a taste call. It went out at 13px in
   Story 2.5 and read as a caption — the review's phrase was "rationale that
   renders as metadata is rationale that gets skipped", and this is the line the
   product's anti-abandonment thesis rests on.

   The AC also forbids collapsing, truncating, hiding behind a disclosure or
   rendering as a tooltip, and says that if space is tight something ELSE yields.
   So there is deliberately no `text-overflow`, no `-webkit-line-clamp` and no
   `max-height` here, and a test asserts their absence — those are the three
   properties someone reaches for when a card gets crowded. */
.node-why {
  margin: 0;
  font-size: 15px;
  color: var(--text-muted);
  line-height: 1.7;
}

/* The leading rule. AC6: decorative for assistive technology — which a CSS
   border is by construction, since it is not in the accessibility tree at all.
   The arc's MEANING travels in `.node-role` and `.node-gloss` as text, exactly
   as it does for the dot, because --blue and --purple converge under
   deuteranopia and this rule would otherwise be a second colour-only carrier. */
.node-why {
  border-inline-start: 3px solid var(--border);
  padding-inline-start: 10px;
}
.node[data-arc="attract"] .node-why { border-inline-start-color: var(--blue); }
.node[data-arc="nurture"] .node-why { border-inline-start-color: var(--green); }
.node[data-arc="convert"] .node-why { border-inline-start-color: var(--purple); }

/* Once it holds a generated why-line rather than the template's objective, it
   is her season's own sentence — full text colour, because at that point it is
   content rather than a frame around content. */
.node-why.is-generated { color: var(--text); }

/* ── Slot state treatment (Story 3.6, AC4) ──────────────────────────────────
   Keyed on `data-state` so the season list and the reel detail (Epic 4) inherit
   exactly this rather than reimplementing it slightly differently. Only ONE of
   those three screens exists today; this is the shared definition for when they
   do.

   `done` and `failed` are at full opacity — see the override below the approved
   rule. Only work still in flight is reduced. */
.node[data-state="claimed"] .node-state::after {
  content: '';
  display: inline-block;
  inline-size: 6px;
  block-size: 6px;
  margin-inline-start: 6px;
  border-radius: 50%;
  background: currentColor;
  animation: node-working 1.4s ease-in-out infinite;
}

@keyframes node-working {
  0%, 100% { opacity: 0.25; }
  50%      { opacity: 1; }
}

/* AC4 says the in-progress indicator is suppressed under reduced motion. Those
   creators lose it entirely, and that is acceptable for one reason: the state
   line already says "بنكتب السكريبت دلوقتي…" in words. Same argument that keeps
   the arc role out of its dot — the animation may decorate the meaning, never
   carry it. */
@media (prefers-reduced-motion: reduce) {
  .node[data-state="claimed"] .node-state::after {
    animation: none;
    display: none;
  }
}

/* ── The pending state ─────────────────────────────────────────────────── */
/* The UX spec defines slot states once and says the structured wait REUSES
   `pending` rather than introducing a second loading component. So: reduced
   opacity and placeholder bars, the same treatment a pending reel gets.

   There is no spinner, no percentage and no time estimate anywhere on this
   screen. Nine tenths of it is real content from the moment it opens. */
.node-title.is-pending,
.node-hook.is-pending { opacity: 0.45; }

.ghost { display: block; }

.ghost-bar {
  display: block;
  block-size: 0.72em;
  margin-block: 0.22em;
  border-radius: 4px;
  background: var(--surface3);
}
.ghost-bar-short { inline-size: 62%; }

/* ── Editing one slot (Story 2.6) ──────────────────────────────────────── */

/* The angle. Hidden when empty — nothing generates one, so an absent angle is a
   field she has not used rather than content that is on its way. It reads as a
   quieter aside than the hook, because it is a note she wrote to steer the
   script, not a line that goes on camera. */
.node-angle {
  margin: 0 0 6px;
  font-size: 13px;
  color: var(--text-muted);
  line-height: 1.55;
  padding-inline-start: 10px;
  border-inline-start: 2px solid var(--border);
}

/* The authorship mark (AC3). TEXT, never a colour or a dot: this screen already
   refuses to let colour carry meaning — `--blue` and `--purple` converge under
   deuteranopia — and a mark that says which slots she changed is exactly the
   kind of thing that must survive being printed in greyscale. */
.node-mark {
  font-size: 11px;
  font-weight: 700;
  color: var(--text-muted);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 1px 8px;
}

/* ⚠️ min-*-size 44px is Story 1.8's AC, not a preference — "every interactive
   target measures at least 44×44px". The first draft of this button was 13px
   underlined text with `padding: 2px 0`, which reads fine on a desktop screenshot
   and is a ~18px-tall tap target on the phone this product is used on. The
   inline-flex is what makes the min sizes apply to a text-only button. */
.node-edit {
  appearance: none;
  background: none;
  border: 0;
  display: inline-flex;
  align-items: center;
  min-block-size: 44px;
  min-inline-size: 44px;
  padding-inline: 2px;
  font: inherit;
  font-size: 13px;
  font-weight: 700;
  color: var(--text-muted);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
}
.node-edit:hover { color: var(--text); }

/* Story 3.4 — regenerate one reel. Shares .node-edit's shape deliberately: the
   two are the same KIND of thing (a quiet, per-slot action) and they are never
   on screen at the same time, since the edit affordance goes away at approval
   and this one only appears after it. The 44px minimums are Story 1.8's AC and
   are the reason this is inline-flex rather than plain text. */
.node-regen {
  appearance: none;
  background: none;
  border: 0;
  display: inline-flex;
  align-items: center;
  min-block-size: 44px;
  min-inline-size: 44px;
  padding-inline: 2px;
  font: inherit;
  font-size: 13px;
  font-weight: 700;
  color: var(--text-muted);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
}
.node-regen:hover:not(:disabled) { color: var(--text); }
.node-regen:disabled { cursor: default; opacity: 0.65; }

/* The outcome of her tap, on its own line. Never merged into .node-state: that
   line says what the slot IS, and overwriting it with a retry notice would erase
   the only text explaining why she is retrying. */
.node-regen-note:empty { display: none; }
.node-regen-note {
  margin: 6px 0 0;
  font-size: 12px;
  color: var(--text-muted);
}

/* The form replaces the view INSIDE the node. No modal, no overlay, no route
   change (AC1) — the eight other slots stay on screen and in position, which is
   the whole point: she is correcting one thing against the plan, not editing a
   record in isolation. */
.node-form { margin-block-start: 4px; }

/* The slot's objective, kept on screen while she rewrites it. Same text as
   `.node-why` and written from the same slot object. It sits ABOVE the fields
   deliberately: it is the brief, and a brief printed under the answer is a
   brief nobody reads. */
.form-why {
  margin: 0 0 12px;
  font-size: 13px;
  color: var(--text-muted);
  line-height: 1.55;
}
.form-why:empty { display: none; }

.field { margin: 0 0 10px; }

.field-label {
  display: block;
  margin-block-end: 4px;
  font-size: 12px;
  font-weight: 700;
  color: var(--text-muted);
}

.field-input {
  inline-size: 100%;
  box-sizing: border-box;
  padding: 8px 10px;
  font: inherit;
  font-size: 14px;
  line-height: 1.5;
  color: var(--text);
  background: var(--surface2);
  border: 1px solid var(--border);
  border-radius: 10px;
  resize: vertical;
}
.field-input:focus-visible {
  outline: 2px solid var(--text-dim);
  outline-offset: 1px;
}

.field-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
}

/* Smaller than the page's primary buttons, but never below the 44px floor. */
.btn-sm {
  padding: 6px 14px;
  font-size: 13px;
  min-block-size: 44px;
  min-inline-size: 44px;
}

/* Failures only, and empty otherwise. `role="status"` is on the element, so a
   screen reader hears the refusal without the focus being stolen out of the
   textarea she is still standing in. */
.field-error:empty { display: none; }
.field-error {
  margin: 8px 0 0;
  font-size: 13px;
  line-height: 1.55;
  color: var(--text);
}

/* ── Approval (Story 2.7) ──────────────────────────────────────────────── */

/* Sticky within the viewport, so one tap is reachable without scrolling past
   ten slots (AC2). `position: sticky` with `bottom` rather than a fixed
   overlay: a fixed bar sits on top of the page and WILL cover slot 10, which
   the AC forbids. Sticky keeps the bar inside the document flow, so the space
   it occupies is space the list already accounts for.

   `padding-block-end` on the section below gives it somewhere to rest. */
.approve-bar {
  position: sticky;
  inset-block-end: 12px;
  z-index: 2;
  margin-block-start: 20px;
  padding: 12px;
  border-radius: 16px;
  background: var(--surface);
  border: 1px solid var(--border);
  box-shadow: 0 6px 24px rgb(0 0 0 / 0.18);
}
.approve-bar .btn-primary { inline-size: 100%; }

.approve-note:empty { display: none; }
.approve-note {
  margin: 10px 0 0;
  font-size: 13px;
  line-height: 1.55;
  color: var(--text);
}

/* Room for the sticky bar to rest without ever overlapping slot 10. */
.season { padding-block-end: 12px; }

/* ── The post-approval treatment (AC9) ─────────────────────────────────── */

/* Reduced opacity, matching the UX spec's `pending` slot state — the same
   treatment the ghost lines use, rather than a second visual language for the
   same idea. It is deliberately mild: this is a season that exists and is
   queued, not one that is unavailable.

   ⚠️ Applied to the CONTENT, not to the whole node. The first version dimmed
   `.node-body`, which took the state line down with it — and that line is the
   only NEW information on the card after approval. Compounded with a dim
   colour it became the faintest text on screen, which is exactly the mistake
   corrected for the why-layer in Story 2.5. Only a render showed it. */
.node.is-approved :where(.node-title, .node-hook, .node-angle, .node-why) {
  opacity: 0.72;
}

/* 🔴 Story 3.6, AC4 supersedes the blanket dimming above.
   `done` is full opacity WITH its actions available; only work still in flight
   is reduced. Story 2.7 dimmed every approved card because at that point every
   approved card was pending — the treatment was right for its moment and wrong
   as a permanent rule.

   Written as an override rather than by rewriting the rule above, so the
   pre-approval and still-generating treatment stays exactly what it was and this
   change is legible as what it is. */
.node.is-approved:is([data-state="done"], [data-state="failed"])
  :where(.node-title, .node-hook, .node-angle, .node-why) {
  opacity: 1;
}

/* Story 3.5, AC3 — a slot that will not generate renders at FULL OPACITY on a
   neutral surface.

   The approved treatment above dims every card's content, which is right for
   nine finished reels and exactly wrong for the one that needs her. Dimming the
   only card asking for attention is the same error this screen already made once
   at a smaller scale, when post-approval dimming took the state line with it.

   ⚠️ NO RED, NO ICON, NO COLOUR AT ALL. The AC is explicit and it agrees with how
   this screen already works: the arc role is never carried by its dot, because
   --blue and --purple converge under deuteranopia. A red card would carry the
   whole meaning in the one channel some creators cannot read — and it would also
   read as alarm, when the honest message is "this one slot, and your season is
   fine". Words carry it; opacity separates it. */
.node.is-approved.is-failed
  :where(.node-title, .node-hook, .node-angle, .node-why) {
  opacity: 1;
}

/* The state, in words. Reduced opacity alone tells her something is different
   about the row, not WHAT — the same reason the arc role is never carried by
   its dot alone. Full opacity and `--text-muted`, so it reads as text rather
   than as the faintest thing on a card that has just been dimmed. */
.node-state:empty { display: none; }
.node-state {
  margin: 6px 0 0;
  font-size: 12px;
  font-weight: 700;
  color: var(--text-muted);
}

/* Visible to screen readers only. The ghost bars are aria-hidden, so this is
   what a non-sighted creator hears in place of a title that is still being
   written — silence would read as an empty slot. */
.sr-only {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ── The limit screen ──────────────────────────────────────────────────── */
/* Shares .invite's surface deliberately: hitting the ceiling is a normal
   moment in the product, not a failure state, and giving it its own alarmed
   treatment would say otherwise.
   Hiding is handled by the global [hidden] rule at the top of this file —
   which exists because exactly this failure did happen, on the intake's back
   button. */

/* ══════════════════════════════════════════════════════════════════════════
   Daily Return — calendar screen (Story 4.1).

   The season view shown when a creator re-enters after approving.
   Single column at all widths; no grid, no filtering, no multi-column.
   RTL-safe: logical properties only (rule 2 at the top of this file).
   No fixed heights on any container (text-spacing override survival, AC8).
   All tap targets ≥ 44×44px (enforced in this block, not mechanically).
   ══════════════════════════════════════════════════════════════════════════ */

/* ── Hero card ─────────────────────────────────────────────────────────── */
/* Elevated surface with arc-coloured leading rule.
   --arc-colour is set as an inline style by calendar-screen.js, defaulting
   to --border when the arc role is unknown. */
.season-hero {
  padding: 0;
  margin-block-end: 24px;
}

.calendar-hero {
  background: var(--surface2);
  border: 1px solid var(--border);
  border-radius: var(--shape-l);
  border-inline-start: 4px solid var(--arc-colour, var(--border));
  box-shadow: var(--md-elev-2);
  padding: 22px 20px;
  /* No fixed height — text-spacing overrides must not clip */
}

.calendar-kicker {
  margin: 0 0 6px;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.08em;
  color: var(--text-muted);
  text-transform: uppercase;
}

.calendar-hero-title {
  margin: 0 0 8px;
  font-size: 20px;
  font-weight: 800;
  line-height: 1.4;
}

.calendar-hero-hook {
  margin: 0 0 14px;
  font-size: 15px;
  color: var(--text-muted);
  line-height: 1.6;
}

/* Arc role indicator: colour dot + role name + gloss */
.calendar-arc-indicator {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 16px;
  font-size: 13px;
  color: var(--text-dim);
}

.calendar-arc-dot {
  display: inline-block;
  inline-size: 8px;
  block-size: 8px;
  border-radius: 50%;
  flex-shrink: 0;
}

.calendar-arc-name {
  font-weight: 600;
}

.calendar-arc-gloss {
  color: var(--text-dim);
}

/* Copy button — primary action, min 44×44px */
.calendar-copy-btn {
  min-block-size: 44px;
  min-inline-size: 44px;
}

/* ── Season list ───────────────────────────────────────────────────────── */
/* Vertical arc-ordered sequence; one per slot; no grid; no horizontal scroll.
   The list is an <ol> because the order IS the meaning (arc order). */
.season-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 12px;
}

.calendar-reel-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--shape-m);
  padding: 16px 18px;
  /* No fixed height */
}

.calendar-reel-meta {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-block-end: 6px;
  font-size: 12px;
  color: var(--text-dim);
}

.calendar-reel-num {
  font-weight: 800;
  font-size: 13px;
}

.calendar-reel-date {
  font-size: 12px;
}

.calendar-reel-title {
  margin: 6px 0 0;
  font-size: 15px;
  font-weight: 600;
  line-height: 1.5;
}

/* ══════════════════════════════════════════════════════════════════════════
   Reel detail view (Story 4.2).

   Card anatomy: back → meta → title → hook → why-band → script → actions.
   RTL-safe: logical properties only. No fixed heights. All tap targets ≥ 44px.
   ══════════════════════════════════════════════════════════════════════════ */

.reel-detail {
  padding: 8px 0 64px;
  /* No fixed height — text-spacing overrides must not clip */
}

/* Back button — real <button>, min 44×44px tap target */
.reel-detail-back {
  display: inline-flex;
  align-items: center;
  min-block-size: 44px;
  min-inline-size: 44px;
  margin-block-end: 16px;
}

/* Meta row: slot · date · arc-role indicator */
.reel-detail-meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  margin-block-end: 10px;
  font-size: 13px;
  color: var(--text-dim);
}

.reel-detail-slot {
  font-weight: 800;
}

.reel-detail-date {
  font-size: 13px;
}

.reel-detail-sep {
  color: var(--text-dim);
  font-size: 11px;
}

/* Arc-role indicator */
.reel-detail-arc {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.reel-detail-arc-dot {
  display: inline-block;
  inline-size: 8px;
  block-size: 8px;
  border-radius: 50%;
  flex-shrink: 0;
}

.reel-detail-arc-name {
  font-weight: 600;
  font-size: 13px;
}

.reel-detail-arc-gloss {
  color: var(--text-dim);
  font-size: 12px;
}

/* Title */
.reel-detail-title {
  margin: 0 0 10px;
  font-size: 20px;
  font-weight: 800;
  line-height: 1.4;
  /* No fixed height */
}

/* Hook */
.reel-detail-hook {
  margin: 0 0 4px;
  font-size: 16px;
  color: var(--text-muted);
  line-height: 1.6;
  /* No fixed height */
}

/* ── Why-layer band (ST-2) ──────────────────────────────────────────────────
   Elevated surface, full-width, prose — never a label, chip or metadata row.
   Colour is --text-muted (NOT --text-dim — wrong in Story 2.5, must not repeat).
   Font size 15px — spec minimum, enforced here. */
.why-band {
  background: var(--surface2);
  border-radius: var(--shape-m);
  padding: 14px 16px;
  margin-block: 16px;
  /* No fixed height */
}

.why-band-text {
  margin: 0;
  font-size: 15px;
  color: var(--text-muted);
  line-height: 1.7;
}

/* Script — full text, no collapse, no max-height */
.reel-detail-script {
  margin: 0 0 24px;
  font-size: 15px;
  line-height: 1.85;
  white-space: pre-wrap;
  /* No max-height, no overflow:hidden, no -webkit-line-clamp */
}

/* Action row — primary action within thumb reach at bottom */
.reel-detail-actions {
  display: flex;
  gap: 12px;
}

.reel-detail-copy {
  min-block-size: 44px;
  min-inline-size: 44px;
  flex: 1;
}

/* Copy — confirmed state (Story 4.3).
   Persistent: no timeout, no reset. Icon + label set by JS (copy-utils.js). */
.reel-detail-copy.is-copied,
.calendar-copy-btn.is-copied {
  background-color: var(--green);
  gap: 6px;
}

/* Live region is announced but invisible (AD-4). */
.copy-live-region {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
}

/* ── Larger screens ────────────────────────────────────────────────────── */
@media (min-width: 640px) {
  .invite { padding: 40px 36px; }
  .invite-title { font-size: 34px; }
  .wrap { padding-block-end: 80px; }
  .season { padding: 40px 36px; }
  .season-goal { font-size: 30px; }

  /* Desktop: single column capped at 800px, centred (plan ST-6). */
  .season-list {
    max-inline-size: 800px;
    margin-inline: auto;
  }
  .season-hero {
    max-inline-size: 800px;
    margin-inline: auto;
    margin-block-end: 28px;
  }

  /* Reel detail — same 800px cap, centred (Story 4.2). */
  .reel-detail {
    max-inline-size: 800px;
    margin-inline: auto;
  }
}

/* ── Past seasons entry point (Story 4.6) ──────────────────────────────── */
.past-seasons-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-block-end: 16px;
  color: var(--text-muted);
  font-size: 14px;
}

/* ── Completed seasons list (Story 4.6) ────────────────────────────────── */
.completed-seasons-back {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-block-end: 24px;
  color: var(--text-muted);
}

.completed-seasons-heading {
  font-size: 22px;
  font-weight: 700;
  margin-block: 0 20px;
  color: var(--text);
}

.completed-seasons-list {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.completed-season-item {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
  padding-block: 16px;
  padding-inline: 16px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--shape-s);
  cursor: pointer;
  text-align: start;
  color: var(--text);
  font-family: inherit;
  font-size: 16px;
  transition: background-color 0.15s;
}

.completed-season-item:hover,
.completed-season-item:focus-visible {
  background: var(--surface2);
  outline: 2px solid var(--red);
  outline-offset: 2px;
}

.completed-season-title {
  font-weight: 600;
}

.completed-season-meta {
  font-size: 13px;
  color: var(--text-muted);
}

/* ── Reduced motion ────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* The control that opens the sheet. Shares .node-regen's quiet shape — both are
   per-reel secondary actions — and the 44px floor is stated, not inherited. */
.reel-detail-hook-swap {
  appearance: none;
  background: none;
  border: 0;
  display: inline-flex;
  align-items: center;
  min-block-size: 44px;
  min-inline-size: 44px;
  padding-inline: 2px;
  font: inherit;
  font-size: 13px;
  font-weight: 700;
  color: var(--text-muted);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
}
.reel-detail-hook-swap:hover { color: var(--text); }

/* ══════════════════════════════════════════════════════════════════════════
   The hook sheet (Story 5.1) — the only overlay primitive on this page.
   ══════════════════════════════════════════════════════════════════════════ */

.sheet-scrim {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: flex;
  /* Anchored to the BLOCK-END edge: a bottom sheet within thumb reach, not a
     centred modal. On a 390px screen a centred dialog puts the options under
     her hand and the cancel action out of one-handed reach. */
  align-items: flex-end;
  background: rgb(0 0 0 / 0.5);
}

.sheet {
  inline-size: 100%;
  max-inline-size: 720px;
  margin-inline: auto;
  background: var(--surface);
  border: 1px solid var(--border);
  /* LOGICAL corners (AC). The leading edges are the top ones in both
     directions, and a physical border-top-left-radius would put the rounded
     corner on the wrong side of an RTL document the day a subtree flips. */
  border-start-start-radius: var(--shape-l);
  border-start-end-radius: var(--shape-l);
  padding: 22px 20px calc(20px + env(safe-area-inset-bottom, 0px));
  box-shadow: var(--md-elev-2);
  animation: sheet-rise 0.22s ease-out;
}

@keyframes sheet-rise {
  from { transform: translateY(12%); }
  to   { transform: translateY(0); }
}

/* AC — it SNAPS into place rather than animating. A sheet that slides is the
   single most motion-heavy thing on this page, and it arrives unrequested. */
@media (prefers-reduced-motion: reduce) {
  .sheet { animation: none; }
}

.sheet-title {
  margin: 0 0 6px;
  font-size: 19px;
  font-weight: 900;
}

.sheet-lede {
  margin: 0 0 14px;
  font-size: 14px;
  color: var(--text-muted);
}

/* The busy note. Never red, never an icon — "briefly unavailable" is not an
   error, and this page does not let colour carry state anywhere. */
.sheet-note:empty { display: none; }
.sheet-note {
  margin: 0 0 12px;
  font-size: 13px;
  font-weight: 700;
  color: var(--text-muted);
}

.sheet-options {
  display: grid;
  gap: 8px;
  margin-block-end: 14px;
}

/* 44px minimum is Story 1.8's AC. These are comfortably taller because each
   carries a label and a hint, but the floor is stated rather than assumed —
   this rule has already been failed twice on this page. */
.sheet-option {
  appearance: none;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  min-block-size: 44px;
  padding: 10px 14px;
  border: 1px solid var(--border);
  border-radius: var(--shape-m);
  background: var(--surface2);
  color: var(--text);
  font-family: 'Cairo', sans-serif;
  text-align: start;
  cursor: pointer;
  transition: background-color 0.18s ease;
}
.sheet-option:hover:not(:disabled) { background: var(--surface3); }
.sheet-option:disabled { opacity: 0.45; cursor: default; }

.sheet-option-label { font-size: 15px; font-weight: 800; }
.sheet-option-hint { font-size: 13px; color: var(--text-muted); }

.sheet-cancel { min-block-size: 44px; }
