/*
 * Vesper UI layer: base reset + reusable components (ADR-0005).
 * Consumes tokens.css only. Add a new pattern HERE (and to /styleguide), never
 * inline in a view. Every value is a var(--token) or a color-mix() over one.
 */

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

html { -webkit-text-size-adjust: 100%; }

/* The hidden attribute must win over a component's display (e.g. .tarot-card). */
[hidden] { display: none !important; }

body {
  margin: 0;
  background: var(--color-bg);
  color: var(--color-text);
  font-family: var(--font-sans);
  font-size: var(--text-base);
  line-height: var(--leading-normal);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

a { color: inherit; }
img, svg { max-width: 100%; display: block; }

h1, h2, h3 {
  font-family: var(--font-serif);
  font-weight: var(--weight-semibold);
  color: var(--color-heading);
  line-height: var(--leading-snug);
  margin: 0;
}

/* Visible keyboard focus for every interactive element. */
:where(a, button, input, textarea, select, [tabindex]):focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* -------------------------------------------------------------------- Layout */
.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--space-6);
}
.container--narrow { max-width: var(--container-narrow); }

.stack > * + * { margin-top: var(--space-4); }
.stack-lg > * + * { margin-top: var(--space-8); }

/* --------------------------------------------------------------- Typography */
.display {
  font-family: var(--font-serif);
  font-weight: var(--weight-semibold);
  font-size: var(--text-display);
  line-height: var(--leading-tight);
  color: var(--color-heading);
  letter-spacing: -0.01em;
  text-wrap: balance;
  margin: 0;
}
.heading { font-size: var(--text-xl); }
.heading--lg { font-size: var(--text-2xl); }

.eyebrow {
  text-transform: uppercase;
  letter-spacing: var(--tracking-eyebrow);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  color: var(--color-eyebrow);
  margin: 0;
}

.lede {
  font-size: var(--text-md);
  line-height: var(--leading-normal);
  color: var(--color-text-muted);
  margin: 0;
}
.muted { color: var(--color-text-muted); }
.micro { font-size: var(--text-sm); color: var(--color-text-muted); }

/* Read-only rich text: the paragraphs simple_format emits from writing already
   set down -- a Reflection in the Journal, a kept Threshold's writing (ADR-0013).
   Both surfaces read it back through this one component, so a Reflection reads
   the same wherever it is read back. Token rhythm between paragraphs, and
   overflow-wrap so a pasted URL (free text with no break opportunity in it)
   wraps instead of pushing past the surface at 360px. */
.prose > p { margin: 0; overflow-wrap: anywhere; }
.prose > p + p { margin-top: var(--space-3); }

/* An inline text link: a quiet accent underline for an action woven into a line of
   copy (e.g. setting up your Chart from the day's thread), where a full button would
   sit too loudly. Focus is handled by the global :focus-visible outline. */
.link {
  color: var(--color-accent);
  text-decoration: underline;
  text-decoration-color: color-mix(in srgb, var(--color-accent) 45%, transparent);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
  border-radius: var(--radius-sm);
  transition: text-decoration-color var(--dur) var(--ease-out);
}
.link:hover { text-decoration-color: var(--color-accent); }

/* ----------------------------------------------------------------- Buttons */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  font-family: var(--font-sans);
  font-weight: var(--weight-semibold);
  font-size: var(--text-md);
  line-height: 1;
  text-decoration: none;
  cursor: pointer;

  /* Transparent rather than none: .btn--ghost draws a real 1px border, and with
     no explicit height a border adds to the box whatever box-sizing says. Without
     this a ghost and a solid button of the same size class stand 2px apart
     wherever they sit side by side -- the hero pair, and Sign in beside Begin in
     the public header. */
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  padding: var(--space-4) var(--space-8);
  background: var(--color-accent);
  color: var(--color-accent-ink);
  transition: transform var(--dur-fast) var(--ease-out),
              background var(--dur-fast) var(--ease-out);
}
.btn:hover:not(:disabled) { background: color-mix(in srgb, var(--color-accent) 86%, var(--tint-light)); transform: translateY(-1px); }
.btn:active { transform: translateY(0); }

/* Not ready rather than not offered. The Print's share control waits for the
   sheet to finish drawing (ADR-0011), so it has to read as a button whose turn
   has not come -- half opacity, the same "unavailable" the switch uses. */
.btn:disabled { opacity: 0.5; cursor: not-allowed; }

.btn--ghost {
  background: transparent;
  color: var(--color-text);
  border: 1px solid var(--color-border-strong);
}
.btn--ghost:hover:not(:disabled) { background: color-mix(in srgb, var(--star) 8%, transparent); border-color: var(--color-text-muted); }

.btn--sm { font-size: var(--text-sm); padding: var(--space-3) var(--space-5); }
.btn--lg { font-size: var(--text-lg); padding: var(--space-4) var(--space-10); }

/* Full-width within its container: keeps a long label (notably the longer es-MX
   CTAs, e.g. "Guardar hora de nacimiento") from spilling a narrow form card at
   360px. Pair with single-column forms. */
.btn--block { width: 100%; }

/* ----------------------------------------------------------- Surfaces / cards */
.surface {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xl);
  padding: var(--space-8);
}

.card {
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-6);
  box-shadow: var(--shadow-lg);
}

.rule { height: 1px; border: 0; background: var(--color-border); margin-block: var(--space-12); }

/* The recurring star glyph (wordmark, accents). Decorative. Mark aria-hidden. */
.star {
  display: inline-block;
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--color-accent);
  box-shadow: var(--glow-amber);
}

/* The Vesper mark: the gold eight-pointed star (shared/_mark), inlined so it
   renders anywhere without the asset pipeline fingerprinting an SVG. Sized by
   the context it sits in via --mark-size; the token-driven gradient and house
   amber glow give it the same warmth as the .star dot it replaces. Decorative. */
.vesper-mark {
  --mark-size: 1em;

  display: inline-block;
  flex: none;
  width: var(--mark-size);
  height: var(--mark-size);
  filter: drop-shadow(0 0 7px color-mix(in srgb, var(--amber) 70%, transparent));
}
.vesper-mark__stop--light { stop-color: var(--amber); }
.vesper-mark__stop--deep  { stop-color: var(--amber-strong); }

/* Beside a wordmark (app + marketing headers, footer): a touch larger than the
   cap height so the star reads as a logo, not a bullet. */
.vesper-mark--lg { --mark-size: 1.5em; }

