/* Taam: client.css. The client's path after the start screen: the invite, the deck, the result, the
   credits page; and the two pages a result arrives on in the studio (import, fallback).
   Builds on base.css (tokens, .tile, .btn, .fact, .card, .stamp ...). The start screen has its own file (start.css).

   CLASSES
   -------
   Shared    .cards (a still, fanned stack: one photo card on two blank ones)  .cards__card--1/--2/--3
             .cards__stamp  .cards__credit  .cards--dim
             .trio (the three answers as a picture: circle, words, arrow key)  .trio__item--like/--love/--dislike
             .trio__circle  .trio__key
             .glyph (a round mark that holds one icon)
   Invite    .invite  .invite__head  .invite__body  .invite__visual  .invite__tips  .invite__privacy
             .invite__cta (sticks to the bottom on a phone)  .invite__cta--pair  .invite__gone
   Deck      .deck  .deck__top  .deck__undo  .prog (progress)  .deck__band
             .deck__stage  .deck__stack  .deck__sheet--2/--3 (blank sheets under the top card)
             .deck__card (moved by the finger)  > .deck__card-in (canned animations)  .deck__stamp--like/--dislike/--love
             .coach (first-card overlay)  .deck__msg (done / offline / catalog states)  .deck__glyph  .deck__donebar
             .deck__creditline  .deck__credit  .credit-sheet
             .deck__actions  .act  .act--like/--love/--dislike  .act__circle  .act__label  .act__key
   Result    .result  .result__notice  .result__title  .result__hero  .result__styles  .result__board
             .result__mosaic  .result__rest  .result__lean  .result__traits  .result__next  .result__send
             .result__stale  .result__auto  .result__more  .result__opt  .result__credits  .result__bar
   Credits   .credits  .credits__sec  .credits__list  .credits__nums      (the text-only credits page)
   Import    .incoming  .incoming__preview  .incoming__styles  .incoming__strip  .incoming__dest
             .incoming__choose  .incoming__actions  .incoming__notes
   Fallback  .arrive  .arrive__text  .arrive__visual

   Gestures are PHYSICAL (ux.md 3.4): everything about the card, the stamps, the coach, the three answers and
   the action bar uses left / right / translateX on purpose. Everything else is logical, like the rest of the app. */

/* ---------- shared: the still stack, the three answers, the round mark ---------- */

/* One photo card resting on two blank ones, fanned as on the start screen. Not interactive. */
.cards { position: relative; flex: none; width: var(--card-w, 240px); height: calc(var(--card-w, 240px) * 1.25 + 40px); direction: ltr; }
.cards__card { position: absolute; inset: 0; transform-origin: 50% 90%; }
.cards__card > .card { height: 100%; }
.cards__card .card__photo { aspect-ratio: auto; flex: 1 1 auto; min-height: 0; }
.cards__card--1 { z-index: 3; transform: rotate(4deg); }
.cards__card--2 { z-index: 2; transform: translate(-6%, 1.5%) rotate(-4.5deg) scale(.96); }
.cards__card--3 { z-index: 1; transform: translate(6%, 2.5%) rotate(6deg) scale(.93); }
.cards__card--2 > .card { background: var(--stone); }
.cards__card--3 > .card { background: var(--stone-deep); }
.cards__stamp { position: absolute; z-index: 2; top: 7%; left: 6%; transform: rotate(-12deg); }
.cards--dim { opacity: .5; }
.cards .card__label { direction: rtl; }      /* the strip reads as on the deck: the room at the right, the number at the left */
.cards__credit { margin-block-start: var(--s-5); max-width: 100%; text-align: center; overflow-wrap: anywhere; }

.trio { direction: ltr; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); align-items: start; column-gap: var(--s-3); width: 100%; max-width: 420px; }
.trio__item { direction: rtl; display: grid; justify-items: center; gap: 10px; min-width: 0; text-align: center;
  font: 400 15px/1.3 var(--font); color: var(--ivory-2); text-wrap: balance; }
.trio__item--dislike { grid-column: 1; grid-row: 1; }
.trio__item--love { grid-column: 2; grid-row: 1; }
.trio__item--like { grid-column: 3; grid-row: 1; }
.trio__circle { display: grid; place-items: center; width: 68px; height: 68px; border-radius: 50%; box-shadow: var(--shadow-round);
  print-color-adjust: exact; -webkit-print-color-adjust: exact; }
.trio__circle .icon { width: 42%; height: 42%; }
.trio__circle .icon, .act__circle .icon { stroke-width: 2.25; }      /* the weight of the stamp and of the start screen's card */
.trio__item--dislike .trio__circle { background: var(--ivory); color: var(--ink); }
.trio__item--love .trio__circle { width: 58px; height: 58px; margin-block-start: 5px; background: var(--brass); color: var(--ink); }
.trio__item--like .trio__circle { background: var(--accent); color: var(--on-accent); }
.trio__key { display: grid; place-items: center; width: 30px; height: 30px; border-radius: var(--r-xs); background: var(--wash-dark); color: var(--ivory-2); }
.trio__key .icon { width: 14px; height: 14px; }
@media (min-width: 1100px) {
  .trio__item { font-size: 16px; }
  .trio__circle { width: 88px; height: 88px; }
  .trio__item--love .trio__circle { width: 74px; height: 74px; margin-block-start: 7px; }
  .trio__key { width: 34px; height: 34px; }
}

