/* ==========================================================================
   mytarot — ink on paper
   One stylesheet, layered: tokens, reset, chrome, forms, wait, table.
   ========================================================================== */

@font-face {
  font-family: "Doves Type";
  src: url("/assets/DovesType-Regular-edfeaa85.woff2") format("woff2");
  /* One roman cut. Map the weights the page already asks for so the
     browser does not synthesise a light or a bold. Italic still slants. */
  font-weight: 300 600;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Mytarot Dingbats";
  src: url("/assets/MytarotDingbats-Regular-2906f9da.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: block;
  unicode-range: U+2726;
}

:root {
  --paper:        #f4f1ea;
  --paper-panel:  #eae5da;
  --paper-raised: #fbf9f5;

  --ink:          #2f2c27;
  --ink-dim:      #6a655b;
  --ink-faint:    #97917f;

  --gold:         #856121;
  --gold-tint:    #c0a984;
  --ember:        #a3452e;

  --rule:         rgba(47, 44, 39, 0.13);
  --rule-strong:  rgba(47, 44, 39, 0.28);

  /* Cards sit on the page rather than glowing out of it. */
  --lift:         0 1px 2px rgba(47, 44, 39, 0.10), 0 6px 18px rgba(47, 44, 39, 0.13);
  --lift-high:    0 2px 4px rgba(47, 44, 39, 0.12), 0 12px 30px rgba(47, 44, 39, 0.20);

  /* The cloth the cards are dealt onto: dark warm wool, lit from the head of
     the page like the wash above it. Cards on cloth need their own elevation
     and their own dimming — a paper shadow is too soft to read against it, and
     a translucent card over cloth goes dark rather than faint. */
  --cloth:           #b3a48c;
  --cloth-sheen:     rgba(255, 250, 238, 0.34);  /* lit from the head */
  --cloth-shade:     rgba(74, 66, 52, 0.20);     /* pooling at the foot */
  --cloth-drape:     rgba(74, 66, 52, 0.16);     /* the fall into the edges */
  --cloth-ink:       #4a453c;   /* a card's number, printed on the cloth */
  --cloth-mark:      rgba(133, 97, 33, 0.34);    /* the bed a card lands in */
  --cloth-mark-ink:  rgba(133, 97, 33, 0.60);    /* the position's name in it */
  --cloth-lift:      0 1px 2px rgba(41, 36, 27, 0.24), 0 8px 20px rgba(41, 36, 27, 0.26);
  --cloth-lift-high: 0 2px 4px rgba(41, 36, 27, 0.28), 0 14px 30px rgba(41, 36, 27, 0.34);
  --cloth-bleed:     clamp(1.75rem, 5vw, 3.5rem); /* cloth beyond the card box */
  --cloth-hem:       1.1rem;    /* the stitch line, inside the cloth's edge */
  --cloth-dim:       0.62;      /* how far a card not being read sinks in */
  --mark-gap:        0.2rem;    /* how far the bed stands outside the card */

  --display: "Doves Type", Georgia, serif;
  --ornament: "Mytarot Dingbats", "Zapf Dingbats", "Segoe UI Symbol", sans-serif;
  --body: var(--display);
  /* Labels: real small caps from Doves Type (`smcp` + `c2sc`). Uppercase
     stays as the fallback if the face does not load. Small-cap glyphs read
     smaller than the old sans, so --label-scale doubles the label sizes. */
  --label-caps: all-small-caps;
  --label-scale: 2;
  --copy-size: 1.25rem;
  --copy-leading: 1.65;
  --prose-size: 1.55rem;
  /* A card's notes sit a step under the reading's own voice. */
  --note-size: 1.4rem;
  --note-leading: 1.8rem;
  --footnote-size: 1rem;
  --keyword-size: 1rem;

  --measure: 34rem;
  --gutter: clamp(1.25rem, 4vw, 3rem);
  --prose-leading: 2.1rem;
  --passage-heading-size: calc(0.7rem * var(--label-scale));
  --conclusion-heading-size: calc(1.05rem * var(--label-scale));
  /* A card's position sits under "The Spread", and reads a step down from it. */
  --position-heading-size: calc(1.05rem * var(--label-scale));
  --conclusion-size: var(--prose-size);
  --conclusion-leading: var(--prose-leading);
  --conclusion-drop: 3.75rem;
  --conclusion-fade: 0.5s;
  --reading-stream-min: 24rem;

  --wait-corona-size: 32px;
  --wait-halo-border: 2px;
  --wait-breathe-duration: 2s;
  --wait-breathe-min: 0.2;
  --wait-stage-gap: 1.5rem;

  /* Ornament. One stroke weight for every drawn mark, so the fleuron glyph,
     the corner flourish and the tailpiece all read as the same hand. */
  --ornament-stroke:       1;
  --ornament-terminal:     clamp(2rem, 6vw, 3rem);  /* a terminal's box, 38 × 16 */
  --ornament-tailpiece:    clamp(8rem, 34%, 12rem);
  --ornament-rule-offset:  3rem;     /* the bound, out from the content column */
  --ornament-bound-inset:  1.5rem;   /* where the bound's rules stop short */

  --card-ratio: 0.6;      /* 768 × 1280 card scans */
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
}

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

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

body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--paper);
  color: var(--ink);
  font: 300 var(--copy-size)/var(--copy-leading) var(--body);
  letter-spacing: 0.01em;
  overflow-x: hidden;
}