/* The focal seal on a face-down card back: a calm, glowing star at rest. */
.vesper-mark--seal {
  --mark-size: 4rem;

  filter: drop-shadow(0 0 18px color-mix(in srgb, var(--amber) 70%, transparent));
}

/* The chosen Mood marker: the star fills the 24px scale circle's footprint exactly,
   so it reads as the focal point while staying in the row's rhythm and optically
   aligned with the empty rings (sizing it past 24px pushed it out of line). The glow
   stays contained so it never bleeds into the neighbours. */
.vesper-mark--mood {
  --mark-size: 24px;

  filter: drop-shadow(0 0 4px color-mix(in srgb, var(--amber) 60%, transparent));
}

/* ------------------------------------------------------------------- Fields */
.field { display: grid; gap: var(--space-3); text-align: left; }

.field__label {
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--color-eyebrow);
}
.field__hint { font-size: var(--text-sm); color: var(--color-text-muted); }

.input, .textarea {
  font: inherit;
  font-size: var(--text-base);
  width: 100%;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-sm);
  border: 1px solid var(--color-border-strong);
  background: var(--color-surface-raised);
  color: var(--color-text);
  color-scheme: dark;
}
.textarea { min-height: 8rem; resize: vertical; line-height: var(--leading-normal); }
.input::placeholder, .textarea::placeholder { color: color-mix(in srgb, var(--mist) 70%, transparent); }

/* Native date & time inputs. iOS/WebKit sizes these to its own intrinsic control
   metrics that ignore our box model: wider than the text input, and — once the
   native appearance is dropped — shorter too (they collapse to their empty
   content instead of a line-height). Strip the appearance (same trick as the
   custom .select above) so width:100% + our padding apply, then pin the height to
   the text field's and centre the value. All three fields end up identical.
   The min-height mirrors the text input: one line (font-size × leading) + the
   block padding on both sides + the 1px border top and bottom. */
.input[type="date"],
.input[type="time"] {
  -webkit-appearance: none;
  appearance: none;
  display: flex;
  align-items: center;
  min-width: 0;
  min-height: calc(var(--text-base) * var(--leading-normal) + var(--space-3) * 2 + 2px);
}

/* Select: the native control with our own chevron, given room to breathe from
   the edge (feedback: the platform arrow crowded the border). The chevron is a
   token-coloured caret (same border trick as the disclosure marker, so no raw
   hex), drawn by the wrapper; the select reserves inline-end space for it. */
.select { position: relative; display: grid; }

.select > select.input {
  appearance: none;
  -webkit-appearance: none;
  padding-inline-end: var(--space-10);
}

.select::after {
  content: "";
  position: absolute;
  inset-inline-end: var(--space-4);
  top: 50%;
  width: 0.5rem;
  height: 0.5rem;
  border-right: 2px solid var(--color-text-muted);
  border-bottom: 2px solid var(--color-text-muted);
  transform: translateY(-70%) rotate(45deg);
  pointer-events: none;
}

/* ----------------------------------------------------------- Numbered steps */

/* The Draw -> Read the sky -> Reflect ritual pattern. */
.steps { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-12); }
.steps__item { max-width: 30rem; }

.steps__num {
  font-family: var(--font-serif);
  font-size: var(--text-sm);
  letter-spacing: 0.1em;
  color: var(--color-accent);
}
.steps__title { font-size: var(--text-xl); margin: var(--space-2) 0; }
.steps__body { margin: 0; color: var(--color-text-muted); }

/* --------------------------------------------------------------------- Chip */
.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-pill);
  border: 1px solid var(--color-border-strong);
  color: var(--color-text-muted);
}

/* ===================================================== Product components ===
   Patterns the MVP screens need (issues 01-09). Compose these; don't re-create
   them inline. Showcased in /styleguide. */

/* App bar: the authenticated shell header (issue 01). */
.appbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2) var(--space-4);

  /* Only the block padding here -- leave the inline padding to .container, so the
     brand and burger don't hug the screen edges. (A `padding: x 0` shorthand would
     zero the container's inline padding.) */
  padding-block: var(--space-4);
  border-bottom: 1px solid var(--color-border);
}

.appbar__brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-family: var(--font-serif);
  font-size: var(--text-lg);
  color: var(--color-heading);
  text-decoration: none;
}

/* Wrap, so the longer es-MX chrome ("Diario / Carta / Ajustes" + "Cerrar
   sesión") drops the sign-out pill to its own line whole at 360px rather than
   overflowing or breaking the label mid-word. */
.appbar__nav { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3) var(--space-5); }
.appbar__nav .btn { white-space: nowrap; }

/* Plain destination links only: a direct child that is not a .btn. This lets the
   nav also host buttons (.btn) and nested components (the language toggle) without
   this muted-link rule clobbering their colour. */
.appbar__nav > a:not(.btn) {
  padding-block: var(--space-2);
  color: var(--color-text-muted);
  text-decoration: none;
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
}
.appbar__nav > a:not(.btn):hover { color: var(--color-text); }

/* Burger: the mobile menu trigger (issue 02). Hidden by default -- it is both the
   no-JS state (the controller reveals it) and the wide-screen state; the media
   query below shows it only on narrow, enhanced headers. */
.appbar__burger {
  display: none;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  padding: 0;
  background: transparent;
  color: var(--color-text);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  cursor: pointer;
}

/* Three stacked bars: the element is the middle bar, its pseudo-elements the
   outer two. currentColor so it inherits the button's colour (no raw hex). */
.appbar__burger-bars,
.appbar__burger-bars::before,
.appbar__burger-bars::after {
  content: "";
  display: block;
  width: 1.15rem;
  height: 2px;
  border-radius: 1px;
  background: currentColor;
}
.appbar__burger-bars { position: relative; }
.appbar__burger-bars::before { position: absolute; top: -6px; }
.appbar__burger-bars::after { position: absolute; top: 6px; }

/* The logged-out site footer, shared chrome across the marketing landing and the
   legal pages (shared/_marketing_footer). Three tracks so the links stay centred
   in the footer regardless of the brand's and copyright's widths; it collapses to
   a centred stack below the narrow breakpoint. */
.mk__footer {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: var(--space-4);
  padding-block: var(--space-12);
  margin-top: var(--space-12);
  border-top: 1px solid var(--color-border);
}

.mk__footer-brand {
  justify-self: start;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-family: var(--font-serif);
  font-size: var(--text-base);
  color: var(--color-heading);
}
.mk__footer-links { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--space-5); }

.mk__footer-link {
  color: var(--color-text-muted);
  text-decoration: none;
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
}
.mk__footer-link:hover { color: var(--color-text); }
.mk__footer-copyright { justify-self: end; margin: 0; color: var(--color-text-muted); font-size: var(--text-sm); }

