/* TendingDaily wizard -- round 50 redesign, "The Missal Page" (see
   DESIGN.md at the project root for the full system: palette reasoning,
   named rules, component specs).

   EXPERIMENTAL DARK INVERSION (round 50, same session, "have a play and
   see"): the light paper->vellum ladder is inverted here to a dark
   ladder -- one surface (--paper, now the darkest value, not the
   lightest) instead of a light page resting on a dark desk. Same token
   NAMES/ROLES as the light version (paper/vellum/vellum-deep = base/
   raised-1/raised-2 surface, ink/ink-soft = text, border = hairline) so
   every rule below still reads correctly -- only the :root values
   changed. This is exploratory, not yet a locked decision; if it's
   kept, DESIGN.md and these token names should get a proper pass
   (they're momentarily misleading -- "paper" is dark right now). Kept
   the terracotta/gold values exactly as documented (round 41/42
   locked); both already read well on a dark ground. */

:root {
  --terracotta: oklch(56.2% 0.138 29.8);
  --terracotta-deep: oklch(49.5% 0.124 29.2);
  --terracotta-ember: oklch(38% 0.10 29.5);
  --gold: oklch(72% 0.10 80);
  --gold-deep: oklch(58% 0.10 78);
  --ink: oklch(93% 0.008 53);
  --ink-soft: oklch(68% 0.02 50);
  --paper: oklch(21% 0.016 40);
  /* Darker and warmer than the first pass (was a fairly neutral 27%L/
     hue 53, low chroma) -- richer ember tone to match the gradient
     instead of a flat grayish-brown box. */
  --vellum: oklch(23% 0.035 38);
  --vellum-deep: oklch(28% 0.045 36);
  --border: oklch(38% 0.03 40);
  --error: oklch(72% 0.16 25);
  /* Fixed light value for text/icons placed directly on a terracotta
     fill (buttons) -- decoupled from --paper on purpose, since --paper
     itself is now dark in this variant and would produce dark-on-
     terracotta text if reused here. */
  --on-terracotta: oklch(98.2% 0.004 53);

  --font-display: 'Fraunces', Georgia, 'Times New Roman', serif;
  --font-body: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;

  --radius-sm: 6px;
  --radius-md: 10px;
  --radius-lg: 16px;

  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);

  /* So native browser chrome (select arrows, the date-input calendar
     icon/picker, checkbox ticks) renders its dark variant instead of
     guessing from a light default against this dark page. */
  color-scheme: dark;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: var(--font-body);
  color: var(--ink);
  /* One surface, no separate light "page" -- header, banners, and the
     wizard all sit directly on --paper (dark in this variant). Flex
     column so content can vertically center in the leftover space on
     short screens (step 1's grid) instead of leaving one large dead
     gap below it -- on tall content (a long review/edit screen) it
     simply becomes as tall as its content and this has no effect. */
  /* Experimental v6: lighter start point -- the header's icon/"Sign in"
     sit in the top-left corner where the gradient begins, and 40%L
     wasn't enough separation from the terracotta accent color itself.
     Bumped to 54%L (chroma eased back slightly so it doesn't clip/
     oversaturate at that lightness), same hue. */
  background: linear-gradient(135deg, oklch(54% 0.09 12) 0%, oklch(27% 0.05 15) 45%, var(--paper) 85%);
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

/* Experimental cover treatment, access-gate screen only -- see
   index.html's comment above the markup. */
