/* === Quest Log ==================================================== */
/* The show page is a single parchment leaf: title, rank glyph, wax
   stamp angled at the corner, brief in a centered prose column with a
   drop cap, signature seal at the bottom, and a collapsed <details>
   "Annals" exposing creation/update metadata. No labels for fields a
   reader can already see — the design carries the meaning. */


/* ----------------------------------------------------------- rank glyph */
/* Inline sigil for rank. Main quest = filled star, ember halo on
   hover; side quest = hollow diamond, muted ink. */
/* ----------------------------------------------------------- quest mark */
/* Composite: rank ornament + status glyph fused into a single visual unit.
   Rendered from a hand-painted PNG sprite (3 cols × 5 rows) — rank picks
   the column (side / main / elite), status picks the row (pending /
   available / accepted / accepted+ready-to-seal / complete).

   Row Y offsets are calibrated to the artist's actual content positions:
   the elite rays extend above the natural 1020/5 cell boundary on lower
   rows, so per-row offsets keep every glyph base at the same display
   baseline and capture the rays without clipping. Column X offsets are
   on the regular ~227px cell grid. --scale shrinks the native 226×204
   cell down to a list or masthead glyph; pixel offsets multiplied by
   --scale stay aligned at any size. */
.quest-mark {
  --scale: 1;
  display: inline-block;
  vertical-align: middle;
  width: calc(180px * var(--scale));
  height: calc(160px * var(--scale));
  background-image: url("/static/quest_spritesheet-cf851584.png");
  background-repeat: no-repeat;
  background-size: calc(680px * var(--scale)) calc(1020px * var(--scale));
  flex-shrink: 0;
  text-decoration: none;
}

/* Rank → column. Each cell is 180px wide and tight-cropped around the
   artist's actual content (measured: side centroid 88, main 320, elite
   551 in source), so the painted glyph fills the displayed cell without
   the wide transparent margins of the geometric thirds. */
.quest-mark.rank-side  { background-position-x: calc(   2px * var(--scale)); }
.quest-mark.rank-main  { background-position-x: calc(-230px * var(--scale)); }
.quest-mark.rank-elite { background-position-x: calc(-462px * var(--scale)); }

/* State → row. Baseline-anchored: every glyph's base (disc / wax seal)
   lands at display y=148 in the 160-tall cell, like text on a typographic
   baseline. The painted glyphs are drawn at different sizes per state
   (the dim pending `!` is intentionally smaller than the lit available
   one) — sharing a baseline keeps them visually consistent across the
   list and filter strip even when the heights differ. */
.quest-mark.pending                { background-position-y: calc(  18px * var(--scale)); }
.quest-mark.available              { background-position-y: calc(-197px * var(--scale)); }
.quest-mark.accepted               { background-position-y: calc(-387px * var(--scale)); }
.quest-mark.accepted.ready-to-seal { background-position-y: calc(-574px * var(--scale)); }
.quest-mark.complete               { background-position-y: calc(-791px * var(--scale)); }

/* Pending is the quietest state — softened further beyond its slate paint
   so it sits in the periphery until you opt into seeing it. */
.quest-mark.pending { opacity: 0.7; }

/* Sizes. Native tight cell is 180×160; --scale shrinks proportionally. */
.quest-mark.size-sm { --scale: 0.28; }
.quest-mark.size-lg { --scale: 0.60; }

/* Lit states (available, accepted+ready) wear a warm ember halo on the
   dark wood — the painted gold is already lit, the halo just lets it
   bleed into the surrounding ink. */
.quest-mark.available,
.quest-mark.accepted.ready-to-seal {
  filter: drop-shadow(0 0 6px color-mix(in oklch, oklch(68% 0.20 50) 60%, transparent));
}

/* Sealed — a quieter wine halo, slight transparency so the filed quest
   reads as past tense. */
.quest-mark.complete {
  filter: drop-shadow(0 0 4px color-mix(in oklch, oklch(45% 0.15 30) 40%, transparent));
  opacity: 0.92;
}

a.quest-mark {
  transition: filter var(--dur-fast) var(--ease),
              transform var(--dur-fast) var(--ease);

  &:hover {
    filter: brightness(1.08);
    transform: scale(1.06);
  }
}

/* Status-advance flourish — when a quest has just moved forward a stage, the
   masthead mark drops in from below with a small overshoot, like a seal
   pressed into place. The `just-advanced` class is server-set for exactly
   one render after a transition (see QuestsController#show). `backwards`
   fill applies the start frame before first paint and reverts afterwards,
   so the hover transform keeps working once the flourish is done. */
@keyframes quest-mark-advance {
  from { opacity: 0; transform: translateY(16px) scale(0.55); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}

@media (prefers-reduced-motion: no-preference) {
  .rank-mark.just-advanced .quest-mark {
    animation: quest-mark-advance 520ms cubic-bezier(0.34, 1.7, 0.55, 1) backwards;
  }
}

/* Parchment override. On a sheet (the show-page masthead), ground the glyph
   with a real cast shadow instead of the dark-wood halo, and lift the
   dormant slate a touch so it doesn't sink into the parchment. */
.sheet .quest-mark.available,
.sheet .quest-mark.accepted.ready-to-seal {
  filter:
    drop-shadow(0 1px 0.5px oklch(25% 0.04 35 / 0.15))
    drop-shadow(0 2px 6px oklch(22% 0.04 30 / 0.18));
}

.sheet .quest-mark.pending,
.sheet .quest-mark.accepted {
  filter: brightness(1.07) drop-shadow(0 1px 4px oklch(22% 0.04 30 / 0.16));
}

.sheet .quest-mark.complete {
  filter: drop-shadow(0 1px 4px oklch(22% 0.05 30 / 0.28));
  opacity: 1;
}


/* ----------------------------------------------------------- filter strip */
/* Single horizontal bar carrying three filter groups separated by hairline
   dividers: scope (narrator's voice — Mine / Everyone's), status (icon
   toggles, multi-select), rank (icon toggles, multi-select). The toggles
   reuse the quest mark glyphs so the bar reads as the same vocabulary as
   the list. No active toggle in either icon group = no filter (show all). */
.filter-strip {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  flex-wrap: wrap;
  margin-bottom: var(--s-5);
}

.filter-group {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s-1);
}

.filter-divider {
  width: 1px;
  height: 22px;
  background: oklch(100% 0 0 / 0.10);
}

/* Scope segmented control — serif italic, the narrator picking the view. */
.filter-group.scope { gap: var(--s-2); }

.filter-pill {
  display: inline-flex;
  align-items: center;
  padding: var(--s-1) var(--s-3);
  font-family: var(--font-serif);
  font-style: italic;
  font-size: var(--fs-sm);
  color: var(--cream-faint);
  background: oklch(100% 0 0 / 0.03);
  border: 1px solid oklch(100% 0 0 / 0.10);
  border-radius: 999px;
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease);

  &:hover {
    color: var(--cream);
    border-color: oklch(100% 0 0 / 0.20);
  }

  &.active {
    color: oklch(82% 0.14 28);
    border-color: color-mix(in oklch, var(--wine-bright) 60%, transparent);
    background: color-mix(in oklch, var(--wine-deep) 25%, transparent);
  }
}