/* A single fixed wash behind everything: warm at the head, cooler and heavier
   at the foot and corners, the way a sheet ages. No image, no repaint. */
.wash {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(ellipse 120% 70% at 50% -15%, rgba(196, 172, 118, 0.20), transparent 65%),
    radial-gradient(ellipse 100% 60% at 50% 115%, rgba(120, 112, 96, 0.16), transparent 65%),
    radial-gradient(circle at 8% 12%, rgba(47, 44, 39, 0.05), transparent 38%),
    radial-gradient(circle at 94% 84%, rgba(47, 44, 39, 0.05), transparent 38%);
}

h1, h2, h3 { font-family: var(--display); font-weight: 400; letter-spacing: 0.01em; line-height: 1.15; margin: 0; }
p { margin: 0 0 1em; }
a { color: var(--ink); text-decoration-color: var(--rule-strong); text-underline-offset: 0.25em; }
a:hover { color: var(--gold); }

/* --- chrome ------------------------------------------------------------- */

.masthead {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1.5rem;
  flex-wrap: wrap;
  padding: 1.5rem var(--gutter);
  border-bottom: 1px solid var(--rule);
}

.masthead__mark {
  font-family: var(--display);
  font-size: 2.2rem;
  letter-spacing: 0.12em;
  text-decoration: none;
  color: var(--ink);
}
.masthead__glyph {
  font-family: var(--ornament);
  font-style: normal;
  color: var(--gold);
}

.masthead__nav {
  display: flex;
  align-items: baseline;
  gap: 1.5rem;
  font-size: calc(0.8rem * var(--label-scale));
  letter-spacing: 0.09em;
  text-transform: uppercase;
}
.masthead__nav a {
  color: var(--ink-dim);
  text-decoration: none;
}
.masthead__nav a:hover { color: var(--gold); }

.account-actions {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin-bottom: 2.5rem;
}
.account-actions form { margin: 0; }
.account-actions .sub { margin: 0; }

/* Entitlement on the account page: apparatus, not prose and not a card.
   Hairline separates identity/actions above from the form below. */
.sub:has(+ .quota),
.account-actions:has(+ .quota) { margin-bottom: 0; }
.quota {
  margin: 1.75rem 0 2.5rem;
  padding-top: 1.1rem;
  border-top: 1px solid var(--rule);
}
.quota__plan {
  margin: 0 0 0.35rem;
  font-size: calc(0.7rem * var(--label-scale));
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--gold);
}
.quota__usage {
  margin: 0;
  color: var(--ink-dim);
  font-size: 1.2rem;
}
.quota__usage em {
  font-style: italic;
  color: var(--gold);
  font-family: var(--display);
  font-size: 2em;
}

.page {
  position: relative;   /* the ruled bound is positioned against it */
  flex: 1;
  width: 100%;
  padding: clamp(2rem, 6vw, 5rem) var(--gutter);
}

/* One hairline rule and a single row, mirroring the masthead: the label sits
   at the left margin, the credit at the right, and the whole thing is as tall
   as one line of type. On a phone it stacks and stays quiet. */
.colophon {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem 1.5rem;
  flex-wrap: wrap;
  padding: 0.9rem var(--gutter);
  border-top: 1px solid var(--rule);
  color: var(--ink-faint);
  font-size: 0.9rem;
  letter-spacing: 0.09em;
}
.colophon p { margin: 0; }
.colophon__credit { margin-left: auto; }
.colophon__sep {
  font-family: var(--ornament);
  font-style: normal;
  color: var(--gold-tint);
  margin: 0 0.35em;
}
.colophon__link {
  font-size: calc(0.7rem * var(--label-scale));
  text-transform: uppercase;
  text-decoration: none;
  letter-spacing: 0.12em;
  color: var(--ink-dim);
}
.colophon__link:hover { color: var(--gold); }
.colophon__credit-link {
  color: inherit;
  text-decoration: none;
}
.colophon__credit-link:hover { color: var(--gold); }

.flashes { padding: 0 var(--gutter); margin-top: 1rem; }
.flash {
  margin: 0 auto;
  max-width: var(--measure);
  padding: 0.7rem 1rem;
  border-left: 2px solid var(--gold);
  background: var(--paper-raised);
  font-size: 1.15rem;
}
.flash--alert { border-left-color: var(--ember); }

/* --- prose blocks -------------------------------------------------------- */

.centered { max-width: var(--measure); margin-inline: auto; }

.lede {
  font-family: var(--display);
  font-size: clamp(1.9rem, 5vw, 2.9rem);
  font-weight: 300;
  line-height: 1.1;
  margin-bottom: 0.6rem;
}
.lede em { font-style: italic; color: var(--gold); }

/* The closed sign's own phrase, inside an ordinary lede. Its own block, so
   the sentence around it stays at reading size and this carries the weight.
   Upright rather than italic: Doves Type ships a real `smcp` table, and
   synthesised italic small capitals would fight it. */
.lede__notice {
  display: block;
  /* One line, always. The phrase is twenty-odd characters and the column is
     capped at --measure, so the size is bounded by what fits rather than by
     taste: 8vw on a phone, 3.2rem once the column stops growing. `nowrap`
     holds the line if a font substitution comes in wider than expected. */
  font-size: min(3.2rem, 8vw);
  font-style: normal;
  font-variant-caps: small-caps;
  letter-spacing: 0.04em;
  line-height: 1.15;
  white-space: nowrap;
}