.glyph { display: grid; place-items: center; flex: none; width: 56px; height: 56px; border-radius: 50%; background: var(--stone); color: var(--accent-ink); }
.on-dark .glyph { width: 48px; height: 48px; background: var(--wash-dark); color: var(--accent-soft); }

/* ---------- invite (2.2) ---------- */

.invite { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--s-5); }
.invite__head, .invite__body { display: flex; flex-direction: column; gap: var(--s-4); min-width: 0; }
.invite__head { gap: var(--s-3); }
.invite__lead { color: var(--ink-2); max-width: 46ch; font-size: 19px; line-height: 1.45; text-wrap: pretty; }
.invite__visual {
  display: flex; flex-direction: column; align-items: center; gap: 20px;
  padding-block: var(--s-5) 20px;
  --card-w: 132px;
}
.invite__visual--gone { justify-content: center; padding-block: var(--s-7); }
.invite__tips { display: grid; gap: var(--s-2); }
.invite__tips li { display: flex; align-items: center; gap: var(--s-3); min-height: 52px; padding: 8px 16px;
  border-radius: var(--r-sm); background: var(--tile); font-size: 16px; line-height: 1.35; }
.invite__tips .icon { width: 18px; height: 18px; color: var(--accent-ink); }
.invite__privacy { max-width: 52ch; }
.invite__gone { display: flex; flex-direction: column; align-items: flex-start; gap: var(--s-3); }
.invite__gone p { color: var(--ink-2); max-width: 46ch; }
/* A short phone: the three answers say it all, the card steps aside. */
@media (max-width: 599.98px) and (max-height: 699.98px) { .invite__visual:not(.invite__visual--gone) .cards { display: none; } }
/* Phone: the body's parts join the page's own column, so the start button can stick from the first screen on. */
@media (max-width: 599.98px) { .invite__body { display: contents; } }

/* The start button stays in reach when the page is taller than the screen: a glass bar, as the result's. */
.invite__cta {
  position: sticky; bottom: 0; z-index: 2;
  display: flex; flex-direction: column; align-items: stretch; gap: var(--s-1);
  margin-inline: calc(-1 * var(--gutter)); padding: var(--s-3) var(--gutter) calc(var(--s-3) + env(safe-area-inset-bottom));
  background: var(--glass-bar); -webkit-backdrop-filter: var(--blur-bar); backdrop-filter: var(--blur-bar);
}
/* An invite that no longer opens: a short page, so its one button stands in the page and is no bar. */
.invite__cta--plain {
  position: static; margin-inline: 0; padding: 0;
  background: none; -webkit-backdrop-filter: none; backdrop-filter: none;
}
/* The round was already finished on this phone: "see the result" and "answer again" are equal in size. */
.invite__cta--pair { gap: var(--s-3); }
@media (min-width: 600px) {
  .invite__cta {
    position: static; flex-direction: row; flex-wrap: wrap; align-items: center; gap: var(--s-3) var(--s-4);
    margin-inline: 0; padding: 0; background: none; -webkit-backdrop-filter: none; backdrop-filter: none;
  }
  .invite__cta .btn--primary { min-width: 240px; }
  .invite__cta--pair .btn { flex: 0 1 240px; min-width: 0; }
  .invite__visual { --card-w: 200px; gap: var(--s-6); padding-block: var(--s-6) var(--s-5); }
}
@media (min-width: 1100px) {
  .invite {
    grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: var(--gap-tiles); row-gap: var(--s-5);
    grid-template-areas: "head visual" "body visual";
    /* The window less the bar and the column's own padding (--s-6 above, --s-7 below): the page fits, nothing scrolls. */
    min-height: calc(100vh - var(--hdr-h) - var(--s-6) - var(--s-7));
    min-height: calc(100dvh - var(--hdr-h) - var(--s-6) - var(--s-7));
  }
  .invite__head { grid-area: head; align-self: end; padding-inline-end: var(--s-7); }
  .invite__body { grid-area: body; align-self: start; padding-inline-end: var(--s-7); }
  .invite__visual {
    grid-area: visual; align-self: stretch; justify-content: center; gap: var(--s-7);
    --card-w: clamp(170px, calc((100vh - 560px) * .8), 300px);
  }
  .invite__lead { font-size: 21px; }
}

/* ---------- the deck (2.3, section 3) ---------- */