/* Below the narrow breakpoint the three tracks would crowd; stack them centred. */
@media (max-width: 40rem) {
  .mk__footer { grid-template-columns: 1fr; justify-items: center; text-align: center; gap: var(--space-5); }
  .mk__footer-brand, .mk__footer-copyright { justify-self: center; }
}

/* Drawer: the mobile nav in a native <dialog>, anchored full-height to the inline
   end. showModal() supplies the focus trap, Esc-to-close and inert background;
   this styles it and gives it a calm slide. On open the panel slides in from the
   inline end and the backdrop fades up; on close both reverse. display and overlay
   transition with allow-discrete so the panel stays rendered and in the top layer
   through the close. Motion runs on --dur, which is zeroed under
   prefers-reduced-motion -- there the panel simply appears, no slide. */
.drawer {
  position: fixed;
  inset-block: 0;

  /* Anchor to the inline end (right in LTR). inset-inline-start must be reset to
     auto: the UA dialog stylesheet pins BOTH sides to 0, and with a fixed width
     that over-constrains the box to the START edge, ignoring inset-inline-end --
     which is why the panel sat on the left and fought the slide-in. */
  inset-inline: auto 0;
  height: 100dvh;
  width: min(20rem, 82vw);
  max-width: none;
  max-height: none;
  margin: 0;
  padding: 0;
  border: 0;
  border-inline-start: 1px solid var(--color-border);

  /* Opaque: this is a full-height overlay panel, so the page behind it must be
     hidden. --color-surface is a 4%-white glassy tint meant for small cards ON
     the page; used here it let the page show straight through the drawer. */
  background: var(--color-surface-raised);
  color: var(--color-text);
  box-shadow: var(--shadow-lg);
  translate: 0 0;
  transition:
    translate var(--dur) var(--ease-out),
    overlay var(--dur) var(--ease-out) allow-discrete,
    display var(--dur) var(--ease-out) allow-discrete;
}

/* Slide in from the inline end on open (@starting-style), back out on close. */
@starting-style {
  .drawer[open] { translate: 100% 0; }
}
.drawer:not([open]) { translate: 100% 0; }

.drawer::backdrop {
  background: color-mix(in srgb, var(--night-900) 68%, transparent);
  transition:
    background var(--dur) var(--ease-out),
    overlay var(--dur) var(--ease-out) allow-discrete,
    display var(--dur) var(--ease-out) allow-discrete;
}

/* Fade the dim in/out with the panel. */
@starting-style {
  .drawer[open]::backdrop { background: transparent; }
}
.drawer:not([open])::backdrop { background: transparent; }

.drawer__panel {
  display: grid;
  gap: var(--space-2);
  align-content: start;
  height: 100%;
  padding: var(--space-6);
}

.drawer__close {
  justify-self: end;
  width: 2.5rem;
  height: 2.5rem;
  padding: 0;
  background: transparent;
  color: var(--color-text-muted);
  border: 0;
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.drawer__close:hover { color: var(--color-text); }
.drawer__close-glyph { font-size: var(--text-2xl); line-height: 1; }

.drawer__nav { display: grid; gap: var(--space-1); }

/* Plain destination links only (see .appbar__nav): a direct child that is not a
   .btn, so the drawer can also hold buttons and the nested language toggle. */
.drawer__nav > a:not(.btn) {
  padding: var(--space-3) var(--space-2);
  color: var(--color-text);
  text-decoration: none;
  font-size: var(--text-md);
  font-weight: var(--weight-semibold);
}
.drawer__nav > a:not(.btn):hover { color: var(--color-heading); }

/* Sign-out is a button_to, so the <form> is the grid item -- align and space that,
   not the inner .btn (which would be inert). */
.drawer__nav form { justify-self: start; margin-top: var(--space-4); }

/* The public header stacks its actions full-width in the drawer, with the
   language toggle set off below them (mirrors the app drawer's sign-out spacing).
   justify-self:center shrinks the toggle to just its two options and centres it,
   rather than letting the grid stretch it across the panel. */
.drawer__nav .lang-toggle { justify-self: center; margin-top: var(--space-4); }

/* The drawer's actions are full-size .btn (not btn--sm), so grow the toggle
   options to stand the same height beside them. */
.drawer__nav .lang-toggle__option { padding-block: var(--space-3); }

/* Below the breakpoint, and only once JS has enhanced the header: the inline nav
   gives way to the burger + drawer. Above it (and with JS off) the inline nav
   stays, so every destination is always reachable. */
@media (max-width: 40rem) {
  /* Pin the bar to the top on mobile so it stays reachable through a long scroll
     (one-handed, as an installed PWA). Sticky keeps it in flow -- no content
     offset needed -- with an opaque, page-matched background so scrolled content
     passes cleanly beneath its bottom border. z-index sits below the flash toast
     (z 100) so a passing confirmation still floats over the bar. */
  .appbar {
    position: sticky;
    top: 0;
    z-index: 50;
    background: var(--color-bg);
  }

  /* Centre the wordmark on mobile (feedback): a three-column grid puts the brand
     in the middle cell and the burger at the inline end, with a matching empty
     cell balancing it so the brand reads as centred in the bar. The inline nav
     gives way to the burger. Only once enhanced -- the no-JS header keeps the
     inline nav and its space-between flow, so every destination stays reachable. */
  .appbar--enhanced {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
  }
  .appbar--enhanced .appbar__nav { display: none; }
  .appbar--enhanced .appbar__brand { grid-column: 2; justify-self: center; }
  .appbar--enhanced .appbar__burger { display: inline-flex; grid-column: 3; justify-self: end; }
}

/* While the drawer is open, hold the page still behind it. */
.is-scroll-locked { overflow: hidden; }

/* --------------------------------------------------------------------- Print */

/* The Print (ADR-0011): the shareable sheet of one Draw. Its drawer is the nav
   drawer's <dialog> -- the same focus trap, Esc-to-close and calm slide, zeroed
   under prefers-reduced-motion -- widened to cradle a 9:16 sheet and made
   scrollable, because the sheet plus its controls outruns a short viewport. */
.drawer--print { width: min(26rem, 92vw); }
.drawer--print .drawer__panel { gap: var(--space-4); height: 100%; overflow-y: auto; }

.print__title { margin: 0; font-family: var(--font-serif); font-size: var(--text-xl); color: var(--color-heading); }
.print__lede { margin: 0; color: var(--color-text-muted); font-size: var(--text-sm); }

/* The canvas is the Print at its full 1080x1920, scaled down to the panel: what
   the drawer shows and what the download hands over are the same pixels, which is
   the only check ADR-0011 keeps on the drawing. */
.print__canvas {
  width: 100%;
  height: auto;
  border-radius: var(--radius);
  background: var(--night-900);
  box-shadow: var(--shadow-lg);
}

/* Holds its line whether or not it is saying anything, so the download does not
   jump when the sheet finishes drawing. */
.print__status { margin: 0; min-height: 1.5em; }

/* ------------------------------------------------------------- Language toggle */

/* A compact segmented control for switching locale (ADR-0006), used in the public
   header inline and in its drawer. Each option is a link that records the choice;
   the active one is filled with a warm amber tint (tying it to the accent) that
   reads on both the page (night-900) and the drawer surface (night-800). */
.lang-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-1);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-pill);
  background: var(--color-surface);
}

