/* ==========================================================================
   Whitehead Wellness — Components.
   The things a member actually touches. Mobile first, all of it.
   ==========================================================================

   Two rules restated, because they are the ones that got broken before:

     1. --dragon-color is never a text fill. Six of the seven fail WCAG AA as ink
        on parchment. It is a border, a rule, a glow.
     2. The script face is for accents only. If a price, a benefit, or a
        description is set in Caveat, that is a bug (CLAUDE.md §4).
   ========================================================================== */

/* --- Forms ----------------------------------------------------------------
   Labels are always visible. A placeholder is not a label — it vanishes the
   moment someone starts typing, which is exactly when they need it, and screen
   readers announce it as a hint rather than a name.
   -------------------------------------------------------------------------- */

.field + .field {
  margin-top: var(--space-5);
}

.field > label,
.field__label {
  display: block;
  font-weight: 600;
  font-size: var(--text-base);
  margin-bottom: var(--space-2);
}

.field__help {
  font-size: var(--text-sm);
  color: var(--color-text-soft);
  margin-top: -0.25rem;
  margin-bottom: var(--space-2);
}

.field__error {
  font-size: var(--text-sm);
  color: var(--color-error);
  font-weight: 600;
  margin-top: var(--space-2);
}

/* --- Live password requirements (signup) ---------------------------------
   The rules for a password, checked as you type: red for not-yet, green for
   done. But colour is NEVER the only signal — each line also carries an icon
   (○ pending, ✓ met), because a member who cannot tell red from green still
   needs to know where they stand (Launch Readiness, CLAUDE.md §9).

   The muted feedback tokens, not alarm-bright red/green: the voice here is
   encouraging, not scolding (CLAUDE.md §5).

   Default is neutral and iconless-plain — the list only becomes "live" (the
   red/green game) once signup_password.js adds the --live class. Without it,
   a no-JS visitor reads a calm set of instructions, not a permanent wall of
   red. */
.pw-reqs {
  list-style: none;
  margin: var(--space-3) 0 0;
  padding: 0;
  display: grid;
  gap: var(--space-1);
}

.pw-req,
.pw-match {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  font-size: var(--text-sm);
  color: var(--color-text-soft);
}

.pw-req::before,
.pw-match::before {
  content: "•";
  font-weight: 700;
  flex: none;
}

.pw-match {
  margin-top: var(--space-2);
  font-weight: 600;
}

.pw-reqs--live .pw-req,
.pw-match--live {
  color: var(--color-error);
  transition: color var(--duration-fast) var(--ease);
}

.pw-reqs--live .pw-req::before,
.pw-match--live::before {
  content: "\25CB"; /* ○ pending */
}

.pw-reqs--live .pw-req.is-met,
.pw-match--live.is-met {
  color: var(--color-success);
}

.pw-reqs--live .pw-req.is-met::before,
.pw-match--live.is-met::before {
  content: "\2713"; /* ✓ met */
}

/* --- "Is this taken?" indicator (signup username/email) ------------------
   Shown under the field once the member leaves it: a green ✓ Available or a
   red ✕ Not Available. As with the password rules, the icon carries the
   meaning too — never colour alone (CLAUDE.md §9) — and the muted feedback
   tokens keep it reassuring rather than alarming (CLAUDE.md §5). */
.avail {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  margin-top: var(--space-2);
  font-size: var(--text-sm);
  font-weight: 600;
}

.avail::before {
  font-weight: 700;
  flex: none;
}

.avail--checking {
  color: var(--color-text-soft);
  font-weight: 400;
}

/* No icon while checking — the "Checking…" text carries its own ellipsis, and a
   green/red mark has not been earned yet. */

.avail--ok {
  color: var(--color-success);
}

.avail--ok::before {
  content: "\2713"; /* ✓ */
}

.avail--taken {
  color: var(--color-error);
}

.avail--taken::before {
  content: "\2715"; /* ✕ */
}

input[type="text"],
input[type="email"],
input[type="password"],
input[type="url"],
input[type="number"],
input[type="search"],
input[type="date"],
input[type="file"],
select,
textarea {
  display: block;
  width: 100%;
  min-height: var(--tap-target);
  padding: var(--space-3);
  background: var(--color-surface);
  border: 1px solid var(--color-line-strong);
  border-radius: var(--radius-sm);
  font-size: var(--text-base);
}

textarea {
  min-height: 7rem;
  resize: vertical;
  line-height: var(--leading-body);
}

input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  outline: none;
  border-color: var(--color-gold);
  box-shadow: var(--shadow-glow);
}

input[aria-invalid="true"],
textarea[aria-invalid="true"] {
  border-color: var(--color-error);
}

fieldset {
  border: 0;
  padding: 0;
}

legend {
  font-weight: 700;
  font-size: var(--text-lg);
  margin-bottom: var(--space-2);
  padding: 0;
}

.choices {
  display: grid;
  gap: var(--space-2);
}

.choice {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  min-height: var(--tap-target);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-line-strong);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  cursor: pointer;
  transition: border-color var(--duration-fast) var(--ease);
}

.choice:hover {
  border-color: var(--color-gold);
}

.choice:has(input:checked) {
  border-color: var(--color-gold);
  border-width: 2px;
  padding: calc(var(--space-3) - 1px) calc(var(--space-4) - 1px);
  background: var(--color-gold-wash);
}