.deck {
  position: relative; isolation: isolate;
  flex: 1 1 auto; min-height: 0; height: 100%;
  display: flex; flex-direction: column;
  overflow: hidden; overscroll-behavior: none;
  background: var(--espresso); color: var(--ivory);
  /* Nothing on this screen scrolls: a drag that starts beside the card must not pan or refresh the page (iOS). */
  touch-action: none;
  -webkit-user-select: none; user-select: none;
}
/* Desk: the card, its credit and the three answers stand in one rounded panel, one tone up. Decoration only. */
@media (min-width: 1100px) {
  .deck::before {
    content: ""; position: absolute; z-index: -1; pointer-events: none;
    inset-block: calc(var(--hdr-h) + var(--s-2)) 0; left: 50%; width: min(720px, 100% - 48px); transform: translateX(-50%);
    border-radius: calc(var(--r-md) * 2) calc(var(--r-md) * 2) 0 0;
    background: var(--espresso-2); box-shadow: var(--glass-ring-soft);
  }
  /* In person: the name chip sits between the bar and the panel. */
  .deck:has(> .deck__band)::before { inset-block-start: calc(var(--hdr-h) + 28px + var(--s-2)); }
}

/* Heights here are minimums: at a large text size (6.1) the bar grows with its text instead of cutting it. */
.deck__top {
  flex: none; min-height: var(--hdr-h);
  display: flex; align-items: center; gap: var(--s-2);
  padding-inline: 6px;
  margin-block-start: env(safe-area-inset-top);
}
.deck__top .icon-btn { background: var(--wash-dark); }
@media (hover: hover) and (pointer: fine) { .deck__top .icon-btn:hover:not(:disabled) { background: var(--wash-dark-2); } }
.deck__undo {
  flex: none; display: inline-flex; align-items: center; gap: 6px;
  min-height: 44px; padding-inline: var(--s-2);
  border-radius: var(--pill); color: var(--ivory-2);
  font: 400 14px/20px var(--font); white-space: nowrap;
  transition: opacity var(--t-2) var(--ease-std), background .3s var(--ease);
}
@media (hover: hover) and (pointer: fine) { .deck__undo:hover:not(:disabled) { background: var(--wash-dark); } }
.deck__undo:disabled { opacity: .35; }
/* In rem, so a phone at a large text size gets the icon-only button too (360px at the default size). */
@media (max-width: 22.49rem) {
  .deck__undo { width: 44px; justify-content: center; padding: 0; }
  .deck__undo span { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
}

/* Progress: a rounded track that fills in reading order, from the right. */
.prog { flex: 1 1 auto; min-width: 0; min-height: 34px; display: flex; flex-direction: column; gap: 4px; padding-inline: var(--s-1); }
.prog__line { position: relative; display: block; height: 12px; }
.prog__line::before, .prog__fill { position: absolute; inset-inline-start: 0; top: 50%; height: 6px; margin-top: -3px; border-radius: var(--pill); }
.prog__line::before { content: ""; inset-inline-end: 0; background: var(--wash-dark-2); }
.prog__fill { background: var(--accent-soft); transition: width var(--t-3) var(--ease-out); }
.prog__phase {
  min-height: 18px; color: var(--ivory-2);
  font: 400 14px/18px var(--font);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  animation: fade-in var(--t-2) var(--ease-std) both;
}

/* In-person session: whose turn it is. A glass chip under the bar. */
.deck__band {
  flex: none; min-height: 28px; margin-block: 0; padding-block: 3px;
  background: var(--glass-chip); color: var(--ivory-2);
  font-size: 14px; line-height: 18px;
}

.deck__stage {
  flex: 1 1 auto; min-height: 0; position: relative;
  display: flex; align-items: center; justify-content: center;
  padding: 8px 16px;
}
/* --card-w is measured by the screen: min(100vw - 32px, 460px, (stage height - 64px) * 0.8). */
.deck__stack {
  position: relative; flex: none;
  width: var(--card-w, 280px);
  height: calc(var(--card-w, 280px) * 1.25 + 40px);      /* 4:5 photo + the 40px label strip */
}
.deck__sheet, .deck__card { position: absolute; inset: 0; transform-origin: 50% 100%; }

/* What lies under the top card is blank: the deck is adaptive, so the next photo is not known yet. */
.deck__sheet { z-index: 1; }
.deck__sheet--2 { background: var(--stone); transform: translate3d(0, 8px, 0) scale(.96); will-change: transform; }
.deck__sheet--3 { z-index: 0; background: var(--stone-deep); transform: translate3d(0, 16px, 0) scale(.92); }
.deck__sheet--2.is-promote { animation: deck-sheet-up 220ms var(--ease-out) backwards; }
.deck__sheet--3.is-promote { animation: fade-in 140ms var(--ease-std) backwards; }
.deck__sheet--2.is-drop { animation: deck-sheet-drop 260ms var(--ease-out) backwards; }
@keyframes deck-sheet-up { from { transform: translate3d(0, 16px, 0) scale(.92); } }
@keyframes deck-sheet-drop { from { transform: translate3d(0, 0, 0) scale(1); } }

.deck__card {
  z-index: 2;
  touch-action: none; -webkit-user-select: none; user-select: none; -webkit-touch-callout: none;
  will-change: transform; cursor: grab;
}
.deck__card:active { cursor: grabbing; }
.deck__card.is-out { z-index: 3; pointer-events: none; }
.deck__card-in { height: 100%; transform-origin: 50% 100%; }
.deck__card-in > .card { height: 100%; }
.deck__card .card__photo { aspect-ratio: auto; flex: 1 1 auto; min-height: 0; }
.deck__card img { pointer-events: none; }

/* A new top card grows out of the second position; --from-* continue where the drag left the sheet. */
.deck__card-in.is-promote { animation: deck-promote 220ms var(--ease-out) backwards; }
@keyframes deck-promote { from { transform: translate3d(0, var(--from-y, 8px), 0) scale(var(--from-s, .96)); } }
/* Undo: the card comes back from the side it left. */
.deck__card-in.is-undo-like { animation: deck-undo-like var(--t-4) var(--ease-out) backwards; }
.deck__card-in.is-undo-dislike { animation: deck-undo-dislike var(--t-4) var(--ease-out) backwards; }
.deck__card-in.is-undo-love { animation: deck-undo-love var(--t-4) var(--ease-out) backwards; }
@keyframes deck-undo-like { from { transform: translate3d(120vw, 0, 0) rotate(18deg); } }
@keyframes deck-undo-dislike { from { transform: translate3d(-120vw, 0, 0) rotate(-18deg); } }
@keyframes deck-undo-love { from { transform: translate3d(0, -110vh, 0); } }
/* Idle hint on the first card: one rock to the right, one to the left. */
.deck__card-in.is-rock { animation: deck-rock 900ms var(--ease-std); }
@keyframes deck-rock {
  0%, 100% { transform: none; }
  28% { transform: translate3d(14px, 0, 0) rotate(2deg); }
  68% { transform: translate3d(-14px, 0, 0) rotate(-2deg); }
}

/* Stamps (3.5). Opacity and scale are driven by the drag, straight on the element. */
.deck__stamp { position: absolute; z-index: 2; opacity: 0; }
.deck__stamp--like { top: 20px; left: 20px; transform: rotate(-12deg) scale(.9); }
.deck__stamp--dislike { top: 20px; right: 20px; transform: rotate(12deg) scale(.9); }
.deck__stamp--love { bottom: 64px; left: 50%; transform: translateX(-50%) rotate(-4deg) scale(.9); }

.deck__loading {
  position: absolute; inset: 0; z-index: 1;
  display: flex; align-items: center; justify-content: center;
  color: var(--ink-2); font: 400 14px/18px var(--font);
}

/* Coach overlay (3.12). It never takes the pointer: a drag that starts on it goes straight to the card. */
.coach {
  position: absolute; inset: 0; z-index: 3;
  direction: ltr; pointer-events: none;
  background: var(--scrim-text); color: var(--ivory);
  -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
  font: 600 16px/20px var(--font);
  animation: fade-in var(--t-2) var(--ease-std) both;
}
.coach__dir { position: absolute; display: flex; flex-direction: column; gap: 6px; max-width: 46%; }
.coach__dir span, .coach__foot { direction: rtl; }
.coach__dir--up { top: 16px; left: 0; right: 0; max-width: none; align-items: center; text-align: center; }
.coach__dir--right { right: 14px; top: 40%; align-items: flex-end; text-align: right; }
.coach__dir--left { left: 14px; top: 40%; align-items: flex-start; text-align: left; }
.coach__foot {
  position: absolute; left: 0; right: 0; bottom: 16px; padding-inline: var(--s-4);
  display: flex; flex-direction: column; align-items: center; gap: var(--s-3); text-align: center;
  font-weight: 400; color: var(--ivory-2);
}
.coach__cta { pointer-events: auto; min-width: 120px; }

/* Done, offline and catalog-error states take the place of the stack. */
.deck__msg {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: min(420px, calc(100% - 48px));
  display: flex; flex-direction: column; align-items: center; gap: var(--s-3); text-align: center;
}
.deck__msg p { color: var(--ivory-2); }
.deck__msg .btn { margin-block-start: var(--s-2); }
.deck__glyph { display: grid; place-items: center; width: 56px; height: 56px; border-radius: 50%; background: var(--wash-dark); color: var(--accent-soft); }
/* The end of the deck: the progress track once more, filling from empty to full while the result is put together. */
.deck__donebar { align-self: stretch; margin-block: var(--s-1); }
.deck__donebar .prog__fill { width: 100%; transform-origin: 100% 50%; animation: deck-done-fill 700ms var(--ease-out) both; }
@keyframes deck-done-fill { from { transform: scaleX(0); } }

/* Credit line: under the card and outside it, so it is never part of the swipe. Plain text; the
   info mark beside it opens the linked credit. One row of fixed height: the layout does not jump
   between cards, and a long credit is cut with an ellipsis (the dialog has it in full). */
.deck__creditline {
  flex: none; height: 36px;
  display: flex; align-items: center; justify-content: center; gap: 2px;
  width: 100%; max-width: 492px; margin-inline: auto; padding-inline: 16px;
}
.deck__credit { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font: 400 13px/20px var(--font); color: var(--ivory-3); }
.deck__creditline .icon-btn { flex: none; width: 36px; height: 36px; color: var(--ivory-3); }
.deck__creditline .icon-btn .icon { width: 18px; height: 18px; }
.deck__creditline .icon-btn { position: relative; }
.deck__creditline .icon-btn::after { content: ""; position: absolute; inset: -4px; }      /* a 44px target around the 36px mark */
.credit-sheet { display: flex; flex-direction: column; gap: var(--s-3); }
.credit-sheet__line { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2) var(--s-3); }
.credit-sheet .link { overflow-wrap: anywhere; }
@media (max-height: 480px) { .deck__creditline { height: 28px; } .deck__creditline .icon-btn { width: 28px; height: 28px; } }