.lang-toggle__option {
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-pill);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);

  /* Match .btn's line-height so the toggle stands the same height as the sibling
     buttons; the inherited 1.6 made it noticeably taller. */
  line-height: 1;
  color: var(--color-text-muted);
  text-decoration: none;
  white-space: nowrap;
  transition: color var(--dur-fast) var(--ease-out),
              background var(--dur-fast) var(--ease-out);
}
.lang-toggle__option:hover { color: var(--color-text); }

.lang-toggle__option[aria-current="true"] {
  background: color-mix(in srgb, var(--amber) 22%, transparent);
  color: var(--color-heading);
}

/* Notice: an inline message, one of info (default), error, or success (issues 01, 03). */
.notice {
  border: 1px solid var(--color-border-strong);
  border-left-width: 3px;
  border-radius: var(--radius);
  padding: var(--space-4) var(--space-5);
  background: var(--color-surface);
  color: var(--color-text);

  /* A notice can name an email address, which offers no break opportunity of
     its own and would otherwise overflow a 360px viewport. */
  overflow-wrap: anywhere;
}
.notice--error { border-left-color: var(--color-danger); }
.notice--success { border-left-color: var(--color-accent); }

/* Flash region: a floating toast pinned to the top, OVER the page, so a passing
   confirmation never pushes the header (and everything below it) down (issue 01,
   feedback). The container is click-through; each toast re-enables pointer events.
   Safe-area aware for an installed PWA. */
.flash {
  position: fixed;
  inset-block-start: max(var(--space-4), env(safe-area-inset-top));
  inset-inline: var(--space-4);
  z-index: 100;
  display: grid;
  justify-items: center;
  gap: var(--space-2);
  pointer-events: none;
}

.flash__toast {
  pointer-events: auto;
  width: min(34rem, 100%);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);

  /* Opaque (not the glassy --color-surface the base .notice uses): a toast sits
     over live content, so the page must not read through it. */
  background: var(--color-surface-raised);
  box-shadow: var(--shadow);
  animation: flash-in var(--dur) var(--ease-out);
}

@keyframes flash-in {
  from { opacity: 0; transform: translateY(-8px); }
  to { opacity: 1; transform: none; }
}

/* Exit: the controller adds .is-leaving, then removes the node once this settles
   (a timeout backstops the transition, which is zeroed under reduced motion). */
.flash__toast.is-leaving {
  opacity: 0;
  transform: translateY(-8px);
  transition: opacity var(--dur) var(--ease-out), transform var(--dur) var(--ease-out);
}

/* overflow-wrap: a flash can name an email address, which has no break
   opportunity of its own and would otherwise push the toast past a 360px
   viewport. */
.flash__message { min-width: 0; overflow-wrap: anywhere; }

.flash__dismiss {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  padding: 0;
  margin: calc(var(--space-1) * -1) calc(var(--space-1) * -1) calc(var(--space-1) * -1) 0;
  background: transparent;
  color: var(--color-text-muted);
  border: 0;
  border-radius: var(--radius-sm);
  font-size: var(--text-lg);
  line-height: 1;
  cursor: pointer;
}
.flash__dismiss:hover { color: var(--color-text); }

/* Auth shell: the calm, narrow panel layout shared by the sign-in and
   confirm-sign-in screens (issue 01). Pair with .container--narrow. */
.auth { padding-block: var(--space-16) var(--space-20); }

/* Empty state (issue 04, the empty "today"). Roomy, unhurried spacing so the
   invitation reads calm rather than cluttered (feedback). */
.empty {
  display: grid;
  justify-items: center;
  gap: var(--space-5);
  padding: var(--space-16) var(--space-6);
  text-align: center;
}

.empty__glyph {
  width: 14px;
  height: 14px;
  margin-bottom: var(--space-2);
  border-radius: 50%;
  background: var(--color-accent);
  box-shadow: var(--glow-amber);
}

.empty__title {
  margin: 0;
  font-family: var(--font-serif);
  font-size: var(--text-xl);
  color: var(--color-heading);
}
.empty__body { margin: 0; max-width: 28rem; color: var(--color-text-muted); line-height: var(--leading-normal); }

/* A clear beat before the call to action. */
.empty .btn { margin-top: var(--space-3); }

/* Framed by a .surface, the empty state doesn't need its own tall standalone
   block padding on top of the card's -- it reads as a cavernous box. Halve it so
   the card hugs the invitation while staying calm. */
.surface > .empty { padding-block: var(--space-8); }

/* Badge: orientation / pass / status label (issues 02, 09). */
.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-1) var(--space-3);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-pill);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--color-text-muted);
}

.badge--accent {
  color: var(--color-accent);
  border-color: color-mix(in srgb, var(--color-accent) 50%, transparent);
}

.badge--reversed {
  color: var(--ember);
  border-color: color-mix(in srgb, var(--ember) 50%, transparent);
}

/* Tarot card: the Draw (issue 02). Face-up (art slot, name, meaning) or
   face-down (--facedown). */
.tarot-card {
  display: grid;
  gap: var(--space-4);
  width: 100%;
  max-width: 20rem;
  padding: var(--space-6);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: linear-gradient(165deg, var(--night-700), var(--night-900));
  box-shadow: var(--shadow-lg);
}

.tarot-card__art {
  display: grid;
  place-items: center;
  aspect-ratio: 3 / 5;
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  background:
    radial-gradient(circle at 50% 38%, color-mix(in srgb, var(--color-accent) 70%, transparent), transparent 60%),
    var(--night-800);
}

/* Real card art fills the slot; the night base matches any cover-crop letterbox
   and keeps the engraving dissolving into the dark card. */
.tarot-card__art--photo {
  padding: 0;
  overflow: hidden;
  background: var(--night-900);
}