/* "Show all / fewer" affordance inside a filter group — an action, not a
   filter value, so it sheds the pill's border and fill and reads as a
   quiet link. */
.filter-pill.filter-more {
  border-color: transparent;
  background: none;
  color: var(--cream-faint);
  text-decoration: underline dotted;
  text-underline-offset: 3px;
}

.filter-pill.filter-more:hover {
  border-color: transparent;
  color: var(--cream);
}

/* Icon toggle — square-ish button holding the glyph. Inactive = dimmed,
   active = full opacity + soft wine pill behind. The glyph carries its
   status/rank colour so the user reads "click on the orange ! to filter
   to those" rather than parsing a label. */
.filter-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 8px;
  text-decoration: none;
  opacity: 0.5;
  transition: opacity var(--dur-fast) var(--ease),
              background var(--dur-fast) var(--ease);

  &:hover {
    opacity: 0.85;
    background: oklch(100% 0 0 / 0.04);
  }

  &.active {
    opacity: 1;
    background: color-mix(in oklch, var(--wine-deep) 28%, transparent);
  }

  & .toggle-glyph {
    width: 20px;
    height: 20px;
    overflow: visible;
  }
}

/* Status chips crop the side column (no frame) of the quest sprite — the
   same painted vocabulary as the composite quest mark, just at chip scale.
   Side-column cells carry the bare ! ? sealed glyph; the row picks the
   state. Same tight 180×160 source cell as .quest-mark, scaled down. */
.filter-toggle.status-pending  .toggle-glyph,
.filter-toggle.status-available .toggle-glyph,
.filter-toggle.status-accepted .toggle-glyph,
.filter-toggle.status-complete .toggle-glyph {
  --scale: 0.13;
  width: calc(180px * var(--scale));
  height: calc(160px * var(--scale));
  background-image: url("/static/quest_spritesheet-cf851584.png");
  background-repeat: no-repeat;
  background-size: calc(680px * var(--scale)) calc(1020px * var(--scale));
  background-position-x: calc(2px * var(--scale));
}
.filter-toggle.status-pending   .toggle-glyph { background-position-y: calc(  18px * var(--scale)); }
.filter-toggle.status-available .toggle-glyph { background-position-y: calc(-197px * var(--scale)); }
.filter-toggle.status-accepted  .toggle-glyph { background-position-y: calc(-387px * var(--scale)); }
.filter-toggle.status-complete  .toggle-glyph { background-position-y: calc(-791px * var(--scale)); }



/* ----------------------------------------------------------- quest list */
/* Each entry is a journal row. The quest mark glyph carries the fine
   detail (exact status + rank ornament); the row itself uses three
   independent scan signals, one concept each — see .quest-entry. */
.quest-list {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}

.quest-entry {
  display: grid;
  grid-template-columns: 56px 1fr auto;
  align-items: center;
  gap: var(--s-4);
  padding: var(--s-4) var(--s-5);
  background: oklch(100% 0 0 / 0.02);
  border-left: 3px solid color-mix(in oklch, var(--ink-faint) 30%, transparent);
  border-radius: var(--r-sm);
  text-decoration: none;
  color: var(--cream);
  transition: background var(--dur-fast) var(--ease),
              transform var(--dur-fast) var(--ease);

  &:hover {
    background: oklch(0% 0 0 / 0.22);
    transform: translateX(2px);
  }

  /* Three independent scan signals, one concept each:
       border-left — status: live work (accepted) is lit ember, every
                     other status stays the default grey hairline;
       glow        — rank: main & elite quests carry a soft ember halo,
                     side quests none;
       opacity     — archive: sealed (complete) quests recede.
     Exact status and rank ornament still live in the quest mark glyph. */
  &.accepted {
    border-left-color: var(--ember);
  }

  &.rank-main,
  &.rank-elite {
    box-shadow: 0 0 10px -3px color-mix(in oklch, var(--ember-glow) 34%, transparent);
  }

  &.complete {
    opacity: 0.5;
  }

  & .entry-meta .leader {
    font-family: var(--font-serif);
    font-style: italic;
    text-transform: none;
    letter-spacing: 0;
    color: var(--ember-glow);
  }

  & .entry-title {
    font-family: var(--font-serif);
    font-style: italic;
    font-size: var(--fs-md);
    color: var(--cream);
    margin: 0;
  }

  & .entry-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s-2);
    margin-top: 2px;
    font-family: var(--font-sans);
    font-size: var(--fs-xs);
    color: var(--cream-faint);
    line-height: 1.4;

    & .sep { opacity: 0.4; }

    & .project {
      display: inline-flex;
      align-items: center;
      gap: 4px;

      & svg { width: 12px; height: 12px; }
    }
  }

  /* Keep the title column shrinkable so a long title wraps instead of
     squeezing the aside into a stacked column. */
  & .entry-body { min-width: 0; }

  /* Category + tags, pushed to the right edge of the row and kept quiet —
     classification metadata, not the headline. Single line so the category
     sits at the same height in every row — no row-to-row drift. */
  & .entry-aside {
    display: flex;
    flex-wrap: nowrap;
    justify-content: flex-end;
    align-items: center;
    gap: var(--s-2);
    white-space: nowrap;
    font-family: var(--font-sans);
    font-size: var(--fs-xs);
    color: color-mix(in oklch, var(--cream-faint) 65%, transparent);

    & .category {
      text-transform: uppercase;
      letter-spacing: 0.08em;
    }

    & .margin-tag::before {
      content: "#";
      opacity: 0.5;
    }
  }
}


/* ----------------------------------------------------------- quest show */
/* Show-page wrapper. Single column on narrow viewports (quest article
   then chat below); two columns once there's room, with the chat
   docked to the right and stuck to the top while the quest scrolls. */
.quest-show {
  display: grid;
  gap: var(--s-6);
  align-items: start;
  justify-content: center;
  grid-template-columns: minmax(0, 760px);
}

.quest-show .quest-page {
  margin: 0;
  max-width: none;
}

/* In edit mode the article is wrapped in a <form>. `display: contents`
   dissolves that wrapper so the article stays the grid item itself. */
.quest-show > form { display: contents; }

.quest-show .chat {
  margin-top: 0;
}

@media (min-width: 1024px) {
  .quest-show {
    grid-template-columns: minmax(0, 720px) minmax(320px, 400px);
  }

  /* The new-quest page has no chat yet — drop the second column so the
     article stays centred instead of hugging the left track. */
  .quest-show:not(:has(.chat)) {
    grid-template-columns: minmax(0, 760px);
  }

  .quest-show .chat {
    position: sticky;
    top: var(--s-6);
  }
}

/* The show page is one parchment leaf laid out as vertical journal
   modules: masthead → brief → progress → party → assets → board →
   actions → journal. Each module is a band of content with a small caps
   heading. Atmosphere comes from a compass-rose watermark behind the
   masthead and a soft vignette at the page edges; tags ride inline in
   the inscription rather than living in a sidebar panel. */