.sub {
  color: var(--ink-dim);
  font-size: 1.2rem;
  margin-bottom: 2.5rem;
}

/* Long-form pages: the colophon and anything else that is mostly words. */
.prose h2 {
  font-family: var(--display);
  font-size: 1.5rem;
  font-weight: 400;
  letter-spacing: 0.02em;
  margin: 1.6rem 0 0.75rem;
}
.prose p {
  line-height: 1.7;
  margin-bottom: 1.1rem;
}
.prose em { color: var(--gold); }

.eyebrow {
  font-size: calc(0.7rem * var(--label-scale));
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--gold);
  margin-bottom: 0.75rem;
}

/* --- forms --------------------------------------------------------------- */

.field { margin-bottom: 1.75rem; }
.field > label {
  display: block;
  font-size: calc(0.72rem * var(--label-scale));
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-dim);
  margin-bottom: 0.6rem;
}
.field__hint { font-size: 1rem; color: var(--ink-faint); margin: 0.5rem 0 0; }
.field__value {
  margin: 0;
  font: 300 var(--copy-size)/1.5 var(--body);
  color: var(--ink);
}

.field__secret { position: relative; }
.field__unmask {
  position: absolute;
  right: 0.75rem;
  top: 50%;
  transform: translateY(-50%);
  padding: 0;
  background: none;
  border: 0;
  color: var(--gold);
  font: 400 calc(0.72rem * var(--label-scale))/1 var(--body);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  cursor: pointer;
}
.field__unmask:hover { color: var(--ink); }
.field__secret .field_with_errors { display: contents; }

input[type="text"], input[type="password"], select, textarea {
  width: 100%;
  padding: 0.85rem 1rem;
  background: var(--paper-raised);
  color: var(--ink);
  border: 1px solid var(--rule-strong);
  border-radius: 2px;
  font: 300 var(--copy-size)/1.5 var(--body);
  transition: border-color 0.25s var(--ease);
}
textarea {
  font-family: var(--display);
  font-size: 1.5rem;
  line-height: 1.4;
  resize: vertical;
  min-height: 6rem;
}
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--gold);
}
.field__secret input { padding-right: 4.5rem; }
select { appearance: none; background-image: none; cursor: pointer; }
select option { background: var(--paper-raised); }

.button {
  display: inline-block;
  padding: 0.85rem 2.25rem;
  background: transparent;
  color: var(--gold);
  border: 1px solid var(--gold);
  border-radius: 2px;
  font: 400 calc(0.78rem * var(--label-scale))/1 var(--body);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  transition: background 0.3s var(--ease), color 0.3s var(--ease);
}
.button:hover { background: var(--gold); color: var(--paper); }

.ask {
  text-align: center;
  margin-bottom: 0;
}
.lede + .ask { margin-top: 1.75rem; }

.errors {
  border-left: 2px solid var(--ember);
  padding: 0.6rem 1rem;
  margin-bottom: 1.5rem;
  font-size: 1.15rem;
  color: var(--ink-dim);
}
.errors ul { margin: 0; padding-left: 1.1rem; }

.form-aside {
  margin-top: 2rem;
  font-size: 1.05rem;
  color: var(--ink-faint);
}

/* --- wait (while the draw runs) ------------------------------------------ */

.wait {
  min-height: 60vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
}

.wait__question {
  font-family: var(--display);
  font-style: italic;
  font-size: clamp(1.3rem, 3.5vw, 2rem);
  color: var(--ink);
  max-width: 75vw;
  margin-bottom: 0.5rem;
}