.tarot-card__art--photo img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: inherit;
}

/* A reversed Draw hangs as it actually fell -- the art turned a half-turn -- so the
   orientation reads at a glance, the way a physical reversed card would. Only the art
   turns: the badge, card name, and meaning stay upright and legible. This is the
   card's resting orientation, not motion, so it stays turned under reduced-motion. */
.tarot-card__art--reversed img { transform: rotate(180deg); }

/* Face-down card back: the art slot as a calm panel cradling the Vesper mark. */
.tarot-card__art--back {
  background:
    radial-gradient(circle at 50% 42%, color-mix(in srgb, var(--color-accent) 16%, transparent), transparent 58%),
    var(--night-800);
}

/* The orientation badge, pinned to the top of the card on its own row: a grid item
   stretches by default, so hold it to its content and centre it over the card. */
.tarot-card__badge { justify-self: center; }

.tarot-card__name {
  margin: 0;
  font-family: var(--font-serif);
  font-size: var(--text-xl);
  color: var(--color-heading);
}
.tarot-card__meaning { margin: 0; color: var(--color-text-muted); }

/* Face-down card: the art slot becomes the calm panel (--back) cradling the
   Vesper mark, so it keeps the same 3:5 card shape as the face-up side instead
   of a squat box. In a flip (marketing sample), both faces share one grid cell
   sized to the taller face-up side; align-content:space-between then pins this
   shorter face's art to the top -- matching the face-up art's size and position --
   and drops the caption to the bottom, rather than letting the spare height inflate
   the art. Standalone (no flip / empty back) there is no spare height, so this is
   a no-op. */
.tarot-card--facedown {
  text-align: center;
  align-content: space-between;
}

/* Flip card: the two faces are stacked in one grid cell, so the card keeps a
   constant size; .is-flipped turns it with a calm 3D rotation (motion zeroed
   under prefers-reduced-motion). The /today draw flips in place via a Turbo
   Stream instead of reloading the page. */
.card-flip {
  display: grid;
  width: 100%;
  max-width: 20rem;
  perspective: 1400px;
}

.card-flip > [data-card-flip-target] {
  grid-area: 1 / 1;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  transition: transform var(--dur-slow) var(--ease-out);
}
.card-flip > [data-card-flip-target="back"] { transform: rotateY(180deg); }
.card-flip.is-flipped > [data-card-flip-target="front"] { transform: rotateY(-180deg); }
.card-flip.is-flipped > [data-card-flip-target="back"] { transform: rotateY(0deg); }
.card-flip .tarot-card { max-width: none; }
.card-flip__back { display: grid; }

/* Mood: a 1..5 scale, a radio group (issue 03). Visually-hidden inputs. The
   endpoint anchors name where the scale runs from and to, and the readout (in the
   hint) echoes the selected marker's meaning, so the bare circles are no longer
   unlabelled (feedback). */
.mood { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3); }
.mood__scale { display: inline-flex; gap: var(--space-2); }
.mood__anchor { font-size: var(--text-sm); color: var(--color-text-muted); }
.mood__readout { color: var(--color-accent); font-weight: var(--weight-semibold); }

.mood__readout:not(:empty)::after {
  content: " · ";
  color: var(--color-text-muted);
  font-weight: var(--weight-normal);
}

.mood input {
  position: absolute;
  width: 0;
  height: 0;
  opacity: 0;
}

.mood label {
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border: 1px solid var(--color-border-strong);
  border-radius: 50%;
  cursor: pointer;
  transition: transform var(--dur-fast) var(--ease-out);
}
.mood label:hover { transform: scale(1.08); }

/* The chosen marker becomes a small Vesper star: hidden on every marker, revealed
   on the checked one, which drops its ring so the star stands on its own. The star
   is a shape cue (not color-only) for the selected state. */
.mood label .vesper-mark {
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease-out);
}
.mood input:checked + label { border-color: transparent; }
.mood input:checked + label .vesper-mark { opacity: 1; }

.mood input:focus-visible + label {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
}

/* Transit: an aspect line plus a plain-language explainer (issue 06). */
.transit { padding: var(--space-4) 0; border-bottom: 1px solid var(--color-border); }
.transit__line { font-size: var(--text-md); color: var(--color-text); }
.transit__line b { font-weight: var(--weight-semibold); color: var(--color-heading); }
.transit__aspect { color: var(--color-accent); }
.transit__why { margin: var(--space-2) 0 0; font-size: var(--text-sm); color: var(--color-text-muted); }

/* Placement: a natal chart row of planet, sign, (house) (issue 05). */
.placement {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-3) 0;
  border-bottom: 1px solid var(--color-border);
}
.placement__planet { font-family: var(--font-serif); font-size: var(--text-md); color: var(--color-heading); }
.placement__sign { color: var(--color-text); }
.placement__house { font-size: var(--text-sm); color: var(--color-text-muted); }

/* Unlock: a locked-feature prompt, e.g. add birth time (issue 05, ADR-0002). */
.unlock {
  display: grid;
  justify-items: start;
  gap: var(--space-3);
  padding: var(--space-5);
  border: 1px dashed var(--color-border-strong);
  border-radius: var(--radius);
  background: var(--color-surface);
}
.unlock__title { margin: 0; font-family: var(--font-serif); font-size: var(--text-md); color: var(--color-heading); }
.unlock__body { margin: 0; font-size: var(--text-sm); color: var(--color-text-muted); }

/* Streak: consecutive-day count (issue 07). A quiet marker of continuity, not a
   score: a serif count in the heading ink (not the amber accent, which would read
   as a hero metric beside the date) at large-body size, with a muted label. */
.streak { display: inline-flex; align-items: baseline; gap: var(--space-2); }
.streak__count { font-family: var(--font-serif); font-size: var(--text-lg); color: var(--color-heading); }
.streak__label { font-size: var(--text-sm); color: var(--color-text-muted); }

/* Switch: a settings toggle (issue 08). The vertical padding gives a
   comfortable one-handed hit area around the 24px track. */
.switch {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  padding-block: var(--space-2);
  cursor: pointer;
}

.switch input {
  position: absolute;
  width: 0;
  height: 0;
  opacity: 0;
}

.switch__track {
  position: relative;
  flex: none;
  width: 42px;
  height: 24px;
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-pill);
  background: var(--color-surface-raised);
  transition: background var(--dur-fast) var(--ease-out);
}

/* Unsupported / disabled: the toggle reads as unavailable, not merely off. */
.switch:has(input:disabled) { cursor: not-allowed; color: var(--color-text-muted); }
.switch input:disabled + .switch__track { opacity: 0.5; }