.quest-page {
  position: relative;
  padding: var(--s-8) var(--s-10) var(--s-6);
  max-width: 760px;
  margin: 0 auto;
  overflow: hidden;
  isolation: isolate;

  /* Vignette overlay — darkens the corners so the parchment feels
     ageed at the edges. Pseudo so the inner content stays unaffected. */
  &::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: radial-gradient(
      ellipse at 50% 38%,
      transparent 55%,
      color-mix(in oklch, var(--ink) 6%, transparent) 85%,
      color-mix(in oklch, var(--ink) 14%, transparent) 100%);
    border-radius: var(--r-md);
    z-index: 0;
  }

  & > * { position: relative; z-index: 1; }

  /* A sealed quest carries the strikethrough — the work is done and
     filed; the title reads as past tense. */
  &.complete .quest-title {
    text-decoration: line-through;
    text-decoration-color: color-mix(in oklch, var(--ink) 40%, transparent);
    text-decoration-thickness: 2px;
  }

  &.complete { opacity: 0.85; }
}

/* ---------------------------------------------------- masthead */
/* Compact centered composition. Rank crest above the title, title sized
   to read first without dominating the page, inscription line below
   absorbs category + projects + tags as a single typographic strip. */
.quest-page > .quest-head {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  margin-bottom: var(--s-6);
  padding: 0 var(--s-6);
  border-bottom: 0;
  position: relative;
}

/* While editing, the masthead lifts above the sections below it so the
   tag editor's autocomplete dropdown can overlap the brief. It stays
   under .sheet-tools (z-index 3), which keeps save / cancel clickable. */
.quest-page.editing > .quest-head {
  z-index: 2;
}

.quest-head .rank-mark {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-2);
  margin-bottom: var(--s-3);

  & .rank-label {
    font-family: var(--font-sans);
    font-size: 0.65rem;
    text-transform: uppercase;
    letter-spacing: 0.3em;
    color: var(--ink-faint);
    padding-left: 0.3em;
  }
}

.quest-page.rank-main .quest-head .rank-mark .rank-label {
  color: var(--ember-deep);
  font-weight: 500;
}

.quest-page.rank-elite .quest-head .rank-mark .rank-label {
  color: var(--wax-deep);
  font-weight: 600;
}

/* Rank + status sit side by side under the mark in edit mode. */
.rank-edit {
  display: flex;
  gap: var(--s-2);
}

/* Rank + status pickers — editable twins of .rank-label, kept in the
   small-caps eyebrow register. Their faint field + ember underline (and
   the focus treatment) is the shared in-place-editor rule below. */
.editing .eyebrow-select {
  width: auto;
  padding: var(--s-1) var(--s-5) var(--s-1) var(--s-3);
  font-family: var(--font-sans);
  font-size: 0.65rem;
  text-transform: uppercase;
  letter-spacing: 0.2em;
  text-align: center;
  color: var(--ink-soft);
}

.quest-title {
  font-family: var(--font-serif);
  font-style: italic;
  font-size: clamp(1.5rem, 3.6vw, 2.2rem);
  line-height: 1.1;
  color: var(--ink);
  text-wrap: balance;
  margin: 0;
}

/* The in-place editors — title, brief, the rank/status/leader selects,
   the category and format fields — all sit on the same ember-lit field:
   a faint dark wash with a single accent border-bottom, no box. The
   bottom border is the only chrome — it carries the affordance (this is
   editable) and the focus state, nothing decorative. Scoped under
   .editing (the article in edit mode) for quest/asset editors, AND
   under `.sheet .field` so any generic form (account, future settings
   pages) inside a parchment surface picks up the same treatment without
   reinventing it. Both selectors beat the global input/textarea/select
   rule, which would otherwise paint them as pale framed boxes. */
.editing :is(.quest-title-input, .quest-brief-input, .eyebrow-select, .category-input, .format-input, .slug-input, .leader-select, .project-title-input, .project-brief-input, .url-input, .objective-title-input, .note-title-input, .note-body-input),
.sheet .field :is(input:not([type="checkbox"]):not([type="radio"]):not([type="file"]), textarea, select) {
  margin: 0;
  background: color-mix(in oklch, var(--ink) 5%, transparent);
  border: 0;
  border-bottom: 2px solid color-mix(in oklch, var(--ember) 45%, transparent);
  border-radius: var(--r-sm) var(--r-sm) 0 0;
  transition: background var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease);
}

.editing :is(.quest-title-input, .quest-brief-input, .category-input, .format-input, .slug-input, .project-title-input, .project-brief-input, .url-input, .objective-title-input, .note-title-input, .note-body-input)::placeholder,
.sheet .field :is(input:not([type="checkbox"]):not([type="radio"]):not([type="file"]), textarea)::placeholder {
  color: var(--ink-faint);
  font-style: normal;
}

.editing :is(.quest-title-input, .quest-brief-input, .eyebrow-select, .category-input, .format-input, .slug-input, .leader-select, .project-title-input, .project-brief-input, .url-input, .objective-title-input, .note-title-input, .note-body-input):focus,
.sheet .field :is(input:not([type="checkbox"]):not([type="radio"]):not([type="file"]), textarea, select):focus {
  outline: none;
  box-shadow: none;
  background: color-mix(in oklch, var(--ember) 10%, transparent);
  border-bottom-color: var(--ember);
}

/* The serif-voiced editors — quest title / brief, project name / brief,
   note title / body. */
.editing :is(.quest-title-input, .quest-brief-input, .project-title-input, .project-brief-input, .note-title-input, .note-body-input) {
  width: 100%;
  color: var(--ink);
  font-family: var(--font-serif);
}

/* The masthead title editors — the rendered <h1>'s serif italic voice,
   centered. (Quest, project and note size their <h1> slightly
   differently.) */
.editing :is(.quest-title-input, .project-title-input, .note-title-input) {
  font-style: italic;
  line-height: 1.1;
  text-align: center;
  padding: var(--s-1) var(--s-3);
}

.editing .quest-title-input { font-size: clamp(1.5rem, 3.6vw, 2.2rem); }
.editing .project-title-input { font-size: clamp(1.4rem, 3.2vw, 2rem); }
.editing .note-title-input { font-size: clamp(1.4rem, 3.2vw, 2rem); }

/* Category, format and slug — inline editors inside the inscription
   strip; they size to their content so the strip stays tight. Caps +
   letter-spacing are inherited from .inscription, keeping the register. */
.editing :is(.category-input, .format-input, .slug-input) {
  width: auto;
  min-width: 7ch;
  padding: 2px var(--s-2);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: var(--fs-xs);
  field-sizing: content;
}

/* The category picker borrows the select-input controller for the shared
   parchment autocomplete dropdown — but drops its chevron, which is tuned
   for the form's labelled .field and would mis-sit in the strip. */
.editing .category-picker::after {
  display: none;
}

/* Projects and tags each take their own wrapped line in the strip, below
   the category — both are pickers that need room. */
.editing .inscription :is(.project-picker, .tag-picker) {
  flex-basis: 100%;
  margin-top: var(--s-2);
}