.wait__corona {
  display: block;
  width: var(--wait-corona-size);
  height: var(--wait-corona-size);
  margin-top: var(--wait-stage-gap);
  overflow: visible;
  flex-shrink: 0;
}
.wait__corona-track {
  fill: none;
  stroke: var(--rule-strong);
  stroke-width: var(--wait-halo-border);
}
.wait__corona-moon {
  fill: var(--gold);
}
.wait__corona-halo {
  fill: none;
  stroke: var(--gold);
  stroke-width: var(--wait-halo-border);
  opacity: 0;
}
.wait__corona--overrun .wait__corona-halo {
  animation: wait-breathe var(--wait-breathe-duration) ease-in-out infinite;
}
@keyframes wait-breathe {
  0%, 100% { opacity: var(--wait-breathe-min); }
  50%      { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .wait__corona--overrun .wait__corona-halo {
    animation: none;
    opacity: 1;
  }
}

.wait__status {
  margin-top: var(--wait-stage-gap);
  font-size: calc(0.75rem * var(--label-scale));
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

/* --- the table ----------------------------------------------------------- */

.reading__header,
.reading-layout { animation: rise 0.9s var(--ease) both; }
@keyframes rise {
  from { opacity: 0; transform: translateY(1.5rem); }
  to   { opacity: 1; transform: none; }
}

.reading__header { max-width: 80%; margin: 0 auto 3rem; text-align: center; }
.reading__question {
  font-family: var(--display);
  font-style: italic;
  font-size: clamp(1.5rem, 4vw, 2.3rem);
  line-height: 1.2;
  margin-bottom: 0.75rem;
}
.reading__meta {
  font-size: calc(0.72rem * var(--label-scale));
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
.reading__meta span + span::before { content: "·"; margin: 0 0.6em; color: var(--gold); }

/* Wide layout: the stream sits beside the spread and sets the row height,
   which is what gives the sticky stage its range. The conclusion lives under
   the spread inside that stage (the controller moves it there). Closing notes
   follow under the stream once the cards run out. */
.reading-layout {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(var(--reading-stream-min), 1fr);
  column-gap: clamp(1.5rem, 4vw, 4rem);
  max-width: 84rem;
  margin-inline: auto;
  padding-bottom: 6rem;
  transition: grid-template-columns var(--conclusion-fade) var(--ease);
}

.table-well { grid-column: 1; grid-row: 1; }
.stream     { grid-column: 2; grid-row: 1; margin-inline: 0; }
.closing    { grid-column: 2; grid-row: 2; margin-inline: 0; padding-top: 3rem; }

.passage { scroll-margin-top: 2rem; }

/* The well stretches with the stream; the stage sticks inside it. The lead-in
   is the controller's, and only sets where the spread rests before the stage
   pins — see #leadIn. */
.table-stage {
  position: sticky;
  top: 2rem;
  margin-top: var(--stage-lead, 0px);
}

.table {
  position: relative;
  width: 100%;
  aspect-ratio: var(--table-aspect);
  margin-inline: auto;
  /* The cloth is the query container the printed names size themselves
     against, so a name reads the same on a Celtic Cross's small cards as on a
     Single Card's large one. */
  container-type: inline-size;
  transition: width var(--conclusion-fade) var(--ease);
}
.table-stage[data-pinned] .table { width: min(100%, var(--pinned-table-width, 100%)); }

/* Under the spread the conclusion keeps the column's measure, not the stream's.
   It stays out of flow until the stage pins, then fades in and out with the
   same opacity transition (display waits for the fade via allow-discrete). */
.table-stage .conclusion {
  max-width: none;
  margin-inline: 0;
  margin-top: 2rem;
  padding-bottom: 1rem;
  display: none;
  opacity: 0;
  transition:
    opacity var(--conclusion-fade) var(--ease),
    display var(--conclusion-fade) allow-discrete;
}
.table-stage[data-pinned] .conclusion {
  display: block;
  opacity: 1;
}
@starting-style {
  .table-stage[data-pinned] .conclusion { opacity: 0; }
}

@media (min-width: 75rem) {
  .reading-layout:has(.table-stage[data-pinned]) {
    grid-template-columns: minmax(0, 3fr) minmax(0, 1fr);
  }
}

@media (max-width: 62rem) {
  .reading-layout { grid-template-columns: minmax(0, 1fr); }
  .table-well { grid-row: 1; margin-bottom: 3rem; }
  .stream { grid-column: 1; grid-row: 2; margin-inline: auto; }
  .closing { grid-column: 1; grid-row: 3; margin-inline: auto; border-top: 1px solid var(--rule); }
  .table-stage { position: relative; top: auto; margin-top: 0; }
  .table-stage .conclusion { margin-top: 0; }
}

/* --- the cloth ----------------------------------------------------------- */

/* The cards are dealt onto cloth, not onto the page. It is a box in the layout
   rather than a bleed off the table, so it cannot cover what sits beside or
   above it: the padding is the margin of cloth around the outermost card, and
   it has to be generous, because cards at the edge of a spread overhang the
   table box (the Horseshoe's arc rises above it) and every card's number sits
   above its card. The conclusion is deliberately outside it — it pins below
   the cloth, on paper, because the reading is read on paper and dealt on
   cloth.

   Wool: grey fractal noise blended into a warm near-black, lit from the head
   and pooling dark at the foot, with the weight of a cloth laid on the page. */
.cloth {
  position: relative;
  padding: var(--cloth-bleed);
  margin-inline: auto;
  border-radius: 2px;
  background-color: var(--cloth);
  background-image:
    url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='140' height='140'><filter id='weave'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/><feColorMatrix type='saturate' values='0'/></filter><rect width='140' height='140' filter='url(%23weave)' opacity='0.42'/></svg>"),
    radial-gradient(ellipse 85% 60% at 50% -5%, var(--cloth-sheen), transparent 62%),
    radial-gradient(ellipse 120% 80% at 50% 108%, var(--cloth-shade), transparent 70%);
  background-blend-mode: soft-light, normal, normal;
  box-shadow:
    inset 0 0 0 1px var(--cloth-sheen),
    inset 0 0 5rem var(--cloth-drape),
    0 2px 4px rgba(47, 44, 39, 0.14),
    0 14px 30px rgba(47, 44, 39, 0.18);
  transition: width var(--conclusion-fade) var(--ease);
}

/* The hem, stitched inside the cloth's edge. */
.cloth::after {
  content: "";
  position: absolute;
  inset: var(--cloth-hem);
  border: 1px dashed var(--cloth-mark);
  border-radius: 1px;
  opacity: 0.5;
  pointer-events: none;
}

/* The cloth contracts with the spread it carries. --pinned-table-width is set
   on the stage rather than the table, so both can read it. */
.table-stage[data-pinned] .cloth {
  width: min(100%, calc(var(--pinned-table-width, 100%) + 2 * var(--cloth-bleed)));
}

/* Every position in the spread is printed on the cloth: a bed a hair larger
   than the card, with the position's name inside it. The deal covers the
   names one by one; a bed no card reaches keeps its own. */
.mark {
  position: absolute;
  left: calc(var(--x) * 100%);
  top: calc(var(--y) * 100%);
  width: calc(var(--card-width) * 100%);
  aspect-ratio: var(--card-ratio);
  transform: translate(-50%, -50%) rotate(var(--rot));
  display: flex;
  align-items: center;
  justify-content: center;
  /* A length, not a percentage: percentage padding on an absolutely positioned
     box resolves against the table, which would set every bed the same size. */
  padding: 0 0.4rem;
  border-radius: 3px;
  outline: 1px solid var(--cloth-mark);
  outline-offset: var(--mark-gap);
}

.mark__name {
  font-size: calc(var(--card-width) * 13cqw * var(--label-scale));
  line-height: 1.25;
  letter-spacing: 0.1em;
  text-align: center;
  text-transform: uppercase;
  color: var(--cloth-mark-ink);
}

/* On a narrow screen the cloth runs off both edges of the page, so there is no
   edge left to hem. */
@media (max-width: 62rem) {
  .cloth {
    margin-inline: calc(50% - 50vw);
    border-radius: 0;
  }
  .cloth::after { display: none; }
}

/* A browser prints no backgrounds, so nothing of the cloth survives; what was
   written on it has to come back to ink. */
@media print {
  .cloth { box-shadow: none; }
  .cloth::after { display: none; }
  .mark { outline-color: var(--rule); }
  .mark__name,
  .slot__index { color: var(--ink-faint); }
}

.slot {
  position: absolute;
  left: calc(var(--x) * 100%);
  top: calc(var(--y) * 100%);
  width: calc(var(--card-width) * 100%);
  aspect-ratio: var(--card-ratio);
  transform: translate(-50%, -50%) rotate(var(--rot));
  border: 0;
  padding: 0;
  background: none;
  cursor: pointer;
  border-radius: 3px;
  /* Dealt one after another by --deal-delay. */
  animation: deal 1.4s var(--ease) both;
  animation-delay: var(--deal-delay);
  transition: filter 0.3s var(--ease);
}

@keyframes deal {
  from { opacity: 0; transform: translate(-50%, -50%) rotate(var(--rot)) translateY(-3rem) scale(0.86); }
  to   { opacity: 1; transform: translate(-50%, -50%) rotate(var(--rot)); }
}

.slot__art {
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
  border-radius: 3px;
  box-shadow: var(--cloth-lift);
  transition: box-shadow 0.35s var(--ease), filter 0.35s var(--ease);
}
.slot[data-reversed] .slot__art { transform: rotate(180deg); }

.slot:hover .slot__art,
.slot[data-active] .slot__art {
  box-shadow: 0 0 0 1px var(--gold), var(--cloth-lift-high);
}
/* Cards not being read sink into the cloth. On paper that was opacity; over
   cloth a translucent card only goes dark, so it is dimmed by light instead. */
.table[data-has-active] .slot:not([data-active]) .slot__art {
  filter: brightness(var(--cloth-dim)) saturate(0.7);
}

.slot__index {
  position: absolute;
  top: -1.4em;
  left: 50%;
  transform: translateX(-50%);
  font-size: 0.8rem;
  letter-spacing: 0.14em;
  color: var(--cloth-ink);
}

/* --- a card's entry in the stream ---------------------------------------- */

/* An h4 under "The Spread", so it takes the type the h1-h3 rule used to give
   it — including the zeroed margin the browser would otherwise supply. */
.entry__card {
  font-family: var(--display);
  font-weight: 400;
  letter-spacing: 0.01em;
  font-size: 1.8rem;
  line-height: 1.1;
  margin: 0 0 0.3rem;
}

/* The card's name is the way into the card. It reads as the heading it is,
   with the expand mark alongside to say so. */
.entry__open {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
  transition: color 0.25s var(--ease);
}
.entry__open:hover { color: var(--gold); }
.entry__open:focus-visible { outline: 1px solid var(--gold); outline-offset: 0.25rem; }

.entry__expand {
  width: 0.85rem;
  height: 0.85rem;
  flex: none;
  stroke: currentColor;
  stroke-width: 1.75;
  stroke-linecap: round;
  stroke-linejoin: round;
  fill: none;
  color: var(--gold-tint);
  transition: color 0.25s var(--ease);
}
.entry__open:hover .entry__expand { color: var(--gold); }
.entry__card small {
  display: block;
  font-family: var(--body);
  font-size: calc(0.7rem * var(--label-scale));
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ember);
  margin-top: 0.45rem;
}
/* The position's question, set directly under the position that asks it and
   centred with it — the two are one header, and the card answers them. Beaten
   on size by `.prose p` unless it matches that selector's weight. */
.entry__ask,
.prose .entry__ask {
  text-align: center;
  font-family: var(--display);
  font-style: italic;
  font-size: 1.05rem;
  line-height: 1.5;
  color: var(--ink-dim);
  margin: 0 0 1.6rem;
}

/* A card's attributions belong to its name, so they follow it rather than
   waiting at the foot of the passage. */
.entry__credit,
.prose .entry__credit { margin: 0 0 1.25rem; }

.keywords { display: flex; flex-wrap: wrap; gap: 0.4rem; margin: 0 0 1.5rem; padding: 0; list-style: none; }
.keywords li {
  font-size: var(--keyword-size);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-dim);
  border: 1px solid var(--rule);
  border-radius: 999px;
  padding: 0.25rem 0.7rem;
}

.note { margin-bottom: 1.4rem; }

/* A card's notes are a set of short blocks in one narrow column. They set a
   step under the reading's own voice, and they read as a set when their right
   edges line up. */
.entry .note p {
  font-size: var(--note-size);
  line-height: var(--note-leading);
}

/* Justified only while the column can still hold the full measure — under
   that, not even hyphenation keeps the rivers out, so they go ragged right. */
@media (min-width: 34rem) {
  .entry .note p {
    text-align: justify;
    hyphens: auto;
  }
}
.note h4 {
  font: 400 calc(0.68rem * var(--label-scale))/1 var(--body);
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin: 0 0 0.5rem;
}
.note p { margin: 0; }

/* --- the reading nav ------------------------------------------------------ */

/* Narrow screens only. On a wide screen the conclusion already sits under the
   sticky spread, so the foot pill would only crowd that column. */
.reading-nav {
  display: none;
  position: fixed;
  bottom: 1.5rem;
  left: 0;
  width: 100vw;
  justify-content: center;
  pointer-events: none;
  z-index: 2;
}

/* An open card makes the page inert; the nav should not linger under its
   scrim looking clickable. */
.reading:has(.lightbox[open]) .reading-nav { opacity: 0; }

.reading-nav__pill {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.3rem 0.4rem;
  border: 1px solid var(--rule-strong);
  border-radius: 999px;
  background: var(--paper-raised);
  box-shadow: var(--lift);
  pointer-events: auto;
}

.reading-nav__step {
  width: 1.9rem;
  height: 1.9rem;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: none;
  color: var(--ink-dim);
  cursor: pointer;
  transition: background 0.2s var(--ease), color 0.2s var(--ease);
}
.reading-nav__step:hover:not(:disabled) { background: var(--gold); color: var(--paper); }
.reading-nav__step:disabled { color: var(--ink-faint); opacity: 0.35; cursor: default; }
.reading-nav__step svg {
  width: 0.85rem;
  height: 0.85rem;
  stroke: currentColor;
  stroke-width: 1.75;
  stroke-linecap: round;
  stroke-linejoin: round;
  fill: none;
}

.reading-nav__where {
  display: flex;
  align-items: baseline;
  gap: 0.55rem;
  padding: 0 0.5rem;
  min-width: 0;
  max-width: 46vw;
}
.reading-nav__title {
  font-size: calc(0.72rem * var(--label-scale));
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.reading-nav__count {
  font-size: 0.85rem;
  letter-spacing: 0.1em;
  color: var(--ink-faint);
  white-space: nowrap;
}

@media (max-width: 62rem) {
  .reading-nav { display: flex; }
}

/* --- the lightbox --------------------------------------------------------- */

/* A <dialog>, for the top layer, Esc, and focus handling. The dialog itself is
   the whole viewport and transparent, so a click anywhere but the card or the
   close button lands on it and dismisses. It sets no overflow of its own, so a
   wheel over it still scrolls the reading underneath. */
.lightbox {
  width: 100vw;
  height: 100dvh;
  max-width: none;
  max-height: none;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
}
/* Nothing is dimmed. The scrim covers the card's own column and no more, so
   the reading beside it stays legible — which is the point of standing the
   card in that column rather than over the middle of the page. */
.lightbox::backdrop { background: transparent; }

/* On a wide screen the card takes the column the spread was in, so the reading
   stays where it was. --stage-left and --stage-width are measured from the
   spread's own well when the card is opened. */
.lightbox__frame {
  position: fixed;
  top: 0;
  left: var(--stage-left, 0px);
  width: var(--stage-width, 100vw);
  height: 100dvh;
  display: grid;
  place-items: center;
  /* Semi-transparent, so the fixed .wash behind shows through and the column's
     edge does not read as a seam against the rest of the page. */
  background: rgba(244, 241, 234, 0.94);
  pointer-events: none;
  animation: fade 0.25s var(--ease) both;
}

.lightbox__art {
  max-height: 100dvh;
  max-width: 100%;
  border-radius: 4px;
  box-shadow: var(--lift-high);
  pointer-events: auto;
}
.lightbox__art[data-reversed] { transform: rotate(180deg); }

@keyframes fade { from { opacity: 0; } to { opacity: 1; } }

.lightbox__close {
  position: fixed;
  top: 1rem;
  left: 1rem;
  width: 1.9rem;
  height: 1.9rem;
  display: grid;
  place-items: center;
  padding: 0;
  border: 1px solid var(--rule-strong);
  border-radius: 999px;
  background: var(--paper-raised);
  color: var(--ink);
  cursor: pointer;
  transition: background 0.25s var(--ease), color 0.25s var(--ease), border-color 0.25s var(--ease);
}
.lightbox__close:hover {
  background: var(--gold);
  border-color: var(--gold);
  color: var(--paper);
}
.lightbox__close svg {
  width: 0.75rem;
  height: 0.75rem;
  stroke: currentColor;
  stroke-width: 1.75;
  stroke-linecap: round;
  fill: none;
}

/* Narrow: the card gets the whole screen rather than a column of it. */
@media (max-width: 62rem) {
  .lightbox__frame {
    left: 0;
    width: 100vw;
    padding: 0 var(--gutter);
  }
}

/* --- the written reading -------------------------------------------------- */

.prose { max-width: var(--measure); margin-inline: auto; }
.prose > :first-child > h2:first-child,
.passage:first-child h2 { margin-top: 0; }

/* Section titles sit between hairlines broken by the same mark as the
   masthead: rule, diamond, title, diamond, rule.

   Top-level headings only. A card's position heading is a level under this
   one and is deliberately left bare — ornament thins as the hierarchy
   deepens, and a Celtic Cross would otherwise repeat this mark ten times
   inside a single section. */
h2 {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.65rem;
  text-align: center;
  margin: 2rem 0 1rem;
  font-size: var(--passage-heading-size);
  font-family: var(--body);
  font-weight: 400;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--gold);
}
h2::before,
h2::after {
  content: "✦";
  flex: 1 1 0;
  min-width: 2.5rem;
  font-family: var(--ornament);
  font-style: normal;
  font-variant-caps: normal;
  font-size: 1.0rem;
  letter-spacing: 0;
  line-height: 1;
  color: var(--gold);
}
h2::before {
  text-align: right;
  background:
    linear-gradient(var(--rule-strong), var(--rule-strong)) left center / calc(100% - 1.4em) 1px no-repeat;
}
h2::after {
  text-align: left;
  background:
    linear-gradient(var(--rule-strong), var(--rule-strong)) right center / calc(100% - 1.4em) 1px no-repeat;
}

/* A level under "The Spread": the same centred small capitals, without the
   diamonds, and with a rule that hugs the words rather than running the
   column's width — the section above it already owns the full-width rule. */
.entry__position {
  display: block;
  width: fit-content;
  margin-inline: auto;
  padding-bottom: 0.35rem;
  border-bottom: 1px solid var(--gold-tint);
  text-align: center;
  font-family: var(--display);
  font-size: var(--position-heading-size);
  font-weight: 400;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--gold);
  margin: 1.6rem auto 0.7rem;
}