.switch__track::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--color-text-muted);
  transition: transform var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
}

.switch input:checked + .switch__track {
  border-color: var(--color-accent);
  background: color-mix(in srgb, var(--color-accent) 35%, transparent);
}

.switch input:checked + .switch__track::after {
  transform: translateX(18px);
  background: var(--color-accent);
}

.switch input:focus-visible + .switch__track {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
}

/* Entry list: the Journal stream as an ordered list (issue 04). Wrap in a
   .surface; the list itself just strips the default list chrome. */
.entry-list { list-style: none; margin: 0; padding: 0; }
.entry-list > li:last-child .entry-summary { border-bottom: 0; }

/* Entry summary: a Journal list row (issue 04). */
.entry-summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-4) 0;
  border-bottom: 1px solid var(--color-border);
  color: inherit;
  text-decoration: none;
}
.entry-summary:hover .entry-summary__date { color: var(--color-heading); }
.entry-summary__date { font-family: var(--font-serif); font-size: var(--text-md); color: var(--color-text); }

.entry-summary__glance {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  text-align: right;
}

/* Below the narrowest target, stack the date over its glance so neither wraps
   mid-phrase. */
@media (max-width: 28rem) {
  .entry-summary { flex-direction: column; align-items: flex-start; gap: var(--space-1); }
  .entry-summary__glance { text-align: left; }
}

/* A small dot marking that a Mood was recorded for the day. The exact level is
   on the Entry itself; here it is a quiet presence cue, labelled for assistive
   tech. */
.entry-summary__mood {
  flex: none;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--color-accent);
  box-shadow: var(--glow-amber);
}

/* Skeleton: a loading placeholder for async screens (issues 02, 03, 05, 06).
   Block by default so callers only set a width, not display. */
.skeleton {
  display: block;
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--star) 9%, transparent);
  animation: skeleton-pulse 1.4s var(--ease-out) infinite;
}
.skeleton--line { height: 0.9em; }
.skeleton--block { height: 8rem; }

@keyframes skeleton-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.45; }
}

@media (prefers-reduced-motion: reduce) {
  .skeleton { animation: none; }
}

/* Visually-hidden text: present for screen readers, removed from the visual
   layout. Pair with an aria-hidden decorative element (e.g. the Mood dot). */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* --------------------------------------------------------------- Disclosure */

/* A calm, optional disclosure (native <details>/<summary>): a muted summary that
   expands to reveal more, for content that is available but never demanded -- the
   optional retrospective Pass (issue 09). Collapsed by default; the summary keeps
   a comfortable hit area in both states, and the only motion is the chevron,
   which rests under prefers-reduced-motion (--dur-fast is zeroed there). Pair the
   .disclosure__hint with the [open] rule to show the invitation only while closed. */
.disclosure { border-top: 1px solid var(--color-border); padding-top: var(--space-8); }

.disclosure__summary {
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  padding-block: var(--space-2);
  color: var(--color-text-muted);
}
.disclosure__summary::-webkit-details-marker { display: none; }

.disclosure__summary::before {
  content: "";
  flex: none;
  width: 0.5rem;
  height: 0.5rem;
  margin-top: 0.45em;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(-45deg);
  transition: transform var(--dur-fast) var(--ease-out);
}
.disclosure[open] .disclosure__summary::before { transform: rotate(45deg); }
.disclosure__summary:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; }
.disclosure__label { display: grid; gap: var(--space-2); }
.disclosure__hint { max-width: 34rem; }
.disclosure[open] .disclosure__hint { display: none; }
.disclosure__body { margin-top: var(--space-8); }

/* Layout helpers. */
.cluster { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-4); }
.cluster--between { justify-content: space-between; }
.passes { display: grid; gap: var(--space-6); }

@media (min-width: 720px) {
  .passes { grid-template-columns: 1fr 1fr; }
}

/* ============================================== Today: the daily home ===
   The authenticated home reads as a calm conversation for the day (the approved
   "thread" direction): a small date divider opens it, a quiet companion line speaks
   before each moment, and the practice is the day-ahead Draw, the sky dial, the
   writing, and the optional look-back. Progressive enhancement: every moment renders
   in order as a scroll with JS off; the Draw flips in place via a Turbo Stream. */

/* The page-bottom padding reserves room for the fixed docked compose (~15rem) so the
   last thread content never hides behind it. */
.today { padding-block: var(--space-6) 15rem; }

/* The thread: the day's moments in a calm vertical rhythm -- close like a chat, not a
   roomy scroll. */
.thread { display: grid; gap: var(--space-5); }

/* The "begun" block (sky + look-back) joins the thread's own rhythm directly, and
   takes no space at all before the first Draw fills it. Same for the deferred
   look-back wrapper, so revealing it drops the divider + card straight into the thread. */
.today__begun,
.thread__later { display: contents; }

/* The date divider: a quiet centred marker that opens the thread, not a hero. */
.thread__day {
  justify-self: center;
  margin: 0;
  font-size: var(--text-xs);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}

/* The evening divider marks a section break, so it gets a little more room above. */
.thread__day--evening { margin-top: var(--space-3); }

/* The streak: a quiet centred line of continuity (issue 07), never a score. Uses
   the shared .streak component; here it just sits centred and close under the date. */
.today__streak { justify-self: center; margin: calc(var(--space-2) * -1) 0 0; justify-content: center; }

/* A companion line: a quiet, left-aligned message bubble (the app speaking one calm
   line before a moment). --soft is the calmer, ambient variant. */
.thread__msg {
  justify-self: start;
  max-width: 30rem;
  margin: 0;
  padding: var(--space-4) var(--space-5);
  border-radius: var(--radius-lg) var(--radius-lg) var(--radius-lg) var(--radius-sm);
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border);
  color: var(--color-text);
  font-size: var(--text-md);
  line-height: var(--leading-normal);
}
.thread__msg--soft { background: var(--color-surface); color: var(--color-text-muted); }

/* A reflection you have set down, read back as your own note: a mirrored bubble with a
   warm tint, opposite the app's soft messages. It eases in when inserted, like a chat
   (motion zeroed under prefers-reduced-motion, where --dur is 0). */
.thread__you {
  justify-self: end;
  max-width: 30rem;
  margin: 0;
  padding: var(--space-4) var(--space-5);
  border-radius: var(--radius-lg) var(--radius-lg) var(--radius-sm) var(--radius-lg);
  background: color-mix(in srgb, var(--amber) 14%, var(--color-surface-raised));
  border: 1px solid color-mix(in srgb, var(--amber) 40%, transparent);
  color: var(--color-text);
  font-size: var(--text-md);
  line-height: var(--leading-normal);
  animation: thread-in var(--dur) var(--ease-out);
}