/* In-place tag editor — pared down to sit in the inscription strip like
   the category field, not as a form widget. The chips shed their wine
   pill for plain text + a dismiss cross; the typing field is fully
   transparent (the strip's box is the only container). It hugs its
   content so it reads as compact as the category field. */

/* Hug the field's width (centred) so the anchored autocomplete dropdown,
   which stretches left-to-right of this shell, matches the field instead
   of the full strip. */
.editing .tag-input {
  width: fit-content;
  margin-inline: auto;
}

.editing .tag-input-field {
  gap: var(--s-1) var(--s-3);
  width: fit-content;
  min-height: 0;
  padding: 2px var(--s-3);
  background: color-mix(in oklch, var(--ink) 5%, transparent);
  border: 0;
  border-bottom: 2px solid color-mix(in oklch, var(--ember) 45%, transparent);
  border-radius: var(--r-sm) var(--r-sm) 0 0;
}

.editing .tag-input-field:focus-within {
  box-shadow: none;
  border-bottom-color: var(--ember);
}

/* Chip → plain text + a faint dismiss cross, no pill. */
.editing .tag-input-chip {
  gap: 2px;
  padding: 0;
  font-family: var(--font-sans);
  font-size: var(--fs-xs);
  text-transform: none;
  color: var(--ink);
  background: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}

.editing .tag-input-remove {
  width: auto;
  height: auto;
  font-size: var(--fs-xs);
  color: var(--ink-faint);
}

.editing .tag-input-remove:hover {
  background: none;
  color: var(--wine);
}

/* Typing field — no box of its own, just a caret in the strip. Scoped
   under .editing to shed the global input[type=text] white fill, border
   and focus ring; the field's own underline is the only feedback. */
.editing .tag-input-typed {
  padding: 0;
  font-family: var(--font-sans);
  font-size: var(--fs-xs);
  text-transform: none;
  background: none;
  border: 0;
}

.editing .tag-input-typed:focus {
  outline: none;
  box-shadow: none;
}

/* Inscription line — small caps, letter-spaced. Carries category (plus
   format on assets), projects AND tags inline; tags ride at the end in
   italic so they read as marginalia rather than a sidebar list. Shared
   by the quest masthead and the asset page. */
.inscription {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-2) var(--s-3);
  margin-top: var(--s-2);
  font-family: var(--font-sans);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--ink-soft);

  & .category { color: var(--ink); }

  & .sep { color: var(--ink-faint); opacity: 0.5; }

  & .project {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    color: var(--wine);
    text-decoration: none;
    letter-spacing: 0.14em;

    & svg { width: 11px; height: 11px; }
    &:hover { color: var(--wine-bright); }
  }

  & .inscription-tag {
    font-family: var(--font-serif);
    font-style: italic;
    text-transform: none;
    letter-spacing: 0;
    font-size: var(--fs-sm);
    color: var(--ink-faint);
  }
}

/* The quest masthead centres its inscription under the title. */
.quest-head .inscription { justify-content: center; }

/* Fleuron below the inscription — the masthead's closing flourish.
   Uses the SVG ornament defined in _icons. */
.quest-head .fleuron {
  width: 86px;
  height: 22px;
  margin: var(--s-4) 0 0;
  color: color-mix(in oklch, var(--ink) 38%, transparent);
}


/* ---------------------------------------------------- modules
   Each section of the page below the masthead is a "module": a small
   caps heading + the content. Modules stack vertically with generous
   air between, separated by a hairline rule. */
.quest-module {
  margin: var(--s-6) auto 0;
  max-width: 60ch;
  width: 100%;
}

.module-heading {
  font-family: var(--font-sans);
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.28em;
  color: var(--ink-faint);
  margin: 0 0 var(--s-3);
  padding-left: 0.28em;
}

/* Placeholder text for modules whose feature isn't wired yet. Reads as
   a scribe's note: italic, faint ink. */
.module-stub {
  font-family: var(--font-serif);
  font-style: italic;
  color: var(--ink-faint);
  font-size: var(--fs-sm);
}


/* ---------------------------------------------------- brief */
.quest-brief,
.project-brief {
  max-width: 60ch;
  margin: 0 auto;
}

/* The brief, rendered from Markdown. The element styles below dress the
   plain tags the converter emits (headings, lists, quotes, code…) in the
   page's serif voice — no class hooks, the converter never adds any. */
.brief {
  font-family: var(--font-serif);
  font-size: var(--fs-md);
  line-height: var(--lh-base);
  color: var(--ink);
  text-wrap: pretty;

  & > * + * { margin-top: var(--s-3); }
  & > :first-child { margin-top: 0; }

  /* Markdown headings sit modestly inside a brief — this is a note on
     parchment, not a document, so even an h1 stays close to body size.
     The explicit `color: var(--ink)` overrides the global `h1..h4`
     cream that lives on dark surfaces — left to inherit, headings render
     as faint cream-on-parchment and read as half-erased. */
  & :is(h1, h2, h3, h4, h5, h6) {
    color: var(--ink);
    font-weight: 600;
    line-height: var(--lh-tight);
    margin-top: var(--s-4);
  }

  & h1 { font-size: 1.3em; }
  & h2 { font-size: 1.15em; }
  & :is(h3, h4, h5, h6) { font-size: 1em; }

  /* The global `ul, ol { list-style: none }` reset kills the markers a
     hand-written list needs. Bring them back here — `::marker` already
     tints them wine, and `disc` / `decimal` are the natural fits for
     hand-written bullets / numbered steps. */
  & ul { list-style: disc; }
  & ol { list-style: decimal; }
  & :is(ul, ol) { padding-inline-start: 1.4em; }
  & li + li { margin-top: var(--s-1); }
  & li::marker { color: var(--wine); }

  & a {
    color: var(--wine);
    text-underline-offset: 2px;

    &:hover { color: var(--wine-bright); }
  }

  & em { font-style: italic; }
  & strong { font-weight: 600; }

  & del {
    color: var(--ink-faint);
    text-decoration-color: color-mix(in oklch, var(--ink) 40%, transparent);
  }

  /* A quoted aside — the left rule is the affordance (this text is set
     apart), not decoration. */
  & blockquote {
    padding-inline-start: var(--s-4);
    border-inline-start: 2px solid color-mix(in oklch, var(--wine) 45%, transparent);
    font-style: italic;
    color: var(--ink-soft);
  }

  & code {
    font-family: var(--font-mono);
    font-size: 0.85em;
    padding: 0.1em 0.35em;
    background: color-mix(in oklch, var(--ink) 7%, transparent);
    border-radius: var(--r-sm);
  }

  & pre {
    padding: var(--s-3);
    background: color-mix(in oklch, var(--ink) 7%, transparent);
    border-radius: var(--r-sm);
    overflow-x: auto;

    & code {
      font-size: 0.85em;
      padding: 0;
      background: none;
    }
  }

  & hr {
    border: 0;
    height: 1px;
    background: color-mix(in oklch, var(--ink) 18%, transparent);
  }

  /* Oversized first letter on a leading paragraph — an illuminated
     initial. Skipped when the brief opens on a heading or a list. */
  & > p:first-child::first-letter {
    color: var(--wine);
  }
}

/* Brief — raw Markdown source in the page's body serif; field-sizing
   grows the textarea as the writer types. */