.brand-hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.85rem;
  margin-bottom: 2.25rem;
  text-align: center;
}
.brand-hero-icon {
  width: 84px;
  height: 84px;
  color: var(--terracotta);
  filter: drop-shadow(0 0 28px oklch(56.2% 0.138 29.8 / 0.4));
}
.brand-hero-word {
  font: 600 1.05rem/1 var(--font-display);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

/* Round 50: any interactive/pressable element gets real feedback on
   press (emil-design-eng pass) -- a subtle scale-down, never a shadow.
   Kept off keyboard-repeatable navigation, matches the animation
   decision framework's "don't animate 100+/day actions" guidance --
   these are all low-frequency, once-per-screen actions. */
button, a.btn, .card, .quick-pick-btn {
  transition: transform 150ms var(--ease-out);
}
button:active, a.btn:active, .card:active, .quick-pick-btn:active {
  transform: scale(0.97);
}

/* Round 50: a single, consistent focus language across every
   interactive element -- gold outline, never color-shift alone (a
   low-vision/older-audience requirement, see PRODUCT.md). */
button:focus-visible, a:focus-visible, input:focus-visible,
select:focus-visible, textarea:focus-visible, .card:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
}

.site-header {
  padding: 1.5rem 1.5rem 0.5rem;
  border-bottom: 1px solid var(--border);
  /* Real fix, not another gradient tweak: logo.png bakes terracotta in
     as flat raster pixels, so its contrast rides on wherever the
     gradient happens to be at that scroll position -- a moving target
     that broke as soon as the start point got lighter (real feedback:
     "can't read icon"). A stable dark scrim behind the header only
     means the logo's contrast no longer depends on the gradient at
     all. */
  background: oklch(14% 0.02 40 / 0.6);
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.site-header .logo { height: 40px; }

/* Round 47 follow-up (Phase 6, Step 4 follow-up): the minimal, optional
   sign-in affordance in the header -- see index.html and app.js's
   initAccountAuth(). Deliberately small and quiet: signing in is still
   optional for everyone (see models.py's free-tier-stays-anonymous
   decision), this just gives an institutional seat (or a free
   individual who wants one) a way to attach their real builds to their
   account. */
.account-bar { font-size: 0.82rem; color: var(--ink-soft); display: flex; align-items: center; gap: 0.9rem; }
/* Round 50: a quiet entry point for a prospective institution to learn
   the license option exists -- see #institution-info-dialog. Round 51,
   real feedback after this went live: the original ink-soft/plain-
   border look sat too close in color/weight to "Signed in as
   {email}" right next to it once someone was actually signed in --
   both read as the same muted gray text. Given a persistent terracotta
   tint instead of an ink-soft one so it reads as a distinct badge next
   to plain account text at a glance, not just on hover. Tonal fill, no
   shadow, per the Flat Page Rule. */
.tag-button {
  background: oklch(56.2% 0.138 29.8 / 0.16);
  border: 1px solid oklch(56.2% 0.138 29.8 / 0.5);
  border-radius: 999px;
  color: var(--terracotta);
  cursor: pointer;
  padding: 0.2rem 0.7rem;
  font-size: 0.76rem;
  font-weight: 600;
  font-family: inherit;
}
.tag-button:hover { color: var(--on-terracotta); background: var(--terracotta); border-color: var(--terracotta); }
.account-link {
  background: none;
  border: none;
  color: var(--terracotta);
  cursor: pointer;
  padding: 0;
  font-size: 0.82rem;
  font-family: inherit;
  text-decoration: underline;
}
.account-link:hover { color: var(--terracotta-deep); }
.account-panel {
  padding: 0.85rem 1.5rem 1rem;
  border-bottom: 1px solid var(--border);
  background: var(--vellum);
}
/* Real bug, found round 50: .account-panel sits outside both `form` and
   `#content-panel`, so it never picked up either the input dark-theme
   fix (line ~322 above, "caught live during the dark-inversion pass")
   or the .hint text-scale rule below (form .hint, #content-panel
   .hint) -- it was rendering on raw browser UA defaults the whole time
   (full-size white text, white input box under the autofill tint),
   not a deliberate look. Scoped rules below match the existing form/
   #content-panel treatment exactly, just addressed to .account-panel
   directly since it's neither. */
.account-panel label,
.account-panel .hint {
  display: block;
  font-size: 0.78rem;
  color: var(--ink-soft);
  margin: 0 0 0.5rem;
}
.account-link-inline {
  color: var(--terracotta);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.account-link-inline:hover { color: var(--terracotta-deep); }
.account-panel input[type=email] {
  padding: 0.5rem 0.65rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-size: 0.9rem;
  width: 220px;
  margin-right: 0.4rem;
  background: var(--vellum-deep);
  color: var(--ink);
  font-family: inherit;
  transition: border-color 150ms ease;
}
.account-panel input[type=email]:focus {
  outline: none;
  border-color: var(--terracotta);
}
/* Kills the browser's native autofill yellow tint (Chrome/Safari), which
   was overriding the dark input entirely -- the long transition-delay
   is the standard trick to stop the browser re-applying its own
   background on interaction. */
.account-panel input[type=email]:-webkit-autofill,
.account-panel input[type=email]:-webkit-autofill:hover,
.account-panel input[type=email]:-webkit-autofill:focus {
  -webkit-text-fill-color: var(--ink);
  box-shadow: 0 0 0 1000px var(--vellum-deep) inset;
  transition: background-color 9999s ease-in-out 0s;
}
.account-panel #account-status {
  display: inline-block;
  font-size: 0.78rem;
  margin: 0.5rem 0 0;
  color: var(--ink-soft);
}
.account-panel #account-status.is-error { color: var(--error); }
.account-panel #account-status.is-success { color: var(--gold); }

/* Round 50: the institution-info dialog opened by the header's
   "Institutions" tag button. Flat Page Rule -- tonal layering (vellum
   surface on the paper backdrop), no drop shadow; the browser's own
   ::backdrop dims the page behind it instead. */
#institution-info-dialog {
  max-width: 420px;
  width: calc(100% - 2.5rem);
  padding: 1.75rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--vellum);
  color: var(--ink);
}
#institution-info-dialog::backdrop { background: oklch(10% 0.01 40 / 0.6); }
#institution-info-dialog h2 {
  font-family: var(--font-display);
  font-size: 1.3rem;
  margin: 0 0 0.75rem;
}
#institution-info-dialog p { margin: 0 0 1.5rem; color: var(--ink-soft); line-height: 1.5; }
#institution-info-dialog .dialog-actions { display: flex; gap: 0.75rem; }