@keyframes thread-in {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: none; }
}

/* Each moment: the companion line, then its content, in one calm group. */
.thread__moment { display: grid; gap: var(--space-5); }

/* The day-ahead + look-back card moment (F): an orientation eyebrow, the card as a
   tactile keepsake (a framed art face, lifted with a warm amber glow), then its name
   + meaning read below. Shared by /today and the Threshold (ADR-0013) -- a stranger's
   card must be indistinguishable from a Practitioner's -- and by nothing else, so the
   Journal and marketing stay calmer. */
.today__draw { display: grid; gap: var(--space-5); }
.today__card { display: grid; justify-items: center; gap: var(--space-4); text-align: center; }

/* The heading: the section header (context) above, the orientation badge below it,
   both centred. When the draw scrolls the card into view, this is the anchor, so it
   keeps a margin clear of the sticky header. */
.today__card-eyebrow { display: grid; justify-items: center; gap: var(--space-2); color: var(--color-eyebrow); scroll-margin-top: 5.5rem; }

/* The three slots of the card moment (this, the meta below, the invite footer) are
   grid items in every state, so an unfilled one would still take a row gap. Each is
   a Turbo Stream target, so it has to be emitted whether or not it is filled.
   :not(:has(*)) rather than :empty because ERB leaves a newline inside an unfilled
   slot and :empty does not match whitespace -- the guard written as :empty never
   fired once, on either surface, and /today's undrawn state carried two phantom row
   gaps from the day the card moment shipped. Every filled state of these three holds
   element children, so the selector is exact; :modern browsers only (see
   allow_browser). */
.today__card-eyebrow:not(:has(*)) { display: none; }
.today__card-context { text-transform: uppercase; letter-spacing: var(--tracking-eyebrow); }

/* The card sits flat while face-down; once turned it takes a subtle 3D inclination
   (rested, not looping). The stage supplies the perspective for the tilt; the flip's own
   perspective still drives the face turn. */
.today__card-stage { perspective: 1400px; width: 100%; display: grid; place-items: center; }
.today__card .card-flip { max-width: 16rem; transition: transform var(--dur-slow) var(--ease-out); }
.today__card .card-flip.is-flipped { transform: rotateY(-6deg) rotateX(2deg); }

@media (prefers-reduced-motion: reduce) {
  .today__card .card-flip.is-flipped { transform: none; }
}

/* The turned card is the art: a dark card so the thin mat reads as a quiet frame (not a
   pale border), the art filling it, lifted with a soft amber glow. Name + meaning below. */
.card-flip__back.today__card-face {
  padding: 0;
  align-content: center;
  border: 0;
  background: var(--night-900);
  box-shadow: var(--shadow-lg), 0 0 40px -12px color-mix(in srgb, var(--amber) 38%, transparent);
}

/* The RWS artwork already has its own printed border, so drop the component's border
   here -- otherwise the two stack into a double frame. */
.card-flip__back .tarot-card__art { width: 100%; border: 0; }

/* Nothing sits below the turned card now (no locked note), so the footer collapses
   (see the eyebrow above for why this is :has and not :empty). */
.today__invite-footer:not(:has(*)) { display: none; }

/* Face-down card: a single card with a soft amber glow behind the seal and the caption
   read below -- no nested panel, no halo, sat flat. */
.today__facedown {
  aspect-ratio: 3 / 5;
  display: grid;
  align-content: center;
  justify-items: center;
  gap: var(--space-6);
  padding: var(--space-12) var(--space-8);
  background:
    radial-gradient(circle at 50% 44%, color-mix(in srgb, var(--color-accent) 20%, transparent), transparent 52%),
    linear-gradient(165deg, var(--night-800), var(--night-900));
}
.today__facedown-cap { color: var(--color-text-muted); }

.today__card-meta { display: grid; gap: var(--space-2); max-width: 24rem; }
.today__card-meta:not(:has(*)) { display: none; }
.today__card-name { font-family: var(--font-serif); font-size: var(--text-2xl); color: var(--color-heading); }

/* The meaning in two voices, as a Print sets it (ADR-0011). The statement
   identifies the Card, so it stays with the name and stays quiet. */
.today__card-statement { margin: 0; color: var(--color-text-muted); }

/* The question is what the Card asks, so it reads as the card moment's focal
   line: serif in heading ink, set apart from the pair above it. Kept a step under
   .today__card-name (--text-lg to its --text-2xl) -- the Print's poster scale does
   not transfer to a 24rem column, where matching the name would make the two lines
   compete instead of leading one into the other. */
.today__card-question {
  margin: var(--space-2) 0 0;
  font-family: var(--font-serif);
  font-size: var(--text-lg);
  line-height: var(--leading-snug);
  color: var(--color-heading);
  text-wrap: pretty;
}

.today__invite-footer { display: grid; justify-items: center; gap: var(--space-5); }
.today__invite-lede { max-width: 28rem; }
.today__locked { max-width: 22rem; text-align: center; }

/* The writing moment: the compose (F) -- a mood check-in above a compose bar (the
   Reflection field with the Save set into its end). No heavy surface; it reads as a
   place to write, on the night background. */
.compose { display: grid; gap: var(--space-4); }
.mood-check { display: grid; gap: var(--space-3); }

.mood-check__legend {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-3);
  margin: 0; font-size: var(--text-xs); text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--color-text-muted);
}
.mood-check__opt { opacity: 0.6; }
.mood-check__readout { text-transform: none; letter-spacing: normal; font-size: var(--text-sm); }
.mood-check .mood__readout::after { content: ""; }

/* Heavy, the markers, and Bright sit together at the start of the row, packed to just
   the space they need -- not stretched across it. */
.mood-check__scale { display: flex; align-items: center; justify-content: flex-start; gap: var(--space-4); }
.mood-check__scale .mood__scale { gap: var(--space-2); }

.compose__bar {
  display: grid; grid-template-columns: 1fr auto; align-items: end; gap: var(--space-2);
  padding: var(--space-2) var(--space-2) var(--space-2) var(--space-4);
  border: 1px solid var(--color-border-strong); border-radius: var(--radius-xl);
  background: var(--color-surface-raised);
  transition: border-color var(--dur-fast) var(--ease-out);
}

/* The Reflection field drops its own outline (it fills the bar), so the bar carries the
   visible keyboard-focus state for it (WCAG 2.4.7). */