.editing :is(.quest-brief-input, .project-brief-input, .note-body-input) {
  font-size: var(--fs-md);
  line-height: var(--lh-base);
  padding: var(--s-3);
  field-sizing: content;
  resize: none;
}

.editing .note-body-input { min-height: 18rem; }

.brief-hint {
  margin-top: var(--s-2);
  font-family: var(--font-sans);
  font-size: var(--fs-xs);
  color: var(--ink-faint);
  text-align: center;
}

/* Surfaced by the `draft` controller when it rehydrates a field from a
   draft saved before an accidental refresh or a failed save. */
.draft-notice {
  margin-top: var(--s-2);
  display: flex;
  gap: var(--s-2);
  justify-content: center;
  align-items: baseline;
  font-family: var(--font-sans);
  font-size: var(--fs-xs);
  color: var(--ink-faint);

  &[hidden] { display: none; }
}

.draft-discard {
  font: inherit;
  padding: 0;
  border: 0;
  background: none;
  color: var(--wax);
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;

  &:hover { color: var(--wax-bright); }
}

.brief-empty {
  font-family: var(--font-serif);
  font-style: italic;
  color: var(--ink-faint);
  font-size: var(--fs-md);
  text-align: center;
  margin: var(--s-4) auto;
}


/* ---------------------------------------------------- objectives module
   The objectives section combines (a) the progress bar — an ember strip
   that fills as objectives are ticked — and (b) the ordered list of
   objectives themselves, each a button-row that toggles its done state.
   Frozen state (quest is `complete` — sealed) dims the list and disables
   ticking until the quest is reopened. */
.quest-objectives .objectives-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-3);
  margin-bottom: var(--s-2);
}

.progress-count {
  font-family: var(--font-sans);
  font-size: var(--fs-xs);
  letter-spacing: 0.18em;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}

.quest-objectives .progress-bar {
  display: block;
  height: 6px;
  background: color-mix(in oklch, var(--ink) 8%, transparent);
  border: 1px solid color-mix(in oklch, var(--ink) 14%, transparent);
  border-radius: 999px;
  overflow: hidden;
  margin-bottom: var(--s-4);
}

.progress-fill {
  display: block;
  height: 100%;
  width: var(--progress, 0%);
  background: linear-gradient(90deg,
    color-mix(in oklch, var(--ember-deep) 55%, transparent),
    color-mix(in oklch, var(--ember) 75%, transparent));
  transition: width 480ms cubic-bezier(0.22, 1, 0.36, 1);
}

.quest-objectives.frozen .progress-fill {
  background: linear-gradient(90deg,
    color-mix(in oklch, var(--wine) 60%, transparent),
    color-mix(in oklch, var(--wine-bright) 70%, transparent));
  box-shadow: 0 0 12px color-mix(in oklch, var(--wine-bright) 35%, transparent);
}

@media (prefers-reduced-motion: reduce) {
  .progress-fill { transition: none; }
}

.objectives-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--s-1);
}

.objectives-list .objective-row {
  margin: 0;
}

.objective-form {
  margin: 0;
}

.objective-toggle {
  width: 100%;
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-2) var(--s-3);
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  cursor: pointer;
  font: inherit;
  text-align: left;
  color: var(--ink);
  transition: background 160ms, border-color 160ms;

  &:hover:not(:disabled) {
    background: color-mix(in oklch, var(--ember) 5%, transparent);
    border-color: color-mix(in oklch, var(--ember) 22%, transparent);

    & .objective-tick {
      border-color: color-mix(in oklch, var(--ember) 55%, transparent);
      box-shadow: 0 0 8px color-mix(in oklch, var(--ember) 25%, transparent);
    }
  }

  &:disabled {
    cursor: not-allowed;
    opacity: 0.5;
  }

  &:focus-visible {
    outline: 2px solid color-mix(in oklch, var(--ember) 60%, transparent);
    outline-offset: 2px;
  }
}

.objective-tick {
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: 1.5px solid color-mix(in oklch, var(--ink) 28%, transparent);
  background: color-mix(in oklch, var(--surface) 60%, transparent);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background 200ms, border-color 200ms, box-shadow 200ms, transform 200ms;

  & .objective-tick-mark {
    width: 14px;
    height: 14px;
    color: var(--surface);
    opacity: 0;
    transform: scale(0.5);
    transition: opacity 200ms, transform 200ms cubic-bezier(0.34, 1.56, 0.64, 1);
  }
}

.objective-row.done .objective-toggle {
  & .objective-title {
    color: var(--ink-faint);
    text-decoration: line-through;
    text-decoration-color: color-mix(in oklch, var(--wine) 60%, transparent);
  }

  & .objective-tick {
    background: radial-gradient(circle at 35% 30%,
      color-mix(in oklch, var(--wine-bright) 90%, transparent) 0%,
      color-mix(in oklch, var(--wine) 90%, transparent) 60%,
      color-mix(in oklch, var(--wine-deep) 100%, transparent) 100%);
    border-color: color-mix(in oklch, var(--wine-deep) 80%, transparent);
    box-shadow:
      inset 0 1px 0 color-mix(in oklch, var(--wine-bright) 60%, transparent),
      0 0 6px color-mix(in oklch, var(--wine) 40%, transparent);

    & .objective-tick-mark {
      opacity: 1;
      transform: scale(1);
    }
  }
}

.objective-title {
  flex: 1;
  font-family: var(--font-serif);
  font-size: var(--fs-md);
  line-height: var(--lh-base);
  text-wrap: pretty;
}

@media (prefers-reduced-motion: reduce) {
  .objective-toggle,
  .objective-tick,
  .objective-tick-mark { transition: none; }
}

/* Edit mode: each objective is a text input + remove button; an "Add"
   button at the end appends a fresh blank row via the Stimulus controller. */
.objectives-edit {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--s-2);

  & .objective-row {
    display: flex;
    align-items: center;
    gap: var(--s-2);
  }
}

/* Box / border / focus chrome comes from the shared `.editing :is(...)`
   rule above — only the row-specific fitting (flex grow, slightly
   larger serif body for the objective body) lives here. */
.objective-title-input {
  flex: 1;
  font-family: var(--font-serif);
  font-size: var(--fs-md);
  padding: var(--s-2) var(--s-3);
}

.objective-remove {
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  color: var(--ink-faint);
  cursor: pointer;
  transition: color 160ms, background 160ms, border-color 160ms;

  & svg {
    width: 14px;
    height: 14px;
  }

  &:hover {
    color: var(--wine);
    background: color-mix(in oklch, var(--wine) 8%, transparent);
    border-color: color-mix(in oklch, var(--wine) 22%, transparent);
  }
}

.objective-add {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: var(--s-2) var(--s-3);
  background: transparent;
  border: 1px dashed color-mix(in oklch, var(--ink) 22%, transparent);
  border-radius: var(--r-sm);
  font-family: var(--font-sans);
  font-size: var(--fs-sm);
  color: var(--ink-soft);
  cursor: pointer;
  transition: color 160ms, border-color 160ms, background 160ms;

  & svg {
    width: 14px;
    height: 14px;
  }

  &:hover {
    color: var(--ember-deep);
    border-color: color-mix(in oklch, var(--ember) 45%, transparent);
    background: color-mix(in oklch, var(--ember) 6%, transparent);
  }
}