/* Round 46, "New Suggestions" step 2: the free-tier boundary placeholder
   banner (see index.html and app.js's updateFreeTierBanner()). Deliberately
   quiet, not alarming -- it's informational, never blocks anything. */
.free-tier-banner {
  background: var(--vellum);
  color: var(--ink-soft);
  font-size: 0.82rem;
  text-align: center;
  padding: 0.5rem 1rem;
  border-bottom: 1px solid var(--border);
}

/* Dark-inversion follow-up: no separate "page" card anymore -- the
   wizard sits directly on the same --paper surface as everything else,
   just spaced and vertically centered. :not([hidden]), not a bare
   `main.wizard`: same specificity trap this file already documents
   twice below (.btn, #cover-photo-preview-row) -- an unconditional
   `display: flex` here beat the UA [hidden] rule and made #access-gate
   and #wizard render stacked on top of each other simultaneously,
   caught in testing this same round. */
main.wizard:not([hidden]) {
  max-width: 640px;
  width: 100%;
  margin: 2.5rem auto;
  padding: 2rem 2.5rem 2.5rem;
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
@media (max-width: 700px) {
  main.wizard:not([hidden]) {
    margin: 0 auto;
    padding: 1.75rem 1.25rem 2rem;
  }
}

.steps {
  display: flex;
  list-style: none;
  padding: 0;
  margin: 0 0 2rem;
  gap: 0.5rem;
}
.steps li {
  flex: 1;
  text-align: center;
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--ink-soft);
  padding-bottom: 0.5rem;
  border-bottom: 2px solid var(--border);
}
.steps li.active {
  color: var(--ink);
  border-bottom-color: var(--terracotta);
}
/* Round 50: completed steps mark gold, not terracotta -- terracotta is
   reserved for "current position" only (The Single Ember Rule); gold
   reads as "done", a distinct signal instead of two steps competing
   for the same color. */