.choice:has(input:focus-visible) {
  box-shadow: var(--shadow-glow);
}

.choice input {
  width: auto;
  min-height: auto;
  margin-top: 0.3em;
  flex: 0 0 auto;
  accent-color: var(--color-gold-deep);
}

.choice__label {
  font-weight: 600;
}

.choice__note {
  display: block;
  font-size: var(--text-sm);
  font-weight: 400;
  color: var(--color-text-soft);
  margin-top: 2px;
}

.form-actions {
  margin-top: var(--space-6);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

@media (min-width: 640px) {
  .form-actions {
    flex-direction: row;
    align-items: center;
  }
}

/* --- Tier cards -----------------------------------------------------------
   The pricing table. It shipped with the taglines in handwriting, the benefits
   spilling their bullets outside the card, and four cards of four different
   heights. All three of those were the same bug: no component, just markup.
   -------------------------------------------------------------------------- */

.tier-grid {
  display: grid;
  gap: var(--space-4);
  align-items: stretch;
}

@media (min-width: 640px) {
  .tier-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 1024px) {
  .tier-grid {
    grid-template-columns: repeat(4, 1fr);
    gap: var(--space-4);
  }
}

.tier-card {
  display: flex;
  flex-direction: column;
  height: 100%;
  padding: var(--card-pad);
  background: var(--color-surface);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
}

.tier-card--current {
  border-color: var(--color-gold);
  border-width: 2px;
  padding: calc(var(--card-pad) - 1px);
  box-shadow: var(--shadow-md);
}

.tier-card__name {
  font-size: var(--text-lg);
  margin-bottom: var(--space-1);
}

.tier-card__price {
  display: flex;
  align-items: baseline;
  gap: var(--space-1);
  font-size: var(--text-2xl);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.1;
}

.tier-card__period {
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--color-text-soft);
}

/* The tagline. Plain body type — NOT the script face. It is information a
   person uses to choose what to pay, and information is never handwriting. */
.tier-card__tagline {
  margin-top: var(--space-3);
  font-size: var(--text-base);
  line-height: var(--leading-snug);
  color: var(--color-text-soft);
}

.tier-card__benefits {
  margin-top: var(--space-5);
  padding-top: var(--space-5);
  border-top: 1px solid var(--color-line);
  flex: 1; /* pushes every CTA to the same baseline */
  display: grid;
  gap: var(--space-3);
  align-content: start;
}

.tier-card__benefits li {
  position: relative;
  padding-left: var(--space-5);
  font-size: var(--text-base);
  line-height: var(--leading-snug);
}

.tier-card__benefits li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.55em;
  width: 6px;
  height: 6px;
  border-radius: var(--radius-full);
  background: var(--color-gold);
}

/* "Everything in Seeker, plus:" — a heading inside the list, not a benefit. */
.tier-card__benefits li.is-carryover {
  font-weight: 600;
  color: var(--color-text-soft);
  padding-left: 0;
}

.tier-card__benefits li.is-carryover::before {
  display: none;
}

.tier-card__cta {
  margin-top: var(--space-5);
}

.tier-card__cta .btn {
  width: 100%;
}

/* --- The Morning Scroll ---------------------------------------------------- */

.scroll {
  padding: var(--space-6);
  background: var(--color-surface);
  border: 1px solid var(--color-gold-soft);
  border-top: 3px solid var(--color-gold);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
}

.scroll--sealed {
  text-align: center;
}

.scroll__seal {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 3.5rem;
  height: 3.5rem;
  margin-bottom: var(--space-4);
  border-radius: var(--radius-full);
  background: var(--color-gold-wash);
  border: 1px solid var(--color-gold);
  font-size: 1.5rem;
}

/* The four steps of the ritual, separated by a dashed rule.

   These used to be `:first-of-type` / `:last-of-type`, and those are scoped by
   ELEMENT TYPE, not by class. Step 1 is a <fieldset> (it wraps a radio group) and
   steps 2-4 are <div>s — so `:first-of-type` matched the fieldset AND the first
   div, stripping the top padding from step 2, and `:last-of-type` matched the last
   div AND the fieldset (the only one of its type, hence also the last), stripping
   the divider from under step 1.

   The visible result: the "that is the whole requirement" line collided with the
   next question, and the required step had nothing separating it from the optional
   ones — which is the single distinction this form exists to make.

   Sibling selectors do not care what the element is. */
.scroll__step {
  padding-block: var(--space-5);
}

.scroll__step + .scroll__step {
  border-top: 1px dashed var(--color-line);
}

.scroll__step-label {
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--color-text-soft);
  margin-bottom: var(--space-3);
}

.scroll__voice {
  padding: var(--space-4);
  background: var(--color-surface-sunk);
  border-left: 3px solid var(--dragon-color, var(--color-gold));
  border-radius: var(--radius-sm);
}

.mood-row {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-2);
}

@media (min-width: 640px) {
  .mood-row {
    grid-template-columns: repeat(4, 1fr);
  }
}

.mood-row .choice {
  justify-content: center;
  text-align: center;
  padding-inline: var(--space-2);
}

/* --- Streak --------------------------------------------------------------- */

.streak {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
}

.streak__number {
  font-size: var(--text-3xl);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.03em;
}