/* ---------------------------------------------------- assets module
   List of asset cards attached to the quest, plus two affordances under
   the list: an autocomplete picker for attaching an existing asset, and
   a dropzone for uploading a brand-new one (which inherits the quest's
   projects + the asset-grade subset of its tags). Each card itself is
   a drop target — a file landing on it becomes a new AssetVersion of
   that asset, no navigation needed. */
.quest-assets .assets-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-3);
  margin-bottom: var(--s-3);
}

.quest-asset-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}

/* A card is a row: thumb · info · detach button. The whole card surface
   doubles as a drop target (form wraps the row, with a dropzone-routed
   `drop` handler); the link covers thumb+info so clicks navigate to
   the asset show. The detach button sits on top of the form. */
.quest-asset-card {
  position: relative;
  display: grid;
  grid-template-columns: 48px 1fr 28px 28px;
  gap: var(--s-3);
  align-items: center;
  padding: var(--s-3);
  background: color-mix(in oklch, var(--ink) 4%, transparent);
  border: 1px solid color-mix(in oklch, var(--ink) 12%, transparent);
  border-radius: var(--r-sm);
  transition:
    border-color var(--dur-fast) var(--ease),
    background var(--dur-fast) var(--ease);
}

.quest-asset-drop {
  display: contents;
  margin: 0;
}

.quest-asset-link {
  grid-column: 1 / span 2;
  display: grid;
  grid-template-columns: 48px 1fr;
  gap: var(--s-3);
  align-items: center;
  min-width: 0;
  color: inherit;
  text-decoration: none;
}

.quest-asset-thumb {
  display: block;
  width: 48px;
  height: 48px;
  border-radius: var(--r-xs);
  background:
    var(--preview, none) center / cover no-repeat,
    repeating-conic-gradient(
      oklch(0% 0 0 / 0.10) 0% 25%,
      oklch(0% 0 0 / 0.04) 0% 50%) 0 0 / 8px 8px,
    color-mix(in oklch, var(--ink) 8%, transparent);
  border: 1px solid color-mix(in oklch, var(--ink) 14%, transparent);
}

.quest-asset-info {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.quest-asset-title {
  font-family: var(--font-serif);
  font-size: var(--fs-md);
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.quest-asset-link:hover .quest-asset-title {
  color: var(--wine);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.quest-asset-meta {
  font-family: var(--font-sans);
  font-size: var(--fs-xs);
  color: var(--ink-faint);
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s-1);

  & .sep { opacity: 0.5; }
  & .quest-asset-category { text-transform: uppercase; letter-spacing: 0.08em; }
  & .quest-asset-version  { font-variant-numeric: tabular-nums; color: var(--ink-soft); }
}

/* The hidden file input lives in the form for direct_upload's submit
   hook. No pointer events — drops are routed via Stimulus, clicks
   never reach it. */
.quest-asset-drop-input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
  overflow: hidden;
}

/* The "Drop here for a new version" hint is invisible at rest and
   fades in only while a drag is hovering the card. */
.quest-asset-drop-hint {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--s-2);
  border-radius: inherit;
  background: color-mix(in oklch, var(--ember) 14%, transparent);
  font-family: var(--font-serif);
  font-style: italic;
  font-size: var(--fs-sm);
  color: var(--ember-deep);
  text-shadow: 0 0 6px color-mix(in oklch, var(--surface) 70%, transparent);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-fast) var(--ease);
}

.quest-asset-card:has(.quest-asset-drop.is-dragover),
.quest-asset-card.is-dragover {
  border-color: color-mix(in oklch, var(--ember) 55%, transparent);

  & .quest-asset-drop-hint {
    opacity: 1;
  }
}

/* The drop catcher applies dragover state to the form, which lives in
   display: contents — so we paint the card border via :has(). The card
   surface fades but the link still clicks. */
.quest-asset-drop.is-dragover ~ * { /* keeps the cascade explicit */ }

/* The pair of icon-only actions at the right end of a card: an upload
   label (col 3) and the detach button (col 4). Same square footprint, the
   tones differ — upload hovers ember (additive), detach hovers wine
   (subtractive). Both sit above the drop-hint overlay via z-index. */
.quest-asset-upload-icon,
.quest-asset-detach {
  width: 26px;
  height: 26px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  color: var(--ink-faint);
  cursor: pointer;
  position: relative;
  z-index: 1;
  transition: color var(--dur-fast) var(--ease),
              background var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease);

  & svg { width: 14px; height: 14px; }
}

.quest-asset-upload-icon {
  grid-column: 3;

  &:hover {
    color: var(--ember-deep);
    background: color-mix(in oklch, var(--ember) 8%, transparent);
    border-color: color-mix(in oklch, var(--ember) 35%, transparent);
  }
}

.quest-asset-detach {
  grid-column: 4;

  &:hover {
    color: var(--wine);
    background: color-mix(in oklch, var(--wine) 8%, transparent);
    border-color: color-mix(in oklch, var(--wine) 22%, transparent);
  }
}

.quest-asset-detach-form {
  grid-column: 4;
  margin: 0;
  position: relative;
  z-index: 1;
}

/* Bottom drop surface — a single dashed box that takes both clicks
   (native <input type="file"> dialog) and drops (input catches them). */
.quest-asset-new {
  margin-top: var(--s-3);
}

.quest-asset-dropzone {
  padding: var(--s-4);
}

/* "Attach an existing asset…" trigger living in the module header on
   the right. A discreet wine-italic link — opens the modal gallery. */
.quest-asset-attach-trigger {
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  background: transparent;
  border: 0;
  padding: 0;
  font-family: var(--font-serif);
  font-style: italic;
  font-size: var(--fs-sm);
  color: var(--wine);
  text-decoration: underline dotted;
  text-underline-offset: 3px;
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease);

  & .quest-asset-attach-icon { width: 12px; height: 12px; }

  &:hover {
    color: var(--wine-bright);
  }
}

/* Modal gallery — a wide modal-body shell with a search bar, a row of
   tag chips, and a responsive grid of asset cards. Each card is a
   button-form: a click on the card attaches the asset and closes the
   dialog (Turbo follows the server redirect back to the quest). */
.asset-gallery-modal {
  max-width: min(960px, 96vw);
}

.asset-gallery-body {
  padding: var(--s-6);
  max-height: min(80vh, 720px);
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
}

.asset-gallery-head {
  display: flex;
  flex-direction: column;
  gap: var(--s-1);
}

.asset-gallery-title {
  font-family: var(--font-serif);
  font-style: italic;
  font-size: var(--fs-lg);
  margin: 0;
}

.asset-gallery-hint {
  margin: 0;
  font-family: var(--font-serif);
  font-size: var(--fs-sm);
  color: var(--ink-faint);
}

.asset-gallery-controls {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}