.steps li.done {
  border-bottom-color: var(--gold);
}

h1 { font: 500 clamp(1.5rem, 3vw, 2.1rem)/1.15 var(--font-display); letter-spacing: -0.01em; margin: 0 0 0.6rem; }
.subtle { color: var(--ink-soft); margin-top: 0; }

/* Round 50, "The Rubric Rule" (DESIGN.md Components): a short gold rule
   under every page-level header, echoing a missal's rubric line. Fixed
   width, not full-bleed -- a signature mark, not a divider. */
h1::after {
  content: "";
  display: block;
  width: 48px;
  height: 2px;
  margin-top: 0.5rem;
  background: var(--gold);
}

/* Round 50: given a slight lift now that copy elsewhere (#format-note)
   refers to it by name ("use Back...") -- was plain unstyled text
   before, easy to miss as a real control. Underline (same quiet-link
   pattern as .account-link) is the whole change; still no new color or
   weight, matching the Flat Page Rule's restraint. */
.back-link {
  background: none;
  border: none;
  color: var(--ink-soft);
  cursor: pointer;
  padding: 0;
  margin-bottom: 0.75rem;
  font-size: 0.9rem;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.back-link:hover { color: var(--ink); }

.card-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.75rem;
  margin-top: 1rem;
}
@media (max-width: 480px) {
  .card-grid { grid-template-columns: 1fr; }
}

/* Round 50: flat by default (The Flat Page Rule) -- no shadow anywhere
   below, depth comes only from the paper -> vellum -> vellum-deep
   tonal ladder. Slightly less rounded than the old 8px default (6/10px
   scale) to read more like a printed rule-box than a soft app pill. */
.card {
  border: 1px solid var(--border);
  background: var(--vellum);
  border-radius: var(--radius-md);
  padding: 1.25rem;
  cursor: pointer;
  text-align: left;
  font-size: 1rem;
  font-family: inherit;
  color: var(--ink);
}
.card:hover, .card:focus-visible {
  border-color: var(--terracotta);
  outline: none;
}
.card.selected {
  background: var(--vellum-deep);
  border-color: var(--terracotta);
}
.card .card-sub {
  display: block;
  font-size: 0.8rem;
  color: var(--ink-soft);
  margin-top: 0.25rem;
}

.other-row,
.template-picker {
  margin-top: 1.5rem;
  padding-top: 1rem;
  border-top: 1px solid var(--border);
}
.other-row label { display: block; font-size: 0.85rem; margin-bottom: 0.4rem; color: var(--ink-soft); }
.other-row input {
  width: 100%;
  padding: 0.65rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-size: 1rem;
  margin-bottom: 0.6rem;
  /* Real pre-existing bug, only ever invisible because the old page was
     also white: no background/color was ever set here, so it silently
     rendered on the browser's default white input background. Caught
     live during the dark-inversion pass. */
  background: var(--vellum);
  color: var(--ink);
}

form .field, #content-panel .field { margin-bottom: 1.1rem; }
form label, #content-panel label { display: block; font-size: 0.85rem; font-weight: 600; margin-bottom: 0.4rem; color: var(--ink-soft); }
form select, form input[type=date], form input[type=text], form input[type=password], form textarea,
#content-panel select, #content-panel input[type=text], #content-panel textarea {
  width: 100%;
  padding: 0.65rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-size: 1rem;
  font-family: inherit;
  background: var(--vellum);
  color: var(--ink);
  transition: border-color 150ms ease;
}
form select:focus, form input:focus, form textarea:focus,
#content-panel select:focus, #content-panel input:focus, #content-panel textarea:focus {
  border-color: var(--terracotta);
}
form .hint, #content-panel .hint { font-size: 0.78rem; color: var(--ink-soft); margin: 0.35rem 0 0; }