/* Action bar (ux.md 3.4, 3.6). An LTR grid with explicit columns: dislike left, love centre, like right,
   while the DOM order stays like, love, dislike so focus moves in reading order. */
.deck__actions {
  flex: none; min-height: calc(124px + env(safe-area-inset-bottom)); padding: 10px 0 env(safe-area-inset-bottom);
  direction: ltr;
  display: grid; grid-template-columns: repeat(3, 72px); column-gap: 20px;      /* 28px between the 64px circles */
  justify-content: center; align-items: start;
}
.act {
  direction: rtl; grid-row: 1;
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  min-height: 88px; padding: 0; border-radius: var(--r-sm);
  color: var(--ivory-2);
  transition: opacity var(--t-2) var(--ease-std);
}
.act--dislike { grid-column: 1; }
.act--love { grid-column: 2; padding-top: 4px; }
.act--like { grid-column: 3; }
.act__circle {
  width: 64px; height: 64px; flex: none; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  box-shadow: var(--shadow-round);
  transition: transform .2s var(--ease), box-shadow .3s var(--ease);
}
.act--dislike .act__circle { background: var(--ivory); color: var(--ink); }
.act--dislike .icon { width: 26px; height: 26px; }
.act--love .act__circle { width: 56px; height: 56px; background: var(--brass); color: var(--ink); }
.act--love .icon { width: 24px; height: 24px; }
.act--like .act__circle { background: var(--accent); color: var(--on-accent); }
.act--like .icon { width: 28px; height: 28px; }
/* The label wraps inside its 72px column rather than run into its neighbours at a large text size. */
.act__label {
  display: inline-flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 2px 6px;
  max-width: 100%; text-align: center;
  font: 400 14px/18px var(--font);
}
.act__key {
  display: inline-flex; align-items: center; justify-content: center; gap: 3px; padding: 0 6px; min-width: 22px; min-height: 22px;
  border-radius: var(--r-xs); background: var(--wash-dark); color: var(--ivory-2);
  font-size: 12px; line-height: 1;
}
.act__key .icon { width: 12px; height: 12px; }
/* Desk: the keycap goes under the label, so three labels still fit under circles 28px apart. */
@media (min-width: 1100px) { .act__label { flex-direction: column; gap: 3px; } .act { gap: 4px; } }
@media (hover: hover) and (pointer: fine) { .act:hover:not(:disabled) .act__circle { transform: scale(1.06); } }
.act:active:not(:disabled) .act__circle { transform: scale(.94); }
.act:disabled { opacity: .4; }
.act:focus-visible { outline: none; }
.act:focus-visible .act__circle { outline: 2px solid var(--ivory); outline-offset: 3px; }