.asset-gallery-search {
  width: 100%;
  font-family: var(--font-serif);
  font-style: italic;
  font-size: var(--fs-md);
  padding: var(--s-2) var(--s-3);
  border: 1px solid color-mix(in oklch, var(--ink) 18%, transparent);
  border-radius: var(--r-sm);
  background: color-mix(in oklch, var(--surface) 70%, transparent);
  color: var(--ink);

  &::placeholder { color: var(--ink-faint); }

  &:focus {
    outline: none;
    border-color: color-mix(in oklch, var(--ember) 55%, transparent);
    box-shadow: 0 0 0 3px color-mix(in oklch, var(--ember) 18%, transparent);
  }
}

.asset-gallery-tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-1);
}

.asset-gallery-tag {
  font-family: var(--font-sans);
  font-size: var(--fs-xs);
  letter-spacing: 0.04em;
  padding: 2px var(--s-2);
  border-radius: 999px;
  border: 1px solid color-mix(in oklch, var(--ink) 18%, transparent);
  background: transparent;
  color: var(--ink-soft);
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease),
              background var(--dur-fast) var(--ease);

  &:hover {
    border-color: color-mix(in oklch, var(--wine) 38%, transparent);
    color: var(--wine);
  }

  &.active {
    background: color-mix(in oklch, var(--wine) 18%, transparent);
    border-color: color-mix(in oklch, var(--wine) 50%, transparent);
    color: var(--wine);
  }
}

.asset-gallery-grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: var(--s-3);
  overflow-y: auto;
  min-height: 0;
}

.asset-gallery-item-form {
  margin: 0;
}

.asset-gallery-card {
  display: flex;
  flex-direction: column;
  gap: var(--s-1);
  width: 100%;
  padding: var(--s-2);
  background: color-mix(in oklch, var(--ink) 3%, transparent);
  border: 1px solid color-mix(in oklch, var(--ink) 12%, transparent);
  border-radius: var(--r-sm);
  text-align: left;
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease),
              transform var(--dur-fast) var(--ease),
              background var(--dur-fast) var(--ease);

  &:hover {
    border-color: color-mix(in oklch, var(--wine) 45%, transparent);
    background: color-mix(in oklch, var(--wine) 4%, transparent);
    transform: translateY(-1px);
  }
}

.asset-gallery-thumb {
  display: block;
  width: 100%;
  aspect-ratio: 1;
  border-radius: var(--r-xs);
  background:
    var(--preview, none) center / cover no-repeat,
    repeating-conic-gradient(
      oklch(0% 0 0 / 0.10) 0% 25%,
      oklch(0% 0 0 / 0.04) 0% 50%) 0 0 / 10px 10px,
    color-mix(in oklch, var(--ink) 8%, transparent);
  border: 1px solid color-mix(in oklch, var(--ink) 14%, transparent);
}

.asset-gallery-name {
  font-family: var(--font-serif);
  font-size: var(--fs-sm);
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.asset-gallery-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  font-family: var(--font-sans);
  font-size: var(--fs-xs);
  color: var(--ink-faint);
}

.asset-gallery-category {
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.asset-gallery-tag-chip {
  padding: 0 6px;
  border-radius: 999px;
  background: color-mix(in oklch, var(--ink) 6%, transparent);
  color: var(--ink-soft);
}

.asset-gallery-empty {
  text-align: center;
  font-family: var(--font-serif);
  font-style: italic;
  color: var(--ink-faint);
  padding: var(--s-5);
}

.asset-gallery-item[hidden] { display: none; }

.quest-assets.frozen .quest-asset-card {
  opacity: 0.85;
  background: color-mix(in oklch, var(--ink) 2%, transparent);
}


/* ----------------------------------------------------------- foot */
/* ---------------------------------------------------- party module */
.party {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s-3);
}

.signature {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);

  & .user-stamp {
    width: 44px;
    height: 44px;
    transform: rotate(-2deg);
  }

  & .signed-by {
    font-family: var(--font-serif);
    font-style: italic;
    font-size: var(--fs-sm);
    color: var(--ink-soft);
  }

  &.unsealed .signed-by { color: var(--ink-faint); }
}

.participants {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);

  & .party-amp {
    font-family: var(--font-serif);
    font-style: italic;
    font-size: var(--fs-sm);
    color: var(--ink-faint);
  }

  & .participant {
    display: inline-flex;
    margin-left: -8px;

    &:first-of-type { margin-left: 0; }

    & .user-stamp.sm {
      width: 30px;
      height: 30px;
      transform: rotate(-2deg);
    }
  }
}

/* A labelled editor group — a small-caps label over its control, centred.
   Shared by the in-place leader / participants / projects pickers. */
.edit-field {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-2);
}

.edit-label {
  font-family: var(--font-sans);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--ink-faint);
}

/* A wrapped, centred row of checkbox labels (participants, projects). */
.edit-checks {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--s-1) var(--s-3);
}

.edit-check {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  font-family: var(--font-serif);
  font-size: var(--fs-sm);
  color: var(--ink);
  cursor: pointer;
}

/* Party in edit mode — the wax stamps give way to the leader + participant
   pickers, stacked and centred so the section keeps its composed balance. */
.party-edit {
  flex-direction: column;
  align-items: center;
  gap: var(--s-4);
}

.editing .leader-select {
  width: auto;
  padding: var(--s-1) var(--s-5) var(--s-1) var(--s-3);
  font-family: var(--font-serif);
  font-size: var(--fs-sm);
  color: var(--ink);
  text-align: center;
}

/* Project link editor — sits where the launch button does in show mode. */
.project-link-edit {
  margin-top: var(--s-5);
}

.editing .url-input {
  width: min(100%, 30rem);
  padding: var(--s-1) var(--s-3);
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  text-align: center;
}


/* ---------------------------------------------------- actions footer
   Hosts the single CTA, centered. Edit + Destroy live up in .quest-tools
   so this surface stays exclusive to the page's main call-to-action — no
   secondary chrome competing for attention. */
.quest-actions {
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: var(--s-6);
}


/* ---------------------------------------------------- quest CTA hero
   The page's "do the thing" surface. Single neutral ember treatment — same
   size, same colour, same chrome regardless of rank. Energy and intensity
   live entirely in the canvas-driven feedback (see .quest-cta-haze-back /
   -front below) which the quest_cta_controller spawns on first hover. */