.streak__label {
  color: var(--color-text-soft);
}

.grace-note {
  padding: var(--space-4);
  border-left: 3px solid var(--color-gold);
  background: var(--color-gold-wash);
  border-radius: var(--radius-sm);
  font-size: var(--text-base);
}

/* --- Feed ----------------------------------------------------------------- */

.post {
  padding: var(--card-pad);
  background: var(--color-surface);
  border: var(--border-hairline);
  border-radius: var(--radius-md);
}

.post + .post {
  margin-top: var(--space-4);
}

.post__head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}

.avatar {
  width: 40px;
  height: 40px;
  border-radius: var(--radius-full);
  object-fit: cover;
  flex: 0 0 auto;
  background: var(--color-surface-sunk);
  border: 2px solid var(--dragon-color, var(--color-gold-soft));
}

.avatar--lg {
  width: 72px;
  height: 72px;
}

.post__author {
  font-weight: 600;
  text-decoration: none;
}

.post__meta {
  font-size: var(--text-sm);
  color: var(--color-text-faint);
}

.post__body {
  line-height: var(--leading-body);
}

.post__body img {
  margin-top: var(--space-3);
  border-radius: var(--radius-sm);
}

.post__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-4);
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-line);
}

.reaction {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  min-height: 36px;
  padding: 0 var(--space-3);
  border: 1px solid var(--color-line-strong);
  border-radius: var(--radius-full);
  font-size: var(--text-sm);
  font-weight: 600;
  cursor: pointer;
  background: var(--color-surface);
  transition: border-color var(--duration-fast) var(--ease),
    background-color var(--duration-fast) var(--ease);
}

.reaction:hover {
  border-color: var(--color-gold);
  background: var(--color-gold-wash);
}

/* Reacted. The pill you chose fills in — white to gold — so you can see it landed,
   and tapping it again takes it back off. A 2px gold ring plus the wash reads as
   clearly "on" against the plain outline of the others. */
.reaction[aria-pressed="true"] {
  border: 2px solid var(--color-gold);
  background: var(--color-gold-wash);
  padding-inline: calc(var(--space-3) - 1px); /* hold width steady as the border grows */
}

/* A <summary> that is really a button — the reply discloser and the dragon picker
   — must feel like one: a pointer, and none of the default disclosure triangle. */
summary.reaction {
  list-style: none;
}

summary.reaction::-webkit-details-marker {
  display: none;
}

/* Compact pills, for the two reactions under each reply. */
.reaction--sm {
  min-height: 32px;
  padding: 0 var(--space-2);
  font-size: var(--text-base);
}

.reaction--sm[aria-pressed="true"] {
  padding-inline: calc(var(--space-2) - 1px);
}

/* The Guardian reaction — the dragon's own image in a round pill. Its outline
   carries the dragon's colour (a border, never text — the one place the hue is
   allowed), and lights fully when reacted, the same white-to-on as the others. */
.reaction--dragon {
  padding: 0 var(--space-2);
  border-color: var(--dragon-color, var(--color-line-strong));
}

.reaction--dragon[aria-pressed="true"] {
  border-color: var(--dragon-color, var(--color-gold));
  padding-inline: calc(var(--space-2) - 1px);
}

.reaction__dragon-img {
  display: block;
  width: 24px;
  height: 24px;
  border-radius: var(--radius-full);
  object-fit: cover;
  background: var(--color-surface-sunk);
}

/* Someone else's dragon — read-only. It keeps its colour ring but does not invite
   a press: no pointer, no gold hover. You cannot react as a dragon that is not
   yours. */
.reaction--static,
.reaction--static:hover {
  cursor: default;
  background: var(--color-surface);
  border-color: var(--dragon-color, var(--color-line-strong));
}

.comment {
  padding-block: var(--space-4);
  border-top: 1px solid var(--color-line);
}

/* Two levels, and no more. A third indent at 375px leaves a forty-character
   column, which is not a conversation, it is a margin note. */
.comment--reply {
  margin-left: var(--space-4);
  padding-left: var(--space-4);
  border-left: 2px solid var(--color-gold-soft);
  border-top: 0;
}

/* Horizontal scroll rather than a wrapping pile. At 375px a ten-item wrapped
   nav eats half the screen before a member reads a single word. */
.channel-switcher {
  display: flex;
  gap: var(--space-2);
  overflow-x: auto;
  padding-bottom: var(--space-3);
  margin-bottom: var(--space-6);
  scrollbar-width: thin;
  -webkit-overflow-scrolling: touch;
}

.channel-switcher a {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 40px;
  padding: 0 var(--space-4);
  border: 1px solid var(--color-line-strong);
  border-radius: var(--radius-full);
  text-decoration: none;
  white-space: nowrap;
  font-size: var(--text-sm);
  font-weight: 600;
  background: var(--color-surface);
}

/* The dragon's face on its chip. A small round crop of the character art — the
   left edge of the pill leans a touch tighter so the avatar does not float. */
.channel-switcher a:has(.channel-switcher__avatar) {
  padding-left: var(--space-2);
}

.channel-switcher__avatar {
  flex: none;
  width: 26px;
  height: 26px;
  border-radius: var(--radius-full);
  object-fit: cover;
  background: var(--color-surface-sunk);
}