/* While a card is dragged (3.6): the button of that direction is armed past the threshold, the others step back. */
.deck__actions[data-dir="like"] .act:not(.act--like),
.deck__actions[data-dir="love"] .act:not(.act--love),
.deck__actions[data-dir="dislike"] .act:not(.act--dislike) { opacity: .5; }
.deck__actions[data-armed="like"] .act--like .act__circle,
.deck__actions[data-armed="love"] .act--love .act__circle,
.deck__actions[data-armed="dislike"] .act--dislike .act__circle { transform: scale(1.08); box-shadow: 0 0 0 4px var(--wash-dark-2), var(--shadow-round); }

@media (max-width: 359.98px) {
  .deck__actions { grid-template-columns: repeat(3, 64px); column-gap: 12px; }      /* 20px between the circles */
  .act__circle { width: 56px; height: 56px; }
  .act--love .act__circle { width: 48px; height: 48px; }
}
/* Landscape phone: smaller circles, labels kept for screen readers only; the zones stay as they are. */
@media (max-height: 480px) {
  .act__circle, .act--love .act__circle { width: 48px; height: 48px; }
  .act--love { padding-top: 0; }
  .act { min-height: 48px; }
  .act__label { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
  .deck__actions { align-items: center; }
}

/* Reduced motion (3.3): nothing flies or springs; a leaving card fades while the next one fades in. */
@media (prefers-reduced-motion: reduce) {
  .deck .deck__card-in.is-promote,
  .deck .deck__card-in[class*="is-undo-"] { animation-name: fade-in; animation-duration: 120ms !important; }
  .deck .deck__sheet.is-promote, .deck .deck__sheet.is-drop, .deck .deck__card-in.is-rock { animation: none; }
  .deck__donebar .prog__fill { animation: none; }
  .act__circle { transition: none; }
}

/* ---------- the result (2.4): the start screen's "what you get", for real ---------- */

.result { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--gap-tiles); align-items: start; }
.result > * { min-width: 0; }
.result__notice { align-items: center; justify-content: space-between; margin-block-end: var(--gap-tiles); }
.result__notice p { flex: 1 1 280px; max-width: 62ch; }