/* Round 44, Phase 5 feedback: the content review/edit step (#content-panel,
   see app.js's renderContentPanel()/addCustomTextBlock()). Not inside a
   <form> element deliberately -- these buttons are all type="button", so
   pressing Enter in a text field can't trigger a native form submit /
   page reload, which a real <form> wrapper would risk. */
#content-panel .readonly-text {
  background: var(--vellum);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0.65rem 0.75rem;
  font-size: 1rem;
  margin: 0;
}
#content-panel .custom-text-row {
  border: 1px dashed var(--border);
  border-radius: var(--radius-sm);
  padding: 0.8rem;
  margin-bottom: 1rem;
}
#content-panel .custom-text-remove { margin-top: 0.4rem; }
#content-panel #add-custom-text { margin-bottom: 1.2rem; }

/* Round 44, step 4: cover art -- quick-pick images (see app.js's
   selectQuickPick()) and photo upload (handleCoverPhotoSelected()/
   removeCoverPhoto()). */
#cover-art-field { margin-top: 1.4rem; padding-top: 1rem; border-top: 1px solid var(--border); }
#cover-art-field input[type=file] { font-size: 0.9rem; }

#cover-quick-picks { display: flex; gap: 0.6rem; margin: 0.75rem 0 1rem; flex-wrap: wrap; }
.quick-pick-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.3rem;
  width: 76px;
  padding: 0.5rem 0.25rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--vellum);
  cursor: pointer;
  font-family: inherit;
  font-size: 0.75rem;
  color: var(--ink);
}
.quick-pick-btn img { width: 32px; height: 32px; }
.quick-pick-btn:hover { border-color: var(--terracotta); }
.quick-pick-btn.selected { border-color: var(--terracotta); background: var(--vellum-deep); box-shadow: 0 0 0 1px var(--terracotta); }

/* #content-panel .checkbox-label, not a bare .checkbox-label: the
   existing "#content-panel label" rule above is an ID selector and
   would otherwise win over a plain class selector regardless of source
   order (same specificity lesson as the [hidden] fix above) -- matching
   its ID here, not reaching for !important, keeps this consistent. */
#content-panel .checkbox-label {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  font-size: 0.85rem;
  font-weight: 400;
  color: var(--ink);
  margin: 0.6rem 0;
  cursor: pointer;
}
.checkbox-label input[type=checkbox] { margin-top: 0.15rem; width: auto; }

/* :not([hidden]), not a bare ID selector: an ID selector's specificity
   beats the browser's default `[hidden] { display: none }` UA rule, so
   `display: flex` here unconditionally would defeat the `hidden`
   attribute app.js relies on to show/hide this row -- a real bug caught
   in testing, not a hypothetical one. */
#cover-photo-preview-row:not([hidden]) { margin-top: 0.75rem; display: flex; align-items: center; gap: 1rem; }
#cover-photo-preview {
  max-width: 140px;
  max-height: 140px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  object-fit: contain;
  display: block;
}

/* :not([hidden]), not a bare `.btn`: same specificity lesson as
   #cover-photo-preview-row above -- an unconditional `display` here
   would beat the browser's default `[hidden] { display: none }` UA
   rule for every hidden button (a real bug: #error-retry stayed fully
   laid out and rendered on top of #error-subscribe-button with
   hidden=true, found round 50 chasing a "subscribe button does
   nothing" report -- the report itself was a real click landing on
   the wrong, invisibly-stacked button, not a dead event listener). */