/* On a wide screen the scroller is the wrong shape. There is plenty of room for
   all ten channels, so scrolling buys nothing and costs something real: the row
   runs off the right-hand edge and the last channel is sliced through the middle
   of its own name, which looks like a rendering fault rather than a scroll hint.
   Let it wrap and centre once the room exists. */
@media (min-width: 1024px) {
  .channel-switcher {
    flex-wrap: wrap;
    justify-content: center;
    overflow-x: visible;
  }
}

.channel-switcher a:hover {
  border-color: var(--color-gold);
}

.channel-switcher a[aria-current="page"] {
  border-color: var(--dragon-color, var(--color-gold));
  border-width: 2px;
  background: var(--color-gold-wash);
}

/* --- Quest progress ------------------------------------------------------- */

.tier-row {
  padding: var(--card-pad);
  background: var(--color-surface);
  border: 1px solid var(--color-line);
  border-left: 4px solid var(--color-line-strong);
  border-radius: var(--radius-md);
}

.tier-row + .tier-row {
  margin-top: var(--space-3);
}

.tier-row--earned {
  border-left-color: var(--color-success);
}

.tier-row--open {
  border-left-color: var(--dragon-color, var(--color-gold));
  box-shadow: var(--shadow-sm);
}

.tier-row--locked {
  background: transparent;
  border-style: dashed;
  border-left-style: solid;
}

.tier-row__head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin-bottom: var(--space-2);
}

.tier-row__state {
  font-size: var(--text-sm);
  font-weight: 700;
  color: var(--color-text-soft);
}

.goal {
  padding-block: var(--space-4);
  border-top: 1px dashed var(--color-line);
}

.goal__role {
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--color-text-faint);
  margin-bottom: var(--space-2);
}

.goal--verified .goal__role {
  color: var(--color-success);
}

.goal__title {
  font-weight: 700;
  margin-bottom: var(--space-2);
}

.goal__verify {
  margin-top: var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: var(--color-surface-sunk);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  line-height: var(--leading-snug);
}

.progress-bar {
  height: 6px;
  margin-block: var(--space-3);
  border-radius: var(--radius-full);
  background: var(--color-line);
  overflow: hidden;
}

.progress-bar__fill {
  height: 100%;
  border-radius: var(--radius-full);
  background: var(--dragon-color, var(--color-gold));
  transition: width var(--duration-base) var(--ease);
}

/* --- Wins ----------------------------------------------------------------- */

.win {
  display: flex;
  gap: var(--space-3);
  align-items: flex-start;
  padding-block: var(--space-3);
}

.win + .win {
  border-top: 1px dashed var(--color-line);
}

.win__mark {
  flex: 0 0 auto;
  width: 8px;
  height: 8px;
  margin-top: 0.6em;
  border-radius: var(--radius-full);
  background: var(--dragon-color, var(--color-gold));
}

/* --- Locked, but never a dead end ------------------------------------------
   Every lock explains itself and offers a way forward. A greyed card that says
   nothing is a closed door with a padlock painted on it (CLAUDE.md §3).
   -------------------------------------------------------------------------- */

.locked-note {
  padding: var(--space-4);
  border: 1px dashed var(--color-gold);
  border-radius: var(--radius-md);
  background: var(--color-gold-wash);
  font-size: var(--text-base);
}

.locked-note > * + * {
  margin-top: var(--space-3);
}

/* --- Tables --------------------------------------------------------------- */

.table-wrap {
  overflow-x: auto;
  border: 1px solid var(--color-line);
  border-radius: var(--radius-md);
}

table.data {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-sm);
  background: var(--color-surface);
}

table.data th,
table.data td {
  padding: var(--space-3) var(--space-4);
  text-align: left;
  border-bottom: 1px solid var(--color-line);
}

table.data th {
  font-weight: 700;
  white-space: nowrap;
  background: var(--color-surface-sunk);
}

table.data tr:last-child td {
  border-bottom: 0;
}

/* --- Dialogs (the swap warning, chiefly) ----------------------------------- */

dialog {
  width: min(34rem, calc(100vw - 2rem));
  padding: var(--space-6);
  border: 1px solid var(--color-gold);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-text);
  box-shadow: var(--shadow-lg);
}

/* Center every open dialog, both axes, no matter how tall its content is. The
   browser's default margin:auto centering breaks down once a dialog (e.g. the
   seven-Guardian picker) is taller than the viewport — it pins to the top-left
   and the overflow runs off-screen. Pinning position:fixed + inset:0 + margin
   auto keeps the box centered, and a viewport-bounded max-height lets the
   contents scroll INSIDE it rather than spilling out. */
dialog[open] {
  position: fixed;
  inset: 0;
  margin: auto;
  max-height: min(88vh, 44rem);
  overflow-y: auto;
}

dialog::backdrop {
  background: rgba(46, 63, 74, 0.6);
  backdrop-filter: blur(2px);
}

dialog > * + * {
  margin-top: var(--space-4);
}

.dialog__actions {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-top: var(--space-6);
}

@media (min-width: 640px) {
  .dialog__actions {
    flex-direction: row-reverse; /* the safe choice sits on the right, and first */
    justify-content: flex-end;
  }
}

/* --- Step indicator -------------------------------------------------------- */

.steps {
  display: flex;
  gap: var(--space-2);
  margin-bottom: var(--space-8);
}