.result__title { display: flex; flex-direction: column; gap: var(--s-6); }
.result__hero { display: flex; flex-direction: column; gap: var(--s-2); }
.result__hero .t-display { margin-block-start: 2px; }
.result__sum { color: var(--ink-2); max-width: 52ch; font-size: 19px; line-height: 1.45; text-wrap: pretty; }
.result__styles { display: flex; flex-direction: column; gap: var(--s-4); }
.result__styles ul { display: grid; gap: 18px; }

.result__board, .result__lean, .result__rest { display: flex; flex-direction: column; gap: var(--s-4); }
.result__boardwrap { display: flex; flex-direction: column; gap: var(--s-4); flex: 1 1 auto; min-height: 0; }
.result__sub { color: var(--ink-2); }
/* The moodboard: six columns, the first loved photo large, the rest three across; a short last row closes
   the block. Tiles crop to their cell; the lightbox shows each photograph whole. */
.result__mosaic { grid-template-columns: repeat(6, minmax(0, 1fr)); }
.result__mosaic .ptile { grid-column: span 2; display: flex; flex-direction: column; }
.result__mosaic .ptile__frame { flex: 1 1 auto; min-height: 0; }
.result__mosaic .ptile--hero { grid-column: span 4; grid-row: span 2; }
.result__mosaic .photo { position: absolute; inset: 0; height: 100%; aspect-ratio: auto; }
.result__mosaic .ptile:nth-child(3n+1):nth-last-child(2), .result__mosaic .ptile:nth-child(3n+2):last-child { grid-column: span 3; }
.result__mosaic .ptile:nth-child(3n+1):last-child { grid-column: span 6; }
/* Below the desk the rows take their height from the tiles; on the desk the tile's height sets the rows (see there). */
@media (max-width: 1099.98px) {
  .result__mosaic .ptile__frame { aspect-ratio: 10 / 11; }
  .result__mosaic .ptile--hero .ptile__frame { aspect-ratio: auto; }
  .result__mosaic .ptile:nth-child(3n+1):nth-last-child(2) .ptile__frame, .result__mosaic .ptile:nth-child(3n+2):last-child .ptile__frame { aspect-ratio: 3 / 2; }
  .result__mosaic .ptile:nth-child(3n+1):last-child .ptile__frame { aspect-ratio: 5 / 2; }
}
.result__rest .pgrid { --cols: 3; }

.result__traits { display: grid; gap: var(--s-2); }
.result__traits li { position: relative; padding-inline-start: 18px; }
.result__traits li::before { content: ""; position: absolute; inset-inline-start: 2px; top: .62em; width: 6px; height: 6px; border-radius: 50%; background: var(--accent); }

/* What now: the one dark tile of the page. The mark at the top, the words and the action under it. */
.result__next { --pad-tile: 22px; display: flex; flex-direction: column; justify-content: flex-start; gap: var(--s-5); }
.result__send, .result__say, .result__acts { display: flex; flex-direction: column; gap: var(--s-3); min-width: 0; }
/* The words follow the mark; when the tile is taller than its words, the actions close it at the bottom. */
.result__send { flex: 1 1 auto; }
.result__acts { margin-block-start: auto; }
.result__send p { color: var(--ivory-2); }
.result__send .btn { padding-inline: 16px; }
.result__send .field__error { color: var(--accent-soft); }
.result__stale { padding: var(--s-2) var(--s-3); border-radius: var(--r-sm); background: var(--wash-dark); }
.result__send .result__stale, .result__send .result__auto, .result__send .result__now { color: var(--ivory); }
.result__auto { display: flex; align-items: flex-start; gap: var(--s-2); font-weight: 600; }
.result__auto .icon { flex: none; margin-block-start: 2px; color: var(--accent-soft); }

/* Refine, another round, start over: quiet choices on stone. */
.result__more { display: grid; gap: var(--s-3); }
/* Nothing to offer but "start over": a quiet line, level with the credits under it, and no tile around it. */
.result__more--bare { padding-inline: var(--pad-tile); }
.result__opts { display: grid; gap: var(--s-3); }
.result__opt { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-3) var(--s-5); }
.result__opt > div { flex: 1 1 240px; display: grid; gap: 4px; min-width: 0; }
/* The words, then their button beside them: on a wide row the button does not drift to the far edge. */
@media (min-width: 600px) { .result__opt > div { flex: 0 1 52ch; } }
.result__opt p { color: var(--ink-2); max-width: 52ch; }
/* The option is a plaster tile inside the stone one: its secondary button takes the fill for a light ground. */
.result__more .result__opt .btn--secondary { background: var(--wash); }
@media (hover: hover) and (pointer: fine) { .result__more .result__opt .btn--secondary:hover { background: var(--wash-2); } }