.btn:not([hidden]) {
  display: inline-block;
  padding: 0.75rem 1.5rem;
  border-radius: var(--radius-sm);
  border: none;
  font-size: 0.95rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  cursor: pointer;
  text-decoration: none;
  text-align: center;
}
.btn-primary { background: var(--terracotta); color: var(--on-terracotta); }
.btn-primary:hover { background: var(--terracotta-deep); }
.btn-primary:active { background: var(--terracotta-ember); }
.btn-primary:disabled { background: var(--border); color: var(--ink-soft); cursor: not-allowed; transform: none; }
/* .btn-secondary:not([hidden]), not a bare `.btn-secondary`: same
   specificity trap as `.btn:not([hidden])` above -- that rule's own
   `border: none` (specificity 0,2,0) was silently beating this rule's
   `border: 1px solid var(--border)` (0,1,0) everywhere in the app, so
   every secondary button (Back, Remove, Send magic link, ...) never
   actually rendered its intended border. Real bug, caught checking the
   sign-in panel, not specific to it. */
.btn-secondary:not([hidden]) { background: var(--vellum); color: var(--ink); border: 1px solid var(--border); }
.btn-secondary:hover { background: var(--vellum-deep); }
.btn-secondary:disabled { color: var(--ink-soft); cursor: not-allowed; transform: none; }

dl.summary { border-top: 1px solid var(--border); }
dl.summary div, dl#provenance-list div {
  display: flex;
  justify-content: space-between;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--border);
  font-size: 0.92rem;
  gap: 1rem;
}
dl.summary dt, dl#provenance-list dt { color: var(--ink-soft); }
dl.summary dd, dl#provenance-list dd { margin: 0; text-align: right; }

.spinner {
  width: 28px; height: 28px;
  border: 3px solid var(--border);
  border-top-color: var(--terracotta);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
  margin-top: 1rem;
}
@keyframes spin { to { transform: rotate(360deg); } }

.provenance { margin: 1.25rem 0; }
.provenance summary { cursor: pointer; color: var(--ink-soft); font-size: 0.88rem; }

.error-text { color: var(--error); }

.site-footer {
  text-align: center;
  color: var(--ink-soft);
  font-size: 0.78rem;
  padding: 2rem 1rem;
}

#build-another { margin-top: 0.75rem; display: block; }
#download-link { margin: 0.75rem 0; }

/* Entrance-only animation for step changes -- Category -> Occasion ->
   Details -> Build, and the sub-screens within Build (review -> loading
   -> result/error). Occasional-frequency (a handful of times per build,
   never keyboard-repeated), so real motion is warranted per the
   animation decision framework -- a plain keyframe on :not([hidden])
   plays automatically the moment display flips from none, no JS timing
   needed, and no @starting-style browser-support gamble. Enter-only,
   not a full crossfade: the outgoing step disappears instantly under
   the incoming one's fade, which reads fine in a linear, one-screen-
   at-a-time wizard -- see emil-design-eng's asymmetric enter/exit note. */
.step:not([hidden]),
#review-panel:not([hidden]), #loading-panel:not([hidden]),
#content-panel:not([hidden]), #result-panel:not([hidden]), #error-panel:not([hidden]) {
  animation: step-in 220ms var(--ease-out);
}
@keyframes step-in {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: translateY(0); }
}

/* Respect the OS-level reduced-motion preference: keep opacity/color
   transitions (they aid comprehension), drop the press-scale motion. */
@media (prefers-reduced-motion: reduce) {
  button, a.btn, .card, .quick-pick-btn { transition: background 150ms ease, border-color 150ms ease; }
  button:active, a.btn:active, .card:active, .quick-pick-btn:active { transform: none; }
  .step:not([hidden]),
  #review-panel:not([hidden]), #loading-panel:not([hidden]),
  #content-panel:not([hidden]), #result-panel:not([hidden]), #error-panel:not([hidden]) {
    animation: step-in-fade-only 220ms ease;
  }
}
@keyframes step-in-fade-only {
  from { opacity: 0; }
  to { opacity: 1; }
}