.steps li {
  flex: 1;
  height: 4px;
  border-radius: var(--radius-full);
  background: var(--color-line);
}

.steps li.is-done,
.steps li.done,
.steps li[aria-current="step"] {
  background: var(--color-gold);
}

/* --- Scroll (blog) cards --------------------------------------------------- */

.scroll-grid {
  display: grid;
  gap: var(--space-5);
}

/* A centred wrap rather than fixed tracks. The number of Scrolls is whatever Kate
   has written — it is not going to arrive in neat multiples of three, and a rigid
   grid puts a two-card row hard against the left edge with a card-shaped hole
   next to it, which reads as a post that failed to render. */
@media (min-width: 640px) {
  .scroll-grid {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
  }

  .scroll-grid > * {
    flex: 0 1 calc((100% - var(--space-5)) / 2);
    min-width: 0;
  }
}

@media (min-width: 1024px) {
  .scroll-grid > * {
    flex: 0 1 calc((100% - 2 * var(--space-5)) / 3);
  }
}

.scroll-card {
  display: flex;
  flex-direction: column;
  height: 100%;
  overflow: hidden;
  background: var(--color-surface);
  border: 1px solid var(--color-line);
  border-top: 3px solid var(--dragon-color, var(--color-gold));
  border-radius: var(--radius-md);
}

.scroll-card__img {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}

.scroll-card__body {
  display: flex;
  flex-direction: column;
  flex: 1;
  padding: var(--card-pad);
  gap: var(--space-2);
}

.scroll-card__title {
  font-size: var(--text-lg);
}

.scroll-card__title a {
  text-decoration: none;
}

.scroll-card__excerpt {
  color: var(--color-text-soft);
  font-size: var(--text-base);
  flex: 1;
}

/* --- Pagination ------------------------------------------------------------ */

.pagination {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-8);
}

.pagination a,
.pagination span {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--tap-target);
  min-height: var(--tap-target);
  padding-inline: var(--space-3);
  border: 1px solid var(--color-line-strong);
  border-radius: var(--radius-sm);
  text-decoration: none;
  font-size: var(--text-sm);
}

.pagination [aria-current="page"] {
  border-color: var(--color-gold);
  background: var(--color-gold-wash);
  font-weight: 700;
}

/* --- Empty states ---------------------------------------------------------- */

.empty {
  padding: var(--space-10) var(--space-6);
  text-align: center;
  border: 1px dashed var(--color-gold-soft);
  border-radius: var(--radius-md);
}

.empty > * + * {
  margin-top: var(--space-3);
}

/* --- Composer -------------------------------------------------------------
   The box you type into. It should feel like a low-stakes place to put a
   sentence, not a publishing tool.
   -------------------------------------------------------------------------- */

.composer {
  padding: var(--card-pad);
  background: var(--color-surface);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-md);
  margin-bottom: var(--space-6);
}

.composer > * + * {
  margin-top: var(--space-3);
}

.composer textarea {
  min-height: 5rem;
}

.composer__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  align-items: center;
  justify-content: space-between;
}

/* --- Reactions ------------------------------------------------------------
   Seven dragons plus two universals is roughly 700px of pills on a 343px
   column. So the two universal reactions stay on the visible row and the seven
   live behind a <details> that opens into a wrapping panel — keyboard-operable
   and screen-reader-announced for free, no sideways scroll, 44px targets intact.
   -------------------------------------------------------------------------- */

.reaction-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: center;
}

.reaction--quiet {
  border-color: transparent;
  color: var(--color-text-soft);
  font-weight: 500;
}

.reaction--quiet:hover {
  border-color: var(--color-line-strong);
  background: transparent;
}

.reaction-picker {
  position: relative;
}

.reaction-picker > summary {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  min-height: 36px;
  padding: 0 var(--space-3);
  border: 1px solid var(--color-line-strong);
  border-radius: var(--radius-full);
  font-size: var(--text-sm);
  font-weight: 600;
  cursor: pointer;
  list-style: none;
}

.reaction-picker > summary::-webkit-details-marker {
  display: none;
}

.reaction-picker[open] > summary {
  border-color: var(--color-gold);
  background: var(--color-gold-wash);
}

.reaction-picker__panel {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  width: 100%;
  margin-top: var(--space-3);
  padding: var(--space-3);
  background: var(--color-surface-sunk);
  border-radius: var(--radius-md);
}

.report {
  margin-left: auto;
  font-size: var(--text-sm);
  color: var(--color-text-faint);
  text-decoration: underline;
  text-decoration-color: var(--color-line-strong);
  text-underline-offset: 0.2em;
}

.report:hover {
  color: var(--color-text);
}

.comment__reply-box {
  margin-top: var(--space-3);
  /* Fill the actions row. Without this the <details> is only as wide as its
     summary pill, and the textarea inside it inherits that narrow width — a
     reply box you can barely see the words in. */
  flex-basis: 100%;
}

.comment__reply-box textarea {
  width: 100%;
}

.comment__reactions {
  margin-top: var(--space-3);
}

p.reaction {
  cursor: default;
}

/* --- Flagging -------------------------------------------------------------
   A red flag in the top-right corner of a post or comment opens a form at the
   bottom of that same thing. The corner needs a positioning context. */
.flaggable {
  position: relative;
}