/* "The Spread" and the first card's position are two titles in a row, with
   nothing between them to separate. */
h2 + .entry .entry__position { margin-top: 0.6rem; }

.conclusion {
  border-top: 1px solid var(--gold);
  border-bottom: 1px solid var(--gold);
  padding-block: 1.75rem 1.5rem;
  /* The rules are finished at both ends by a terminal, so the block's own
     contents — the title's hairlines as much as the prose — have to stand
     clear of the leaf rather than run under its tip. Derived from the mark's
     own width, capped as a share of the column so a phone does not lose its
     measure to it. */
  padding-inline: min(calc(var(--ornament-terminal) * 0.9), 6%);
}
/* Same treatment as any passage title, carrying only the size that says which
   passage this is. */
.conclusion > h2 {
  font-size: var(--conclusion-heading-size);
  margin-top: 0;
}

.prose p,
.observations li {
  font-family: var(--display);
  font-size: var(--prose-size);
  line-height: var(--prose-leading);
  color: var(--ink);
}

/* After .prose p so size, weight and italic win on the conclusion. */
.conclusion.prose > p {
  font-size: var(--conclusion-size);
  line-height: var(--conclusion-leading);
  font-style: italic;
  font-weight: 600;
}
.conclusion.prose > p::first-letter {
  float: left;
  font-family: var(--display);
  font-size: var(--conclusion-drop);
  font-style: italic;
  font-weight: 600;
  line-height: 0.85;
  padding: 0.12rem 0.4rem 0 0;
  color: var(--gold);
}