/* The credits of the photos shown: a stone tile that opens its list on request. */
.result__credits { display: flex; flex-direction: column; align-items: flex-start; gap: var(--s-3); }
.result__credits .disclosure { align-self: stretch; }
.result__credits .rp-credits__list { display: block; columns: 1; column-gap: var(--s-5); margin-block-start: var(--s-3); }
.result__credits .rp-credits__list li { display: flex; break-inside: avoid; }
@media (min-width: 600px) { .result__credits .rp-credits__list { columns: 2; } }
/* Phone: a photographer's name under a small board tile takes the lines it needs (two as a rule); it is not cut. */
@media (max-width: 599.98px) {
  .result__mosaic .ptile:not(.ptile--hero) .ptile__credit { white-space: normal; overflow-wrap: anywhere; min-height: 32px; }
}

/* Phone: the primary action stays in reach until the send tile itself is on screen. */
.result__bar {
  position: fixed; z-index: 5; inset-inline: 0; bottom: 0;
  padding: var(--s-3) max(var(--gutter), env(safe-area-inset-right, 0px)) calc(var(--s-3) + env(safe-area-inset-bottom, 0px)) max(var(--gutter), env(safe-area-inset-left, 0px));
  background: var(--glass-bar); -webkit-backdrop-filter: var(--blur-bar); backdrop-filter: var(--blur-bar);
}
.result__bar .result__auto { min-height: var(--btn-h); align-items: center; }
.result__bar .result__auto .icon { margin-block-start: 0; color: var(--accent-ink); }
/* "Not sent yet", above the button that sends it again: a line of its own, not a button's height. */
.result__bar .result__auto--wait { min-height: 0; margin-block-end: var(--s-2); font-weight: 400; }
@media (max-width: 599.98px) { .result { padding-block-end: calc(var(--btn-h) + var(--s-8)); } }

@media (min-width: 600px) {
  .result { grid-template-columns: repeat(12, minmax(0, 1fr)); }
  .result > * { grid-column: 1 / -1; }
  .result__lean { grid-column: 1 / span 7; }
  .result__next { grid-column: 8 / span 5; align-self: stretch; }
  .result__rest .pgrid { --cols: 6; }
  .result__opts { grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
}
@media (min-width: 1100px) {
  .result { align-items: stretch; grid-template-rows: auto 1fr; }      /* what the board adds in height goes to the second row */
  .result__title { grid-column: 1 / span 7; }
  .result__board { grid-column: 8 / span 5; grid-row: span 2; }
  .result__lean { grid-column: 1 / span 4; }
  .result__next { grid-column: 5 / span 3; }
  /* The board takes the height the two rows beside it give: its rows stretch, the photos fill them. */
  .result__mosaic { flex: 1 1 auto; grid-auto-rows: minmax(116px, 1fr); }
  .result__rest .pgrid { --cols: var(--rest-n, 8); }      /* a handful of photos still fills the row */
  .result__sum { font-size: 21px; }
  /* A short title tile (no style rows), or a result that sends itself: the left column is three tiles one
     under the other and the board stands beside all three. The send tile is a row: the mark, the words,
     and the actions (when there are any) at the end. */
  .result--stacked { grid-template-rows: auto auto 1fr; }
  .result--stacked .result__board { grid-row: span 3; }
  .result--stacked .result__lean, .result--stacked .result__next { grid-column: 1 / span 7; }
  .result--stacked .result__next { flex-direction: row; align-items: flex-start; justify-content: flex-start; align-self: start; }
  .result--stacked .result__send { flex: 1 1 0; flex-direction: row; align-items: center; gap: var(--s-6); }
  .result--stacked .result__say { flex: 1 1 0; }
  .result--stacked .result__acts { flex: 0 0 300px; margin-block-start: 0; }
  /* The page closes with two tiles side by side: the further choices in seven columns, the credits in five. */
  .result > .result__more { grid-column: 1 / span 7; }
  .result > .result__credits { grid-column: 8 / span 5; }
  .result__opts { grid-template-columns: minmax(0, 1fr); }
}

/* ---------- credits page (#/credits): text only ---------- */

.credits { display: grid; gap: var(--gap-tiles); }
.credits__head { margin-block-end: 0; }
.credits__lead { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--s-3) var(--s-5); }
.credits__lead p { flex: 1 1 280px; max-width: 62ch; }
.credits__sec { display: flex; flex-direction: column; gap: var(--s-4); }
.credits__list { columns: 1; column-gap: var(--gap-tiles); }
.credits__list li { break-inside: avoid; padding-block: 5px; overflow-wrap: anywhere; font-size: 16px; line-height: 1.45; }
.credits__nums { color: var(--ink-3); font-size: 14px; }
.credits__nums a { color: var(--accent-ink); text-decoration: none; display: inline-block; padding: 2px 0; }
.credits__nums a:hover { text-decoration: underline; }
@media (pointer: coarse) {
  .credits__nums a { padding: 12px 8px; min-width: 44px; text-align: center; }
  .credit-sheet .link { display: inline-flex; align-items: center; min-height: 44px; }
}
@media (min-width: 600px) { .credits__list { columns: 2; } }
@media (min-width: 1100px) { .credits__list { columns: 4; } }