.flag-toggle {
  position: absolute;
  top: var(--space-4);
  right: var(--space-4);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border: 1px solid var(--color-line);
  border-radius: var(--radius-full);
  background: var(--color-surface);
  color: var(--color-error); /* the flag itself is red; currentColor fills the SVG */
  cursor: pointer;
  transition: border-color var(--duration-fast) var(--ease),
    background-color var(--duration-fast) var(--ease);
}

.flag-toggle:hover,
.flag-toggle[aria-expanded="true"] {
  border-color: var(--color-error);
  background: var(--color-surface-sunk);
}

.flag-toggle svg {
  display: block;
  width: 15px;
  height: 15px;
}

/* On a comment the corner is tighter — no card padding to sit inside. */
.comment .flag-toggle {
  top: var(--space-2);
  right: 0;
  width: 30px;
  height: 30px;
}

.comment .flag-toggle svg {
  width: 13px;
  height: 13px;
}

.flag-form {
  margin-top: var(--space-5);
  padding: var(--space-4);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-md);
  background: var(--color-surface-sunk);
}

/* Give the submit room to breathe below the reason field / detail box. */
.flag-form button[type="submit"] {
  margin-top: var(--space-4);
}

/* A reaction pill with its own count stacked beneath it — 👍 over 3. The number
   is always shown, 0 included, so a fresh post reads "be the first" rather than
   looking broken. tabular-nums keeps the pills from shifting as counts change. */
.reaction-tally {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
}

.reaction-tally__count {
  font-size: var(--text-xs);
  font-weight: 700;
  line-height: 1;
  color: var(--color-text-soft);
  font-variant-numeric: tabular-nums;
}

/* Room to breathe between the back-link and the post it sits above. */
.post-back {
  margin-bottom: var(--space-5);
}

/* --- Member directory ------------------------------------------------------ */

.member-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
  gap: var(--space-4);
}

.member-card {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4);
  background: var(--color-surface);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-md);
}

.member-card__name {
  font-weight: 600;
  text-decoration: none;
}

/* --- The Scrolls ---------------------------------------------------------- */

.featured-grid {
  display: grid;
  gap: var(--space-5);
  padding: var(--space-5);
  border: 1px solid var(--color-gold);
  border-radius: var(--radius-lg);
  background: var(--color-gold-wash);
  margin-bottom: var(--section-gap);
}

/* Kate features up to three, and often features two. Centre them in the gold panel
   rather than leaving an empty third column staring at the reader. */
@media (min-width: 768px) {
  .featured-grid {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
  }

  .featured-grid > * {
    flex: 0 1 calc((100% - 2 * var(--space-5)) / 3);
    min-width: 0;
  }
}

.featured-card {
  display: flex;
  flex-direction: column;
  height: 100%;
  overflow: hidden;
  background: var(--color-surface);
  border: 1px solid var(--color-line);
  border-top: 3px solid var(--dragon-color, var(--color-gold));
  border-radius: var(--radius-md);
}

.scroll-card__cover,
.featured-card__cover {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}

/* A post, read. Prose width, generous leading — this is the one place on the
   site where somebody sits and reads for five minutes. */
.scroll-article {
  max-width: var(--width-prose);
  margin-inline: auto;
}

.scroll-article__cover {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: var(--radius-md);
  margin-bottom: var(--space-8);
}

.scroll-article__body {
  font-size: var(--text-md);
  line-height: var(--leading-body);
}

.scroll-article__body > * + * {
  margin-top: var(--space-5);
}

.scroll-article__body h2,
.scroll-article__body h3 {
  margin-top: var(--space-10);
}

/* The body is now HTML from the visual editor, so restore the list markers,
   quote, and link styling the site's global reset strips. Scoped to the article
   so it can't leak into the chrome. */
.scroll-article__body ul,
.scroll-article__body ol {
  margin-left: var(--space-6);
  list-style-position: outside;
}
.scroll-article__body ul { list-style-type: disc; }
.scroll-article__body ol { list-style-type: decimal; }
.scroll-article__body li + li { margin-top: var(--space-2); }
.scroll-article__body blockquote {
  margin-left: 0;
  padding-left: var(--space-5);
  border-left: 3px solid var(--dragon-color, var(--color-gold));
  color: var(--color-text-soft);
  font-style: italic;
}
.scroll-article__body a {
  color: var(--color-text);
  text-decoration: underline;
  text-decoration-color: var(--dragon-color, var(--color-gold));
  text-underline-offset: 2px;
}
.scroll-article__body strong { font-weight: 700; }
.scroll-article__body em { font-style: italic; }

.scroll-signoff {
  margin-top: var(--space-10);
  padding: var(--space-5);
  border-left: 3px solid var(--dragon-color, var(--color-gold));
  background: var(--color-surface-sunk);
  border-radius: var(--radius-sm);
}

/* --- Locked card affordance ------------------------------------------------ */

.card__unlock-message {
  font-size: var(--text-sm);
  font-weight: 600;
}


/* --- Dragon roster card ----------------------------------------------------
   Moved out of lair.css, which only the homepage loads — so the Sanctuary and
   Meet Tara were using .dragon-card with no styles behind it at all.
   -------------------------------------------------------------------------- */

.dragon-card {
  position: relative;
  display: flex;
  flex-direction: column;
  height: 100%;
  overflow: hidden;
}