.quest-cta {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-3) var(--s-6);
  font-family: var(--font-serif);
  font-style: italic;
  font-size: var(--fs-md);
  letter-spacing: 0.02em;
  color: oklch(15% 0.05 30);
  background: var(--ember);
  border: 1px solid var(--ember-deep);
  border-radius: var(--r-md);
  cursor: pointer;
  box-shadow: 0 0 16px color-mix(in oklch, var(--ember-glow) 35%, transparent);
  text-shadow: 0 1px 0 oklch(0% 0 0 / 0.16);
  transition: transform var(--dur-fast) var(--ease),
              box-shadow var(--dur-base) var(--ease),
              filter var(--dur-fast) var(--ease);

  &:hover {
    transform: scale(1.03);
    box-shadow: 0 0 24px color-mix(in oklch, var(--ember-glow) 55%, transparent);
    filter: brightness(1.04);
  }

  &:active {
    transform: scale(0.97);
  }

  &:focus-visible {
    outline: 2px solid color-mix(in oklch, var(--cream) 60%, var(--ember-deep));
    outline-offset: 3px;
  }

  & .quest-cta-mark {
    width: 22px;
    height: 22px;
    display: inline-flex;
    align-items: center;
    justify-content: center;

    & .quest-cta-glyph {
      width: 22px;
      height: 22px;
      overflow: visible;
      /* Read the glyph on the button's lit fill: pale stops, deep
         outline — overrides the cream-outline defaults from quest-mark. */
      --marker-outline: oklch(20% 0.05 30);
    }
  }

  /* The pale palette per glyph: take shows the `!` exclaim, join / complete
     / turn-in the `?` query. */
  &.take .quest-cta-glyph { --glyph-paint: url(#qme-pale); }
  &.join .quest-cta-glyph,
  &.complete .quest-cta-glyph,
  &.turn-in .quest-cta-glyph { --glyph-paint: url(#qmq-pale); }
}


@media (prefers-reduced-motion: reduce) {
  .quest-cta:hover  { transform: none; }
  .quest-cta:active { transform: none; }
  .quest-cta-haze-back,
  .quest-cta-haze-front {
    display: none;
  }
}


/* ---------------------------------------------------- sheet-tools
   The bookkeeping corner of a parchment sheet, top-right. At rest it is a
   single kebab (⋮) opening the overflow menu (pin / edit / destroy — see the
   `menu` section in application.css). In edit mode the kebab gives way to the
   inline cancel (✗) + save (✓) pair, both icon-only and faint at rest. Shared
   by the quest, asset, note and project show pages. */
.sheet-tools {
  position: absolute;
  top: var(--s-3);
  right: var(--s-3);
  /* Above the editing masthead (z-index 2) so save / cancel stay clickable. */
  z-index: 3;
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
}

.sheet-tools form { margin: 0; }

.sheet-tools .sheet-save,
.sheet-tools .sheet-cancel {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--s-2);
  background: transparent;
  border: 0;
  border-radius: var(--r-sm);
  color: color-mix(in oklch, var(--ink) 35%, transparent);
  cursor: pointer;
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease),
              background var(--dur-fast) var(--ease),
              filter var(--dur-fast) var(--ease);

  & svg { width: 16px; height: 16px; }
}

/* Save warms to ember — committing the change is the constructive twin of
   opening the editor. Cancel stays neutral ink: backing out is not an action
   that wants attention. */
.sheet-tools .sheet-save:hover,
.sheet-tools .sheet-save:focus-visible {
  color: var(--ember);
  background: color-mix(in oklch, var(--ember-deep) 12%, transparent);
  filter: drop-shadow(0 0 8px color-mix(in oklch, var(--ember-glow) 50%, transparent));
  outline: none;
}

.sheet-tools .sheet-cancel:hover,
.sheet-tools .sheet-cancel:focus-visible {
  color: var(--ink);
  background: color-mix(in oklch, var(--ink) 8%, transparent);
  outline: none;
}


/* ---------------------------------------------------- quest-cta haze canvases
   Two fullscreen WebGL2 layers mounted on documentElement so they survive
   Turbo's body swap on transition POSTs. The back layer carries the slow
   mist around the button — hover ramps it up, leave fades it out, press
   sucks it inward. The front layer renders only during the brief release
   burst (sparks + outward propulsion), then idles transparent. Both are
   pointer-events: none so they never intercept clicks. */
.quest-cta-haze-back,
.quest-cta-haze-front {
  position: fixed;
  inset: 0;
  width: 100vw;
  height: 100vh;
  pointer-events: none;
}

/* `screen` blending = the canvas can only *lighten* the parchment beneath
   it, never bruise it. On a light surface this is what reads as fog —
   white-cream wisps that lift the surface value instead of stamping their
   own colour onto it. */
.quest-cta-haze-back  { z-index: 2147483600; mix-blend-mode: screen; }
.quest-cta-haze-front { z-index: 2147483700; mix-blend-mode: screen; }


/* ---------------------------------------------------- brass button
   Engraved brass plate. The exception to the "no bombé" rule per
   CLAUDE.md: each shadow has a role — outer cast shadow grounds the
   plate on the parchment, inset highlights/shadows simulate the rim
   bevel of an engraved nameplate. Reads as a *material*, not a faked
   3D effect. */
.btn.brass {
  --plate:        oklch(58% 0.08 85);
  --plate-light:  oklch(76% 0.08 88);
  --plate-shadow: oklch(34% 0.05 75);
  background: var(--plate);
  color: oklch(20% 0.05 50);
  border: 1px solid var(--plate-shadow);
  border-radius: var(--r-xs);
  font-family: var(--font-sans);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-weight: 500;
  padding: var(--s-2) var(--s-4);
  text-shadow: 0 1px 0 color-mix(in oklch, var(--plate-light) 80%, transparent);
  box-shadow:
    inset 0 1px 0 color-mix(in oklch, var(--plate-light) 75%, transparent),
    inset 0 -1px 0 color-mix(in oklch, var(--plate-shadow) 50%, transparent),
    0 1px 1px oklch(0% 0 0 / 0.45);
  transition: filter var(--dur-fast) var(--ease);

  & svg { width: 13px; height: 13px; }

  &:hover {
    filter: brightness(1.05);
  }

}


/* ----------------------------------------------------------- journal */
/* Native <details> — closed by default. Summary reads like a footer
   link; expanded panel shows the bookkeeping in small print. */
.journal {
  max-width: 60ch;
  margin: var(--s-5) auto 0;

  & > summary {
    font-family: var(--font-sans);
    font-size: var(--fs-xs);
    text-transform: uppercase;
    letter-spacing: 0.14em;
    color: var(--ink-faint);
    cursor: pointer;
    list-style: none;
    padding: var(--s-1) 0;
    transition: color var(--dur-fast) var(--ease);

    &::-webkit-details-marker { display: none; }

    &::before {
      content: "▸";
      display: inline-block;
      margin-right: var(--s-2);
      transition: transform var(--dur-fast) var(--ease);
    }

    &:hover { color: var(--ink-soft); }
  }

  &[open] > summary::before { transform: rotate(90deg); }
}

.journal-list {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: var(--s-2) var(--s-5);
  margin-top: var(--s-3);
  font-size: var(--fs-xs);

  & dt {
    font-family: var(--font-sans);
    color: var(--ink-faint);
    text-transform: uppercase;
    letter-spacing: 0.1em;
  }

  & dd {
    color: var(--ink-soft);
    font-family: var(--font-serif);
    font-style: italic;
  }
}


/* ----------------------------------------------------------- responsive */
@media (max-width: 720px) {
  .quest-page {
    padding: var(--s-5) var(--s-4) var(--s-5);
  }

  .quest-head { padding: 0; }

  .quest-title { font-size: clamp(1.4rem, 6vw, 2rem); }

  .quest-entry {
    grid-template-columns: 36px 1fr;

    & .entry-aside {
      grid-column: 2;
      justify-content: flex-start;
      max-width: none;
      margin-top: 4px;
    }
  }
}