.compose__bar:focus-within { border-color: var(--color-focus); }

.compose__input {
  font: inherit; font-size: var(--text-base); line-height: var(--leading-normal);
  border: 0; background: transparent; color: var(--color-text); resize: none;
  padding-block: var(--space-2); min-height: 2.75rem;
}
.compose__input:focus-visible { outline: none; }
.compose__input::placeholder { color: color-mix(in srgb, var(--mist) 70%, transparent); }

.compose__send {
  flex: none; width: 2.75rem; height: 2.75rem; padding: 0; border: 0; cursor: pointer;
  border-radius: 50%; background: var(--color-accent); color: var(--color-accent-ink);
  font-size: var(--text-lg); line-height: 1;
}
.compose__send:hover { background: color-mix(in srgb, var(--color-accent) 86%, var(--tint-light)); }
.compose__privacy { text-align: right; margin: 0; }

/* The docked compose: a fixed bar at the foot of /today that writes either Pass (the
   approved conversational layout). The thread scrolls behind it; the .today page-bottom
   padding (above) reserves room so nothing hides behind it. Safe-area aware for a PWA. */
.compose-dock {
  position: fixed;
  inset-inline: 0;
  bottom: 0;
  z-index: 60;
  border-top: 1px solid var(--color-border);
  background: linear-gradient(to top, var(--color-bg), color-mix(in srgb, var(--color-bg) 80%, transparent));
  backdrop-filter: blur(8px);
}

.compose-dock__inner {
  padding-block: var(--space-3) max(var(--space-3), env(safe-area-inset-bottom));
}

/* In the dock the compose is compact and the privacy note stays a quiet whisper. */
.compose-dock .compose { gap: var(--space-3); }
.compose-dock .compose__privacy { font-size: var(--text-xs); }

/* The /today chat-style header: the Vesper mark in an avatar beside "Vesper / your
   practice", sticky at the top; a burger opens the shared drawer at every width (this
   surface is a mobile-first app). The inline nav is the no-JS fallback, hidden once
   the drawer controller enhances the header (.appbar--enhanced). */
.today-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3) var(--space-4);
  padding-block: var(--space-4);
  border-bottom: 1px solid var(--color-border);
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--color-bg);
}
.today-header__brand { display: inline-flex; align-items: center; gap: var(--space-3); text-decoration: none; }

.today-header__avatar {
  display: inline-grid;
  place-items: center;
  flex: none;
  width: 2.6rem;
  height: 2.6rem;
  border-radius: 50%;
  border: 1px solid var(--color-border);
  background: var(--color-surface);
}
.today-header__brandtext { display: grid; }
.today-header__name { font-family: var(--font-serif); font-size: var(--text-lg); color: var(--color-heading); line-height: 1.1; }
.today-header__sub { font-size: var(--text-sm); color: var(--color-text-muted); }

.today-header__burger {
  display: none;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 2.75rem;
  height: 2.75rem;
  padding: 0;
  background: transparent;
  color: var(--color-text);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  cursor: pointer;
}

.today-header__burger-bars,
.today-header__burger-bars::before,
.today-header__burger-bars::after {
  content: "";
  display: block;
  width: 1.15rem;
  height: 2px;
  border-radius: 1px;
  background: currentColor;
}
.today-header__burger-bars { position: relative; }
.today-header__burger-bars::before { position: absolute; top: -6px; }
.today-header__burger-bars::after { position: absolute; top: 6px; }

.today-header__nav { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3) var(--space-5); }
.today-header__nav > a:not(.btn) { color: var(--color-text-muted); text-decoration: none; font-size: var(--text-sm); font-weight: var(--weight-semibold); }
.today-header__nav > a:not(.btn):hover { color: var(--color-text); }

/* Enhanced: the burger replaces the inline nav at every width -- the header stays a
   two-item app bar (brand + burger), the mobile-app feel the approved layout wants. */
.today-header.appbar--enhanced .today-header__nav { display: none; }
.today-header.appbar--enhanced .today-header__burger { display: inline-flex; }

/* The look-back: gentle optional invitation, then its Draw + writing. */
.today__retrospective { display: grid; gap: var(--space-6); }

/* ---------------------------------------------------- Sky dial (E/F, ADR-0007) */

/* The day's sky as a celestial dial: the most significant Transits as points on a
   ring around the Chart at centre; choosing a point surfaces that Transit's reading
   below. The outer arc marks how far the day has come. Fine-line and celestial,
   never a data dashboard. Its point positions are aesthetic, not astronomical. */
.today__transits { display: grid; gap: var(--space-4); }
.transits__full { border-top: 0; padding-top: 0; }
.transits__full .disclosure__summary { padding-block: var(--space-3); }

.sky { display: grid; justify-items: center; gap: var(--space-4); }
.sky__dial { display: flex; justify-content: center; }
.sky__dial svg { width: min(72%, 15rem); height: auto; }
.sky__ring { fill: none; stroke: color-mix(in srgb, var(--star) 8%, transparent); }

.sky__arc {
  fill: none;
  stroke: var(--color-accent);
  stroke-linecap: round;
  filter: drop-shadow(0 0 6px color-mix(in srgb, var(--amber) 60%, transparent));
}
.sky__tick { stroke: color-mix(in srgb, var(--star) 20%, transparent); }
.sky__now { fill: var(--color-heading); }
.sky__center-k { text-anchor: middle; fill: var(--color-text-muted); font-size: 6px; letter-spacing: 1.5px; text-transform: uppercase; }
.sky__center-v { text-anchor: middle; fill: var(--color-heading); font-family: var(--font-serif); font-size: 12px; }

/* Tappable transit points: a generous transparent hit target; the chosen dot lights. */
.sky__pt-group { cursor: pointer; }
.sky__pt-group:focus { outline: none; }
.sky__hit { fill: transparent; }
.sky__pt { fill: var(--mist); transition: fill var(--dur-fast) var(--ease-out); }
.sky__pt.is-active { fill: var(--color-accent); filter: drop-shadow(0 0 6px color-mix(in srgb, var(--amber) 75%, transparent)); }
.sky__pt-group:hover .sky__pt { fill: color-mix(in srgb, var(--color-accent) 60%, var(--mist)); }
.sky__pt-group:focus-visible .sky__pt { stroke: var(--color-focus); stroke-width: 2; }

/* The chosen reading: one Transit's line + gloss, centred, no divider. */
.sky__readout { text-align: center; max-width: 30rem; }
.sky__reading .transit { border-bottom: 0; padding: 0; }