.dragon-card__glyph {
  position: absolute;
  top: var(--space-3);
  right: var(--space-4);
  font-size: 2rem;
  line-height: 1;
  opacity: 0.28;
  pointer-events: none;
}

/* When the glyph is the dragon's real face instead of an emoji, it stops being a
   faint corner watermark and becomes a small, crisp portrait ringed in the
   dragon's own colour. The emoji case keeps its 0.28 ghost; the image case does
   not — so reset the opacity only when there's an image. */
.dragon-card__glyph:has(img) {
  opacity: 1;
}

.dragon-card__glyph img {
  display: block;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  object-fit: cover;
  object-position: 50% 28%; /* the face, not the belly */
  border: 2px solid color-mix(in srgb, var(--dragon-color, var(--color-gold)) 55%, transparent);
}

.dragon-card .accent-script {
  margin-top: auto;
  padding-top: var(--space-4);
}

.dragon-card h3 a {
  text-decoration: none;
}

.dragon-card h3 a::after {
  /* The whole card is the target, not just four words of it. */
  content: "";
  position: absolute;
  inset: 0;
}

.dragon-card:hover {
  box-shadow: 0 0 0 1px var(--dragon-color), var(--shadow-md);
}


/* --- Dragon detail: big collectible artwork (Revision 2, K4) --------------- */
.dragon-portrait {
  display: grid;
  place-items: center;
  margin-bottom: var(--space-4);
}

.dragon-portrait img {
  width: min(340px, 72vw);
  height: auto;
  filter: drop-shadow(0 14px 30px rgba(34, 50, 74, 0.18));
}

/* A dragon's favourite line — a large handwritten pull-quote. A dragon speaking
   may be handwritten (CLAUDE.md §4). */
.dragon-favorite {
  max-width: 26ch;
  margin: var(--space-10) auto 0;
  text-align: center;
  font-family: var(--font-accent);
  font-size: var(--text-2xl);
  line-height: 1.3;
  color: var(--color-text-soft);
}

/* ==========================================================================
   Modal — one shared dialog surface. Used by the account change/cancel modal
   (declarative, [data-open-modal]) and by wwConfirm() (the site-wide confirm
   that replaces every native window.confirm/alert). Lives here, in the global
   sheet, so any page can open a themed dialog without its own CSS.
   ========================================================================== */
.modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
  background: rgba(34, 50, 74, 0.5);
  animation: modalfade var(--duration-fast) var(--ease);
}
.modal-backdrop[hidden] {
  display: none;
}
.modal {
  width: 100%;
  max-width: 460px;
  max-height: 90vh;
  overflow-y: auto;
  background: var(--color-surface, #fff);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-lg);
  padding: var(--space-6);
  box-shadow: var(--shadow-lg);
  animation: modalpop var(--duration-base) var(--ease);
}
.modal__title {
  margin: 0 0 var(--space-2);
  font-size: var(--text-xl);
  color: var(--color-text);
}
.modal__body {
  margin: 0;
  color: var(--color-text-soft);
  line-height: 1.5;
}
.modal__actions {
  display: flex;
  gap: var(--space-3);
  justify-content: flex-end;
  margin-top: var(--space-5);
  flex-wrap: wrap;
}
/* Destructive confirm (delete, cancel-for-good) reads as a warm warning, never
   a harsh red — brand voice §5, supportive not alarming. */
.modal__confirm--danger {
  background: #a4544c;
  color: #fff;
}
.modal__confirm--danger:hover {
  background: #914940;
}
@keyframes modalfade {
  from { opacity: 0; }
  to { opacity: 1; }
}
@keyframes modalpop {
  from { opacity: 0; transform: translateY(8px) scale(0.98); }
  to { opacity: 1; transform: none; }
}

/* A small inline dragon face for name-chips and card headings (_dragon_inline.html).
   Sized to sit on the text baseline; the portrait is clipped to a circle. Emoji
   fallback (no class) just renders as a glyph. */
.dragon-inline-img {
  display: inline-block;
  width: 1.4em;
  height: 1.4em;
  border-radius: 50%;
  object-fit: cover;
  vertical-align: -0.35em;
  border: 1px solid var(--color-gold-soft, rgba(184, 144, 69, 0.4));
}

/* ==========================================================================
   Portal two-column layout — for login-required pages that were a single narrow
   ribbon with empty margins on desktop. The primary content (a form, the day's
   work) stays in the wider MAIN column; secondary panels (stats, settings,
   occasional prompts) sit in the ASIDE. Mobile-first: one column, MAIN first,
   then ASIDE — so the primary task always leads on a phone (CLAUDE.md §3).
   ========================================================================== */
.portal-cols {
  display: grid;
  gap: var(--space-6);
}
.portal-cols__aside {
  display: grid;
  gap: var(--space-6);
  align-content: start;
}

@media (min-width: 900px) {
  .portal-cols {
    grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
    gap: var(--space-6) var(--space-8);
    align-items: start;
  }
  /* The aside rides along beside the taller main column. */
  .portal-cols__aside {
    position: sticky;
    top: var(--space-6);
  }
}

/* The panels inside a column shouldn't carry the whole-page top margins the
   single-column `.section` rhythm assumed — the grid gap spaces them now. */
.portal-cols .section {
  margin-top: 0;
}
.portal-cols__main > * + *,
.portal-cols__aside > * + * {
  margin-top: var(--space-6);
}

/* Three-region variant: a glanceable STAT (e.g. the streak) pinned to the top of
   the side column, MAIN spanning the tall left, and the ASIDE extras beneath the
   stat. DOM order stays stat -> main -> aside, so on mobile the stat still leads,
   the primary task follows, and settings come last — the page's original order. */
@media (min-width: 900px) {
  .portal-cols--stat {
    grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
    grid-template-areas: "main stat" "main aside";
  }
  .portal-cols--stat .portal-cols__main { grid-area: main; }
  .portal-cols--stat .portal-cols__stat { grid-area: stat; }
  .portal-cols--stat .portal-cols__aside { grid-area: aside; position: static; }
}
.portal-cols__stat > * + * {
  margin-top: var(--space-6);
}

/* ==========================================================================
   Tabs — accessible, no-reload section switching (portal_tabs.js). Shared: the
   account page and the resource library both use it; any portal page can. The
   panel for the selected tab shows, the rest are [hidden]. Mobile-first: the tab
   row scrolls sideways when the labels don't fit.
   ========================================================================== */
.tabs {
  margin-top: var(--space-6);
}
.tabs__list {
  display: flex;
  gap: var(--space-1);
  /* Horizontal scroll only when the tabs don't fit. Pin overflow-y, or the browser
     promotes it to `auto` (because overflow-x is set) and the 3px underline / -1px
     margin trigger a stray VERTICAL scrollbar. */
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  border-bottom: 1px solid var(--color-line);
  margin-bottom: var(--space-6);
}
.tabs__list::-webkit-scrollbar {
  display: none;
}
.tabs__tab {
  flex: 0 0 auto;
  appearance: none;
  border: 0;
  background: transparent;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  font-size: var(--text-md);
  font-weight: 600;
  color: var(--color-text-soft);
  border-bottom: 3px solid transparent;
  margin-bottom: -1px;
  white-space: nowrap;
  transition: color var(--duration-fast) var(--ease),
    border-color var(--duration-fast) var(--ease);
}
.tabs__tab:hover {
  color: var(--color-text);
}
.tabs__tab[aria-selected="true"] {
  color: var(--color-text);
  border-bottom-color: var(--dragon-color, var(--color-gold));
}
.tabs__tab:focus-visible {
  outline: 2px solid var(--color-gold);
  outline-offset: -2px;
  border-radius: var(--radius-sm);
}
.tabs__panel[hidden] {
  display: none;
}
.tabs__panel:not([hidden]) {
  animation: tabfade var(--duration-base) var(--ease);
}
@keyframes tabfade {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: none; }
}