/* ---------- a result opened in the studio (#/r/...) ---------- */

.incoming { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--gap-tiles); align-items: start; }
.incoming > * { min-width: 0; }
.incoming__preview { display: flex; flex-direction: column; gap: var(--s-5); }
.incoming__head { display: flex; flex-direction: column; gap: var(--s-2); }
.incoming__styles { display: grid; gap: 18px; }
.incoming__strip { --cols: 3; }
.incoming__photos { display: flex; flex-direction: column; gap: var(--s-4); }
/* The decision opens the page: who and where, what will happen, and the buttons. */
.incoming__dest { display: flex; flex-direction: column; gap: var(--s-5); }
.incoming__what { display: flex; flex-direction: column; gap: var(--s-4); min-width: 0; }
.incoming__what > p, .incoming__say p { text-wrap: pretty; max-width: 70ch; }
.incoming__say { display: flex; flex-direction: column; gap: var(--s-2); }
.incoming__choose { display: grid; gap: var(--s-3); }
.incoming__actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-3); }
.incoming__notes { display: grid; gap: var(--s-1); }
@media (max-width: 599.98px) { .incoming__actions { flex-direction: column; align-items: stretch; } }
/* Below the desk the page reads top to bottom: whose result it is, then where it goes, then the photos. */
@media (max-width: 1099.98px) {
  .incoming > .incoming__preview { order: 1; }
  .incoming > .incoming__dest { order: 2; }
  .incoming > .incoming__photos { order: 3; }
  .incoming > .incoming__notes { order: 4; }
}
@media (min-width: 600px) { .incoming__strip { --cols: 6; } }
@media (min-width: 1100px) {
  .incoming { grid-template-columns: repeat(12, minmax(0, 1fr)); }
  .incoming > * { grid-column: 1 / -1; }
  .incoming { align-items: stretch; }
  /* The destination runs the width: its words at the start, its buttons at the end. Under it the persona
     with the styles in seven columns and the photos in five; the two end level. */
  .incoming__dest { flex-direction: row; align-items: flex-end; justify-content: space-between; gap: var(--s-5) var(--s-7); }
  .incoming__what { flex: 1 1 0; }
  .incoming__dest .incoming__actions { flex: none; }
  .incoming__dest .sum__stats { flex-wrap: wrap; }
  .incoming__dest .stat { flex: 0 1 auto; min-width: 128px; }
  .incoming__choose { grid-template-columns: repeat(2, minmax(0, 360px)); }
  .incoming__what .field { max-width: 420px; }      /* a select is as wide as a name, not as the tile */
  .incoming__preview { grid-column: 1 / span 7; }
  .incoming__preview--alone { grid-column: 1 / -1; }
  .incoming__photos { grid-column: 8 / span 5; }
  .incoming__strip { --cols: 3; }
}

/* ---------- a sealed result arriving (#/f/...) ---------- */

/* One thing to say and one way on, in the invite page's frame: the dark tile with the still cards and the
   wordmark (above the words on a phone, beside them on a desk), and the words on the ground, not in a
   tile, so no box is ever larger than what it says. */
.arrive { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--s-5); }
.arrive__text { display: flex; flex-direction: column; align-items: flex-start; justify-content: center; gap: var(--s-3); min-width: 0; }
.arrive__text p { color: var(--ink-2); max-width: 46ch; font-size: 19px; line-height: 1.45; text-wrap: pretty; }
.arrive__actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-3) var(--s-4); margin-block-start: var(--s-3); }
.arrive__visual {
  order: -1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--s-4);
  padding-block: var(--s-6) var(--s-5); --card-w: 132px;
}
.arrive__visual .wordmark { color: var(--ivory); }
.arrive__visual .cards__credit { margin-block-start: 0; }
@media (max-width: 599.98px) {
  .arrive__actions { flex-direction: column; align-items: stretch; align-self: stretch; }
  .arrive__visual { min-height: 44vh; min-height: 44dvh; }
}
@media (min-width: 600px) { .arrive__visual { --card-w: 180px; gap: var(--s-5); padding-block: var(--s-7); } }
.fact__dot { width: 8px; height: 8px; border-radius: 50%; background: var(--ink-3); }
.fact__dot--busy { animation: arrive-pulse 1.2s var(--ease) infinite alternate; }
.fact__dot--danger { background: var(--danger); }
@keyframes arrive-pulse { from { opacity: .35; } }
@media (prefers-reduced-motion: reduce) { .fact__dot--busy { animation: none; } }
@media (min-width: 1100px) {
  .arrive {
    grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: var(--gap-tiles);
    min-height: max(520px, calc(100vh - var(--hdr-h) - var(--s-6) - var(--s-8)));
    min-height: max(520px, calc(100dvh - var(--hdr-h) - var(--s-6) - var(--s-8)));
  }
  .arrive__text { padding-inline-end: var(--s-7); gap: var(--s-4); }
  .arrive__text p { font-size: 21px; }
  .arrive__visual { order: 0; gap: var(--s-6); --card-w: clamp(170px, calc((100vh - 560px) * .8), 280px); }
}