.observations { list-style: none; margin: 0; padding: 0; }
.observations li {
  border-left: 1px solid var(--rule);
  padding: 0.15rem 0 0.15rem 1.1rem;
  margin-bottom: 1rem;
  color: var(--ink-dim);
}
/* The rule carries the emphasis, so the type does not have to. */
.observations li[data-stands-out] { border-left-color: var(--gold); color: var(--ink); }

.footnote,
.prose .footnote {
  font-family: var(--body);
  color: var(--ink-faint);
  font-size: var(--footnote-size);
  line-height: 1.7;
}

/* --- ornament ------------------------------------------------------------- */

/* The app ornaments with rule, break, and mark. The dingbat ✦ is the mark
   that BREAKS a rule — the masthead, the h2 headings, the colophon. The
   terminal below is the mark that FINISHES one, and the tailpiece is the two
   of them together, once, at the end of a page.

   Ornament thins as the hierarchy deepens: h2 gets rules and diamonds, the
   card position under it gets none, and nothing interactive gets any. Buttons,
   inputs, the keyword chips, the masthead nav, the reading pill and the
   lightbox controls stay in the plain modern idiom on purpose. */

.ornament-sprite { position: absolute; width: 0; height: 0; overflow: hidden; }

/* One weight and one colour for every drawn mark. `stroke`, `stroke-width` and
   the cap joins are inherited properties, so they reach the cloned symbol
   through the <use> boundary; `vector-effect: non-scaling-stroke` sits on the
   paths themselves, which is what keeps the hairline identical whether the
   symbol is drawn at 42px or at 190px. */