/* --- Daily ritual: single focused column, just not an empty one -------------
   A sequential flow (streak, check-in, reflections, settings) reads worst as two
   staggered columns, so it stays one column. The streak is a slim banner — number
   on the left, grace line filling the right — instead of a tall stacked panel. */
.ritual-streak {
  display: grid;
  gap: var(--space-4);
  margin-top: var(--space-6);
}
@media (min-width: 640px) {
  .ritual-streak {
    grid-template-columns: auto minmax(0, 1fr);
    align-items: center;
    gap: var(--space-6);
  }
}
.ritual-flow > .section {
  padding-block: var(--space-8);
}

/* --- Crest climb: active tier full width, locked tiers two-up ---------------
   The tier ladder was a long column of cards, most of them short "not open yet"
   notes. Grid it: the open/earned tier spans the row, the compact locked tiers
   pack two across. Top-aligned so a short card never stretches to a tall one. */
.tier-climb {
  display: grid;
  gap: var(--space-4);
}
@media (min-width: 720px) {
  .tier-climb {
    grid-template-columns: 1fr 1fr;
    align-items: start;
  }
  .tier-climb > .tier-row--open,
  .tier-climb > .tier-row--earned {
    grid-column: 1 / -1;
  }
}
.tier-climb > .tier-row {
  margin: 0;
}

/* --- Buttons never flush against the field above them ----------------------
   A submit button placed directly after a form control was sitting hard against
   it (0px). Give any button that directly follows a field / control room to
   breathe. Buttons inside .cluster / .form-actions / .page-hero__actions /
   .dialog__actions already carry their own spacing and are untouched here. */
.field + .btn,
.field + button,
.field + [type="submit"],
fieldset + .btn,
fieldset + button,
.choices + .btn,
.choices + button,
.mood-row + .btn,
.mood-row + button,
textarea + .btn,
textarea + button,
select + .btn,
select + button,
input + .btn,
input + button {
  margin-top: var(--space-5);
}

/* --- Legal documents (privacy, terms, refund, health boundary) -------------
   Long-form policy text: restore list markers the global reset strips, give
   sub-headings and paragraphs room, keep it readable on a phone at 1am. */
.legal-doc ul {
  margin: var(--space-4) 0 0;
  padding-left: var(--space-6);
  list-style: disc;
}
.legal-doc li {
  margin-top: var(--space-2);
}
.legal-doc li::marker {
  color: var(--color-gold);
}
.legal-doc h3 {
  margin-top: var(--space-5);
  font-size: var(--text-lg);
}
.legal-doc p + p,
.legal-doc ul + p {
  margin-top: var(--space-4);
}