.terminals__mark,
.tailpiece__mark {
  display: block;
  fill: none;
  stroke: currentColor;
  stroke-width: var(--ornament-stroke);
  stroke-linecap: butt;
  overflow: visible;
}

/* Four marks finishing a block's two rules, from one symbol. The drawn shape
   runs left to right; the rest are rotations and mirrors. Each is centred on
   its rule's end so the hairline it carries lies over the rule itself — the
   rule appears to swell into the leaf rather than to meet one. */
.terminals {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.terminals__mark {
  position: absolute;
  width: var(--ornament-terminal);
  height: calc(var(--ornament-terminal) * 16 / 38);   /* the symbol's own box */
}

/* Rules above and below: the marks point out to left and right. */
.terminals--h .terminals__mark--tl { top: 0;    left: 0;  transform: translateY(-50%) scaleX(-1); }
.terminals--h .terminals__mark--tr { top: 0;    right: 0; transform: translateY(-50%); }
.terminals--h .terminals__mark--bl { bottom: 0; left: 0;  transform: translateY(50%) scaleX(-1); }
.terminals--h .terminals__mark--br { bottom: 0; right: 0; transform: translateY(50%); }

/* Rules left and right: the same mark stood on end, pointing up and down.
   Centred on the rule's end first, then turned, then backed off along its own
   axis so the tip lands on the end rather than past it. */
.terminals--v .terminals__mark--tl { top: 0;    left: 0;  transform: translate(-50%, -50%) rotate(-90deg) translateX(-50%); }
.terminals--v .terminals__mark--tr { top: 0;    right: 0; transform: translate(50%, -50%)  rotate(-90deg) translateX(-50%); }
.terminals--v .terminals__mark--bl { bottom: 0; left: 0;  transform: translate(-50%, 50%)  rotate(90deg)  translateX(-50%); }
.terminals--v .terminals__mark--br { bottom: 0; right: 0; transform: translate(50%, 50%)   rotate(90deg)  translateX(-50%); }

/* The conclusion is the one block in the app framed by its own rules — gold,
   above and below — so it is the one block whose rules get finished. The cloth
   does not: its edge is already stitched with a hem. */
.conclusion { position: relative; }
.conclusion .terminals { color: var(--gold); }

/* The bound: two hairlines standing off the content column, stopped short at
   both ends and finished with the same mark. It draws only where there is
   margin to draw it in. The bound is --measure plus two offsets wide, so it
   needs about 44rem to exist at all and 52rem before there is any paper left
   outside it; below that the measured margin runs to a few pixels and then to
   none, so the bound is simply not there and the headings' own rules carry the
   boundaries. The reading page never opts in — its stream column
   faces the cloth, not paper, at every width. */
.page__bound { display: none; }

@media (min-width: 52rem) {
  .page--ruled .page__bound {
    display: block;
    position: absolute;
    top: var(--ornament-bound-inset);
    bottom: var(--ornament-bound-inset);
    left: 50%;
    width: min(
      100% - 2 * var(--gutter),
      calc(var(--measure) + 2 * var(--ornament-rule-offset))
    );
    transform: translateX(-50%);
    /* The rule and the mark that finishes it are one line, so they are one
       colour: --gold-tint, which the palette keeps for borders and fills. */
    border-left: 1px solid var(--gold-tint);
    border-right: 1px solid var(--gold-tint);
    color: var(--gold-tint);
    pointer-events: none;
  }
}

/* The end of a composition. Once per page, and never at a fixed interval. */
.tailpiece {
  display: flex;
  justify-content: center;
  margin-top: 3.5rem;
  color: var(--gold-tint);
}
.tailpiece__mark {
  width: var(--ornament-tailpiece);
  height: auto;
  aspect-ratio: 15 / 2;   /* the symbol's own 120 × 16 */
}

/* One opening letter per document, in ink. The conclusion's is gold and stays
   the only gold one, so the reading's destination is not competed with. */
.prose__opening::first-letter {
  float: left;
  font-family: var(--display);
  font-size: var(--conclusion-drop);
  line-height: 0.85;
  padding: 0.12rem 0.4rem 0 0;
  color: var(--ink);
}

/* --- the list ------------------------------------------------------------- */

.entries { list-style: none; margin: 0; padding: 0; max-width: var(--measure); margin-inline: auto; }
.entries li { display: flex; align-items: center; gap: 1rem; border-bottom: 1px solid var(--rule); }
.entries a {
  flex: 1;
  min-width: 0;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1.5rem;
  padding: 1.1rem 0;
  text-decoration: none;
}
.entries a:hover .entries__question { color: var(--gold); }
.entries__delete-form { margin: 0; flex: none; }
.entries__delete {
  width: 1.92rem;
  height: 1.92rem;
  display: grid;
  place-items: center;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 999px;
  background: none;
  color: var(--ember);
  cursor: pointer;
}
.entries__delete:hover { border-color: var(--ember); }
.entries__delete svg {
  width: 1.02rem;
  height: 1.02rem;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  fill: none;
}
.entries__question { font-family: var(--display); font-size: 1.2rem; }
.entries__when { font-size: calc(0.7rem * var(--label-scale)); letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-faint); white-space: nowrap; }

.empty { color: var(--ink-faint); font-style: italic; font-family: var(--display); font-size: 1.2rem; text-align: center; }

/* After every `font:` shorthand, which resets variant. Uppercase in the
   element rules is the fallback when the face does not load. */
.masthead__nav,
.quota__plan,
.colophon__link,
.eyebrow,
.field > label,
.field__unmask,
.button,
.wait__status,
.reading__meta,
.mark__name,
.entry__card small,
.keywords li,
.note h4,
.reading-nav__title,
h2,
.entry__position,
.entries__when {
  font-variant-caps: var(--label-caps);
}

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