/* PsycheAI — a warm, readable, single-column app shell. */

:root {
  --bg: #faf7fb;
  --bg-alt: #ffffff;
  --ink: #241a2e;
  --ink-soft: #6b6076;
  --line: #e7dfec;
  --accent: #7b3fa0;
  --accent-2: #d1477a;
  --accent-soft: #f3e9f8;
  --good: #2f7d5b;
  --good-soft: #e8f6ef;
  --warn: #9a5b12;
  --warn-soft: #fdf1e2;
  --danger: #b3261e;
  --radius: 16px;
  --shadow: 0 1px 2px rgba(36, 26, 46, .06), 0 8px 28px rgba(36, 26, 46, .07);
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Roboto, "Helvetica Neue", Arial, sans-serif;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #16121b;
    --bg-alt: #1f1926;
    --ink: #f1ebf5;
    --ink-soft: #a99fb4;
    --line: #322a3c;
    --accent: #c79bec;
    --accent-2: #f08cb0;
    --accent-soft: #2a2033;
    --good: #7fd6ab;
    --good-soft: #1c2b25;
    --warn: #e8b475;
    --warn-soft: #2e2419;
    --danger: #f2988f;
    --shadow: 0 1px 2px rgba(0, 0, 0, .4), 0 8px 28px rgba(0, 0, 0, .35);
  }
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

/* Nothing behind a dialog moves while it is open.
 *
 * `showModal()` makes the rest of the page inert — unclickable, untabbable —
 * but it does not stop it *scrolling*. A wheel or a swipe anywhere outside the
 * dialog still ran the page underneath, so a reader reading a long popout
 * could look up and find the page behind them somewhere else entirely, and on
 * a phone the two scroll areas fought each other for every gesture.
 *
 * Written as `:has()` on the body rather than a class toggled from JS, so it
 * covers all eleven dialogs at once and covers the twelfth automatically. The
 * selector also catches the `setAttribute('open')` fallback path some of them
 * use where `showModal` is missing, which a JS toggle would have had to
 * remember separately.
 *
 * `scrollbar-gutter` is the other half. Hiding overflow removes the
 * scrollbar, and where that scrollbar takes up layout width — desktop, classic
 * scrollbars — the whole page shifts right by 15px the instant a dialog opens,
 * a visible lurch behind the thing that just appeared. Reserving the gutter up
 * front keeps the page still. It goes on `html`, not `body`, because that is
 * the element actually scrolling.
 *
 * Scoped to wide viewports, and that is not cosmetic. Phones draw scrollbars as
 * an overlay, so there is no width to lose and no lurch to prevent — but the
 * gutter is reserved regardless, and 15px out of a 320px screen is 5% of the
 * reader's display given up for nothing. The suite caught this directly: five
 * `does not scroll sideways` checks went to -15px the moment it was applied
 * unscoped. 560px is the app's own narrowest breakpoint, so this changes
 * nothing about where the layout already decides it is on a small screen.
 *
 * Known limit: iOS Safari does not always honour `overflow: hidden` on the
 * body for touch scrolling. The dialogs' own `overscroll-behavior: contain`
 * catches the common case — a swipe that runs past the end of the dialog's own
 * scroll — and the bulletproof fix (position: fixed on the body) loses the
 * reader's scroll position on open, which is worse than the problem. */
body:has(dialog[open]) { overflow: hidden; }
@media (min-width: 560px) {
  html { scrollbar-gutter: stable; }
}

h1, h2, h3 { line-height: 1.25; margin: 0 0 .5em; font-weight: 650; letter-spacing: -.015em; }
h1 { font-size: clamp(1.7rem, 4vw, 2.6rem); }
h2 { font-size: 1.28rem; }
h3 { font-size: 1.02rem; margin-top: 1.4em; }
p { margin: 0 0 1em; }
a { color: var(--accent); text-decoration-thickness: 1px; text-underline-offset: 2px; }
hr { border: 0; border-top: 1px solid var(--line); margin: 1.5rem 0; }

.muted { color: var(--ink-soft); }
.fineprint { color: var(--ink-soft); font-size: .84rem; line-height: 1.5; }
.centre { text-align: center; }
.accent { background: linear-gradient(100deg, var(--accent), var(--accent-2)); -webkit-background-clip: text; background-clip: text; color: transparent; }

/* ---------- layout ---------- */

.nav {
  position: sticky; top: 0; z-index: 20;
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding: .7rem clamp(1rem, 4vw, 2rem);
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--line);
}
.brand { display: inline-flex; align-items: center; gap: .45rem; font-weight: 700; text-decoration: none; color: var(--ink); font-size: 1.05rem; }
/* The mark inherits the accent through currentColor, so it follows the theme
   rather than needing a second copy for dark mode. */
.brand-mark { width: 1.5em; height: 1.5em; flex: none; color: var(--accent); }
.nav-links { display: flex; gap: clamp(.6rem, 2vw, 1.4rem); flex-wrap: wrap; }
.nav-links a { color: var(--ink-soft); text-decoration: none; font-size: .92rem; white-space: nowrap; }
.nav-links a:hover { color: var(--accent); }

/* Keep the three links on one row beside the wordmark on a phone. At .92rem
   they wrap below about 410px, which is most of them. The steps down are
   measured against 412 / 390 / 375 / 360 / 320px viewports rather than
   guessed, and the measurement has been redone twice as the labels changed.

   The longer "My Personality" and "My Compatibility" overflowed by 14px at
   375 and 32px at 320, and shrinking the links to absorb that would have put
   them under the 11px the suite holds them to — so the wordmark came off on
   every phone. Shortening "How it works" to "FAQ" gave back more than it
   cost: re-measured at all five widths the nav now sits on one row with no
   horizontal scroll and nothing below 11.5px, so the wordmark is back. */
@media (max-width: 430px) {
  .nav { padding: .6rem 1rem; gap: .6rem; }
  .nav-links { gap: .7rem; flex-wrap: nowrap; }
  .nav-links a { font-size: .78rem; }
  .brand { font-size: .98rem; gap: .35rem; }
  .brand-mark { width: 1.35em; height: 1.35em; }
}
@media (max-width: 360px) {
  .nav { padding: .55rem .7rem; }
  .nav-links { gap: .55rem; }
  .nav-links a { font-size: .72rem; }
  .brand { font-size: .9rem; }
  .brand-mark { width: 1.25em; height: 1.25em; }
}
/* Narrower than any ordinary phone — a folded Galaxy, say. Shrinking the
   links further would be unreadable, so the wordmark drops and the brain
   mark carries the brand on its own. */
@media (max-width: 319px) {
  .brand span { display: none; }
}

.container { max-width: 820px; margin: 0 auto; padding: clamp(1.2rem, 4vw, 2.5rem) clamp(1rem, 4vw, 2rem) 4rem; }
.view[hidden] { display: none; }
.footer { border-top: 1px solid var(--line); padding: 1.5rem; text-align: center; color: var(--ink-soft); font-size: .85rem; }
/* Small and quiet: it is for whoever is reporting a problem, not for every reader. */
.footer-version { margin: .35rem 0 0; font-size: .75rem; opacity: .8; }
.footer-version a { color: inherit; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }

.page-head { margin-bottom: 1.5rem; }
/* The hero sits on its own warmth rather than the page's flat --bg: two soft
   accent washes, one under the mark and one behind the headline. They are
   painted on a pseudo-element that bleeds past the container's own padding, so
   the colour reaches the edges of the screen while the text stays in column.
   Sized in percentages so the shape holds at any width. */
/* The hero sits on its own warmth rather than the page's flat --bg: two soft
   accent washes, one behind the headline and one under the mark.
   The negative margins pull it out to the container's padding edges and up to
   meet the nav, so it reads as a band rather than a floating card — and
   because they move the box rather than spilling content out of it, they add
   no width to the page.
   `overflow: clip` is what lets the mark bleed off the right without doing
   that either: it was escaping by 24px at phone widths and taking the whole
   document sideways with it. Clip rather than hidden so no scroll container is
   created; the wash is painted here rather than on a bleeding pseudo-element
   because a pseudo-element would be clipped by this same rule. Sized off the
   container rather than 100vw throughout — vw counts the scrollbar. */
.hero {
  position: relative; isolation: isolate; overflow: clip;
  margin: calc(-1 * clamp(1.2rem, 4vw, 2.5rem)) calc(-1 * clamp(1rem, 4vw, 2rem)) 2rem;
  padding: clamp(1.8rem, 5vw, 3rem) clamp(1rem, 4vw, 2rem) clamp(1.1rem, 2.2vw, 1.6rem);
  border-radius: 0 0 28px 28px;
  background:
    radial-gradient(66% 130% at 84% 34%, color-mix(in srgb, var(--accent-2) 18%, transparent), transparent 70%),
    radial-gradient(66% 120% at 10% 18%, color-mix(in srgb, var(--accent) 17%, transparent), transparent 68%);
}

.hero-top { margin-bottom: 0; }
.hero-top h1 { margin-bottom: .6rem; }

/* Backdrop, not an object in the layout: large, faint, bled off the right so
   it reads as texture behind the words rather than a logo sitting beside them.
   pointer-events off because it now covers the buttons underneath it.

   Shared with `.profile-hero-mark` below for the properties that do not
   depend on how big the band is — position, fade, colour. A separate class
   from `.hero-mark` rather than a second copy of it, so the "one shared
   mark" check that counts every `.hero-mark` node in the document is not
   thrown off by a fifth instance in it. */
.hero-mark, .profile-hero-mark {
  position: absolute; z-index: -1; pointer-events: none;
  top: 50%; transform: translateY(-50%); height: auto;
  opacity: .14; color: var(--accent);
}
.hero-mark {
  right: clamp(-5rem, -4vw, -1.5rem);
  width: clamp(230px, 34vw, 420px);
  stroke: url(#hero-mark-gradient) currentColor;
}
.hero-mark circle { fill: url(#hero-mark-gradient) currentColor; }
/* With the video on the hero's right, the mark moves to a corner: the top
   right of the header on a phone, the top left beside the headline from 720px
   up, mostly off the edge and fainter there so the headline reads over it. */
@media (max-width: 719px) {
  #view-welcome .hero-mark { top: -3.6rem; right: -3.4rem; transform: none; width: 165px; opacity: .1; }
}
@media (min-width: 720px) {
  #view-welcome .hero-mark { top: -6.6rem; left: -5.6rem; right: auto; transform: none; width: 260px; opacity: .09; }
}
/* Stops set from CSS rather than as SVG attributes so they follow the theme:
   both ends are the same pair the headline's own gradient uses. Reused by
   the profile page's gradient defs too, since a CSS class applies wherever
   it is written regardless of which <linearGradient> it lives inside. */
.hero-mark-from { stop-color: var(--accent); }
.hero-mark-to { stop-color: var(--accent-2); }

/* Scaled down from the welcome hero's mark to match the shorter band it sits
   in — full size here would loom over one line of text and a button the way
   it never does over a headline, a lede and two buttons. */
.profile-hero-mark {
  right: clamp(-3.5rem, -3vw, -.8rem);
  width: clamp(150px, 24vw, 260px);
  stroke: url(#profile-hero-mark-gradient) currentColor;
}
.profile-hero-mark circle { fill: url(#profile-hero-mark-gradient) currentColor; }

/* The profile page's own top band. `.hero` supplies the bleed, the rounded
   foot and the gradient wash; this only overrides what has to differ because
   there is one line of text and one button here rather than a headline, a
   lede and two — reusing `.hero`'s own padding would leave a band far taller
   than its content needs, which reads as an empty page rather than a full
   one. */
.profile-hero { padding: clamp(1.3rem, 3.5vw, 1.9rem) clamp(1rem, 4vw, 2rem) clamp(1.2rem, 2.5vw, 1.6rem); }
/* Nothing follows the title in this header any more — the download button moved
   to the foot of the page — so the margin under it and the hero's own bottom
   margin were holding open an empty row between the title and the first card.
   Same fix, and same reason, as the welcome hero's button row a few lines down. */
.profile-hero h1 { margin: 0; }
.profile-hero { margin-bottom: 1.1rem; }

.hero .lede { margin: 0 0 1.1rem; max-width: 46ch; }

/* The promo video. 9:16, so it is sized by its width and never stretched: on
   a phone it sits centred under the buttons, as wide as fits without taking
   more than about three quarters of the screen's height; from a tablet up it
   moves beside the headline at a fixed phone-like size. aspect-ratio holds
   its space before anything loads, so the page never jumps when it does. */
.hero-video {
  position: relative; margin: 1.4rem auto 0;
  width: min(72vw, 290px, calc(74svh * 9 / 16)); aspect-ratio: 9 / 16;
  border-radius: 22px; overflow: hidden; background: #f7effd;
  box-shadow: 0 18px 44px color-mix(in srgb, var(--accent) 22%, transparent),
    0 0 0 1px color-mix(in srgb, var(--accent) 14%, transparent);
}
.hero-video video { display: block; width: 100%; height: 100%; object-fit: cover; cursor: pointer; }
/* "Tap for sound": the big invitation, just above the player's bar, while it is silent. */
.hero-video-sound {
  position: absolute; left: 50%; bottom: 3.2rem; transform: translateX(-50%);
  display: inline-flex; align-items: center; gap: .35rem; white-space: nowrap;
  padding: .42rem .85rem; border: 0; border-radius: 999px; cursor: pointer;
  background: rgba(32, 12, 46, .72); color: #fff; font: 600 .8rem/1.2 var(--font);
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
}
.hero-video-sound[hidden] { display: none; }
/* The player's bar: one row of controls with the timing bar stretching
   between them, over a fade so the white icons read on the light video. */
.hero-video-bar {
  position: absolute; left: 0; right: 0; bottom: 0; padding: 1.1rem .4rem .3rem;
  background: linear-gradient(transparent, rgba(24, 8, 36, .62));
}
.hero-video-seek { display: block; flex: 1 1 auto; min-width: 2.5rem; height: 1.1rem; margin: 0 .3rem; accent-color: #fff; cursor: pointer; }
.hero-video-row { display: flex; align-items: center; gap: .1rem; }
.hv-btn {
  display: grid; place-items: center; flex: none; width: 1.95rem; height: 1.95rem; padding: 0; border: 0; border-radius: 999px;
  background: transparent; color: #fff; cursor: pointer;
}
.hv-btn:hover { background: rgba(255, 255, 255, .16); }
.hv-btn svg { width: 19px; height: 19px; fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; }
.hero-video-time { flex: none; color: #fff; font: 600 .7rem/1 var(--font); font-variant-numeric: tabular-nums; white-space: nowrap; }
.hero-video-sound:focus-visible, .hv-btn:focus-visible, .hero-video-seek:focus-visible { outline: 3px solid var(--accent-2); outline-offset: 2px; }
/* Full screen takes the whole player: the frame fills the screen, the video
   shows whole (letterboxed, not cropped) and the bar spans the foot. Written
   twice because a selector list with one unknown pseudo-class drops entirely. */
/* On a phone the player expands over the page instead of using the browser's
   full screen (see initHeroVideo): the same look, fixed over the window, the
   hero lifted above the sticky nav, and the page under it held still. */
.hero:has(.hero-video.is-expanded) { z-index: 1000; }
.hero-video.is-expanded {
  position: fixed; inset: 0; z-index: 1000; width: 100%; height: 100%; margin: 0;
  aspect-ratio: auto; border-radius: 0; box-shadow: none; background: #000;
}
.hero-video.is-expanded video { object-fit: contain; }
.hero-video.is-expanded .hero-video-bar { padding: 2rem 1rem max(.8rem, env(safe-area-inset-bottom)); }
html.hero-video-open, html.hero-video-open body { overflow: hidden; }
/* The page's own scrollbar, and the gutter kept for it (scrollbar-gutter above), would otherwise
   stay beside the player as a strip down the right. */
html:has(.hero-video:fullscreen) { overflow: hidden; scrollbar-gutter: auto; }
html:has(.hero-video:-webkit-full-screen) { overflow: hidden; scrollbar-gutter: auto; }
.hero-video:fullscreen { width: 100%; height: 100%; aspect-ratio: auto; border-radius: 0; box-shadow: none; background: #000; }
.hero-video:fullscreen video { object-fit: contain; }
.hero-video:fullscreen .hero-video-bar { padding: 2rem max(1rem, calc(50% - 22rem)) .8rem; }
.hero-video:-webkit-full-screen { width: 100%; height: 100%; aspect-ratio: auto; border-radius: 0; box-shadow: none; background: #000; }
.hero-video:-webkit-full-screen video { object-fit: contain; }
.hero-video:-webkit-full-screen .hero-video-bar { padding: 2rem max(1rem, calc(50% - 22rem)) .8rem; }
@media (min-width: 720px) {
  .hero { display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: 2rem; align-items: center; }
  .hero-video { margin: 0; width: 230px; }
}
/* Nothing follows the buttons in the hero any more — the privacy badge that
   used to sit under them moved down to "Start here" — so their bottom margin
   was left holding open a row with nothing in it. Scoped through .hero to beat
   .btn-row's own `margin: .8rem 0`, which is defined further down this file at
   the same specificity and would otherwise win on order alone. */
.hero .hero-actions { margin-bottom: 0; }

/* The two hero actions stay on one row on a phone. Left to wrap they stack,
   which pushes the privacy badge and everything under it a button's height
   further down the first screen. They share the row equally and shrink into
   it — nowrap on the labels so a narrowed button breaks the row rather than
   silently splitting "See sample report" over two lines. */
@media (max-width: 460px) {
  .hero-actions { flex-wrap: nowrap; gap: .45rem; }
  .hero-actions .btn {
    flex: 1 1 0; min-width: 0; white-space: nowrap;
    padding: .6rem .45rem; font-size: .86rem;
  }
}
/* A pill (border-radius: 999px) reads fine for a short single-line label, which
   is what this started as. It stopped being that once the storage and
   no-tracking claims merged into one three-sentence line: a pill sized to a
   paragraph just rounds the corners of a block, which looks like a badge that
   grew too big for its shape rather than a banner. Styled like .alert instead
   — a plain bordered card, left-aligned, sized for reading rather than for
   glancing at. */
.eyebrow {
  display: block; margin: 0 0 1rem; padding: .7rem .9rem;
  background: var(--accent-soft); color: var(--accent);
  border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
  border-radius: 10px; font-size: .82rem; font-weight: 500; line-height: 1.5;
}
.eyebrow strong { font-weight: 700; }
/* Sits right under the dropzone with a small gap, so it reads as attached to
   the dropzone rather than floating in the space before the card's own edge
   below it. */
.upload-card .eyebrow { margin-top: .2rem; }
.lede { font-size: 1.06rem; color: var(--ink-soft); max-width: 62ch; }

/* ---------- cards ---------- */

.card {
  background: var(--bg-alt);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: clamp(1.1rem, 3vw, 1.6rem);
  margin-bottom: 1.2rem;
}
.card.good { border-left: 3px solid var(--good); }
.card.warn { border-left: 3px solid var(--warn); }
.alert-card { border-left: 3px solid var(--danger); background: var(--warn-soft); }

/* Sits above the steps row outside any card, so it takes a smaller size than
   the 1.28rem a card heading gets — but full-strength ink, not the muted grey:
   it is a heading, and it should read as one. --ink rather than a literal
   black, which would be invisible against the dark theme. */
.row-head { margin: 0 0 .55rem; font-size: 1.05rem; color: var(--ink); }
.steps-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(163px, 1fr)); gap: .8rem; margin-bottom: 1.6rem; }
.step-card { background: var(--bg-alt); border: 1px solid var(--line); border-radius: var(--radius); padding: 1.1rem; }
.step-card h3 { margin-top: .6rem; font-size: .98rem; }
.step-card p { margin: 0; font-size: .88rem; color: var(--ink-soft); }
.step-num {
  display: grid; place-items: center; width: 26px; height: 26px;
  /* --bg, not #fff: white on the dark theme's pale accent measures 2.25:1,
     which is not readable; --bg flips with the accent. */
  border-radius: 50%; background: var(--accent); color: var(--bg); font-size: .8rem; font-weight: 700;
}

/* ---------- the premium tier block ---------- */

/* The offer of the full report: on the welcome page (insightsHtml) and
   under a free card (fullReportLockedHtml).

   Solid accent border and a tinted fill, against the plain --line borders of
   everything around it: this is the one block on the page asking for money,
   and it should read as a distinct offer rather than as another list of
   features. The dashed border used on the in-report covers would be wrong
   here — dashed says "switched off", and nothing on this page is. */
.premium-tier {
  margin-top: 1.2rem; padding: 1rem clamp(.9rem, 3vw, 1.15rem);
  border: 1px solid var(--accent); border-radius: var(--radius);
  background: color-mix(in srgb, var(--accent-soft) 55%, var(--bg));
}
/* The price is pushed to the far end of the head rather than trailing the
   title, so it lands in the same place at every width and a reader scanning
   for "what does this cost" finds it without reading the sentence. */
.premium-tier-head {
  display: flex; align-items: center; gap: .55rem; flex-wrap: wrap;
}
.premium-tier-head h3 { margin: 0; font-size: 1.02rem; flex: 1 1 auto; }
.premium-tier-price {
  flex: none; font-size: 1.05rem; font-weight: 700; color: var(--accent);
  white-space: nowrap;
}
.premium-tier-blurb { margin: .5rem 0 0; font-size: .9rem; color: var(--ink-soft); }

/* Two columns where there is room for them, one where there is not. Four
   items in a single column is a tall block for something that is not the
   point of the page — but four across a wide laptop screen is the opposite
   problem, a single thin row that is harder to scan than two rows of two.
   Fixed at exactly two columns above the breakpoint, the same one
   the welcome page's tiers switch at, rather than `auto-fit`, which would keep
   adding a third and fourth column as the container gets wider. */
.premium-tier-list {
  list-style: none; margin: .8rem 0 0; padding: 0;
  display: grid; grid-template-columns: 1fr; gap: .55rem .9rem;
}
@media (min-width: 560px) {
  .premium-tier-list { grid-template-columns: repeat(2, 1fr); }
}
.premium-tier-item { display: flex; align-items: flex-start; gap: .55rem; }
.premium-tier-icon { font-size: 1.05rem; line-height: 1.45; flex: none; }
.premium-tier-text { display: flex; flex-direction: column; min-width: 0; }
.premium-tier-text strong { font-size: .92rem; line-height: 1.45; }
.premium-tier-text span { font-size: .84rem; color: var(--ink-soft); line-height: 1.4; }

/* The report's own use of this block — see paidSectionsLockedHtml() in
   app.js — sits among .card.section-card siblings, each with their own
   margin-bottom, so this needs the same spacing below it that .premium-tier
   otherwise only ever had above it on the welcome page. The button is full
   width for a report-width block; on the welcome page's narrower marketing
   copy there is no button at all. */
.paid-consolidated { margin-bottom: 1.2rem; }
/* The unlock offer on a free structured report, by the report's five parts:
   each a white panel led by its numeral, its title and a line on what it
   holds, then its sections. */
.unlock-parts { list-style: none; margin: .9rem 0 0; padding: 0; display: grid; gap: .65rem; }
.unlock-part { padding: .85rem clamp(.8rem, 2.5vw, 1rem) .9rem; border-radius: 14px; background: var(--bg-alt); }
.unlock-part-head { display: flex; align-items: center; gap: .75rem; }
.unlock-part-num {
  flex: none; display: grid; place-items: center; width: 2.3rem; height: 2.3rem; border-radius: 12px;
  font-size: .9rem; font-weight: 800; letter-spacing: .02em; color: #fff; font-variant-numeric: tabular-nums;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
}
.unlock-part-head h4 { margin: 0; font-size: 1rem; line-height: 1.3; }
.unlock-part .premium-tier-list { margin-top: .75rem; }
.paid-consolidated .btn { margin-top: 1rem; width: 100%; }

/* The sample dialog. The head is pinned and only the report scrolls, so the
   cross is on screen the whole way down — a reader four sections deep should
   never have to scroll back up to leave. */
.sample-dialog {
  width: min(46rem, calc(100vw - 1.5rem)); max-width: none;
  max-height: min(88vh, 60rem);
  padding: 0; overflow: hidden;
  flex-direction: column;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--bg); color: var(--ink); box-shadow: var(--shadow);
}
/* Scoped to [open], not set on the element: a bare `display: flex` beats the
   user agent's `dialog:not([open]) { display: none }`, which leaves the closed
   dialog laid out over the page swallowing every click on it. */
.sample-dialog[open] { display: flex; }
.sample-dialog::backdrop { background: rgba(20, 14, 26, .58); backdrop-filter: blur(2px); }

.sample-dialog-head {
  display: flex; align-items: center; gap: 1rem; flex: none;
  padding: .9rem clamp(1rem, 3vw, 1.5rem);
  border-bottom: 1px solid var(--line);
  background: var(--bg-alt);
}
.sample-dialog-head h2 { flex: 1 1 auto; min-width: 0; margin: 0; font-size: 1.15rem; }
/* The sample's head is a slim bar: the report is what the dialog is for. */
#sample-dialog .sample-dialog-head { padding: .35rem clamp(.8rem, 3vw, 1.2rem); gap: .6rem; }
#sample-dialog .sample-dialog-head h2 { font-size: .95rem; }
#sample-dialog .sample-close { width: 30px; height: 30px; margin: 0; border-radius: 8px; }
#sample-dialog .sample-close svg { width: 16px; height: 16px; }

.sample-close {
  flex: none; display: grid; place-items: center;
  width: 38px; height: 38px; margin: -.2rem -.3rem 0 0;
  border: 1px solid var(--line); border-radius: 10px;
  background: var(--bg); color: var(--ink-soft); cursor: pointer;
  transition: color .12s ease, border-color .12s ease, background .12s ease;
}
.sample-close svg { width: 20px; height: 20px; }
.sample-close:hover, .sample-close:focus-visible {
  color: var(--accent); border-color: var(--accent); background: var(--accent-soft); outline: none;
}

/* ---------- the Instagram guide ---------- */
/* Shares the sample dialog's frame, head and close cross — see .sample-dialog.
   Only what differs is written here. */

/* The button sits on the same row as the heading on a wide screen and drops
   under it on a phone, where a heading and a button side by side would leave
   both cramped. */
.help-head {
  display: flex; flex-wrap: wrap; align-items: baseline;
  gap: .6rem 1rem; margin-bottom: .2rem;
}
.help-head h2 { margin: 0; flex: 1 1 auto; }
.help-guide-open { flex: none; padding: .45rem .9rem; font-size: .9rem; }

.guide-steps { list-style: none; margin: 0; padding: 0; }
.guide-step { padding: 1.4rem 0; border-top: 1px solid var(--line); }
.guide-step:first-child { border-top: 0; padding-top: 0; }
.guide-step > p { margin: 0 0 1rem; }
.guide-step h3 {
  display: flex; align-items: baseline; gap: .6rem;
  margin: 0 0 .55rem; font-size: 1.05rem;
}
/* The step number as its own token rather than an <ol> marker, so it sits in
   the heading's line box at a size the rest of the dialog agrees with. */
.guide-num {
  flex: none; display: grid; place-items: center;
  min-width: 1.7rem; height: 1.7rem; padding: 0 .4rem; border-radius: 999px;
  background: linear-gradient(100deg, var(--accent), var(--accent-2));
  color: #fff; font-size: .8rem; font-weight: 700;
}
@media (prefers-color-scheme: dark) {
  .guide-num { color: var(--bg); }
}

/* ---------- the screenshots ---------- */
/* Real captures of the journey, redacted before shipping — see the comment
   above #guide-dialog in index.html.
 *
 * Wrapping grid, never a horizontal scroller. It was a scroller: three across
 * where there was room, swipe sideways on a phone. That hid two of every three
 * screenshots behind a gesture nothing on the page advertised, in a dialog
 * whose whole job is to show somebody what they are about to be looking at.
 * `auto-fit` puts three on a laptop and one on a phone, and everything is
 * reachable by scrolling the one direction the dialog already scrolls. */
.guide-shots {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: 1rem; margin: 0 0 1rem;
}
.guide-shots.is-single { grid-template-columns: minmax(0, 15rem); }
.guide-shot { margin: 0; min-width: 0; }

/* The frame is what the mark is positioned against, so it has to hug the
   image exactly — hence line-height: 0, which drops the inline descender gap
   that would otherwise leave the box a few pixels taller than the picture and
   shift every percentage down with it. */
.guide-frame { position: relative; display: block; line-height: 0; }
.guide-shot img {
  display: block; width: 100%; height: auto; border-radius: 12px;
  /* A capture of a white phone screen sits on a near-white dialog, so the two
     run together and a reader cannot tell where the screenshot stops and the
     page starts. --line is a pale lilac hairline built for separating rows
     inside a card and disappears entirely here; this is a real edge, with a
     shadow under it so the picture reads as a thing lying on the page. */
  border: 1px solid #9a90a6;
  box-shadow: 0 1px 5px rgba(20, 16, 26, .13);
  /* The captures are light-mode phone screens. On a dark page a pure white
     rectangle glares, so it is knocked back a little — enough to sit in the
     page, not so much that the grey UI rows lose their edges. */
  background: #fff;
}
@media (prefers-color-scheme: dark) {
  .guide-shot img { filter: brightness(.88); border-color: #574d63; box-shadow: none; }
}

/* The ring around the thing to tap. Positioned in percentages from the markup
   so it scales with the image at every column width, rather than being burnt
   into the JPEG where it could not be corrected without re-exporting.
 *
 * Magenta rather than the app's own accent: these sit on Instagram's white and
 * blue UI, and a purple ring on a blue button is the one pairing that does not
 * read. The white outer ring is what keeps it visible where it crosses a grey
 * row rather than a white one. */
.guide-mark {
  position: absolute; box-sizing: border-box;
  border: 3px solid #e0217d; border-radius: 10px;
  box-shadow: 0 0 0 2px rgba(255, 255, 255, .9);
  pointer-events: none;
}

/* Where each ring sits on its screenshot.
   These were style="" attributes on the spans themselves until the CSP
   went in: 'unsafe-inline' in style-src exists for inline attributes, and
   seven of them were the only thing keeping it there. One class per step,
   numbered to match guide/0N-*.jpg, so a rectangle and the picture it is
   drawn over are found by the same number. */
.guide-mark-1 { top: 43.3%; left: 2%; width: 92%; height: 18.9%; }
.guide-mark-2 { top: 63.5%; left: 4%; width: 92%; height: 5.5%; }
.guide-mark-3 { top: 55.8%; left: 3%; width: 94%; height: 14.6%; }
.guide-mark-4 { top: 60.6%; left: 4%; width: 92%; height: 15.9%; }
.guide-mark-5 { top: 69.9%; left: 4%; width: 92%; height: 19.4%; }
.guide-mark-6 { top: 58.6%; left: 4%; width: 92%; height: 17.0%; }
.guide-mark-7 { top: 70.4%; left: 4%; width: 92%; height: 25.8%; }
.guide-shot figcaption {
  margin-bottom: .5rem; font-size: .84rem; line-height: 1.4; color: var(--ink-soft);
}
.guide-shot figcaption b { color: var(--ink); font-weight: 600; }

/* ---------- the three settings inside step 3 ---------- */
/* Plain bullets, each reading as one sentence: what to change, what to change
   it to, and why in a few words. These were rows in a grid, each led by its own
   numbered pill — which read as three more steps in a guide that only has four,
   when they are three fields on one screen. */
.guide-settings { list-style: disc; margin: 0 0 .8rem; padding-left: 1.25rem; }
.guide-settings > li { margin: 0 0 .5rem; line-height: 1.5; }
.guide-settings > li:last-child { margin-bottom: 0; }
.guide-setting-name { font-weight: 600; }
.guide-setting-why { font-size: .92rem; color: var(--ink-soft); }
/* The one setting that costs hours if it is wrong is the only one coloured.
   Once in three is a signal; three in three is wallpaper. */
.guide-settings > li.is-warning .guide-setting-why strong { color: var(--danger); }
.guide-then { margin: 0 0 1rem; }

/* ---------- the closing hand-off ---------- */
/* The guide's last word used to be "upload it at the bottom of this page" —
   true, but the reader still has to go find that page and scroll it
   themselves. A button that does the finding removes the one step the guide
   could not otherwise shorten. */
.guide-cta { margin: 1.6rem 0 0; padding-top: 1rem; border-top: 1px solid var(--line); }

/* Sits under the last set of steps inside the download-instructions
   disclosure, separated from them so it reads as a way out of reading rather
   than a fourth instruction. */
.help-illustrated {
  margin: 1.1rem 0 0; padding-top: .9rem; border-top: 1px solid var(--line);
  font-size: .92rem; color: var(--muted);
}
.help-illustrated a { color: var(--accent); font-weight: 600; }

.sample-dialog-body {
  flex: 1 1 auto; overflow-y: auto; overscroll-behavior: contain;
  padding: 1rem clamp(1rem, 3vw, 1.5rem) 1.2rem;
}
/* Inside the dialog the sections are already on a panel, so the page-level
   card treatment would be a box in a box. */
.sample-dialog-body .section-card {
  box-shadow: none; border-radius: 12px; padding: 1rem clamp(.9rem, 2vw, 1.2rem);
}

/* ---------- buttons ---------- */

.btn {
  appearance: none; cursor: pointer; font: inherit; font-weight: 600;
  border: 1px solid transparent; border-radius: 10px;
  padding: .62rem 1.1rem;
  background: linear-gradient(100deg, var(--accent), var(--accent-2));
  color: #fff;
  transition: transform .12s ease, filter .12s ease;
}
.btn:hover { filter: brightness(1.06); }
.btn:active { transform: translateY(1px); }
/* A disabled button has to look disabled. There was no rule for this at all,
   so a button the app had genuinely switched off — the payment sheet's Cancel
   once a charge clears, the promo Apply beside it, the sample report's inert
   Unlock — sat there at full strength, took a click, and did nothing. That
   reads as broken rather than as deliberate. Greyscale plus a flat opacity
   rather than a hand-picked grey, so it lands the same way on the filled
   gradient, the ghost and the outline without three separate rules; the
   hover/active feedback is withdrawn with it, since a button that cannot be
   pressed must not answer the pointer as though it can. */
.btn:disabled,
.btn[disabled] { cursor: not-allowed; opacity: .45; filter: grayscale(1); }
.btn:disabled:hover, .btn[disabled]:hover { filter: grayscale(1); }
.btn:disabled:active, .btn[disabled]:active { transform: none; }
.btn-ghost:disabled:hover, .btn-ghost[disabled]:hover { background: transparent; }
.btn-outline:disabled:hover, .btn-outline[disabled]:hover { background: transparent; }
/* --accent is a deep purple in the light theme and a pale one in the dark, so
   white holds on one and washes out on the other: measured, filled buttons ran
   at 2.25:1 against their own background in dark mode. --bg flips with the
   theme and stays at the far end of it. Scoped to dark rather than applied to
   both, because in the light theme white is the better of the two — it is the
   pink end of the gradient that is tightest there, and --bg would narrow it. */
@media (prefers-color-scheme: dark) {
  .btn { color: var(--bg); }
}
.btn-lg { padding: .78rem 1.5rem; font-size: 1.02rem; }
/* The one button a first-time reader has to find. It sat at its text width in
   the middle of a card and read as one option among the paragraphs around it;
   full width it reads as the thing to press, and on a phone it spans the
   screen the way an app's primary action does. Kept full width at every size
   rather than only on mobile — the card is narrow enough on a laptop that a
   button spanning it still looks deliberate rather than stretched. */
.btn-block { display: block; width: 100%; text-align: center; }
/* The same trap .mode-progress[hidden] documents further down, and it caught
   the welcome page's "Try again" the day it was added: `display:block` above is
   an author rule, so it beats the UA's `[hidden]{display:none}` outright and
   specificity never enters into it. A .btn-block the app has hidden stays on
   screen without this. */
.btn-block[hidden] { display: none; }
.btn-ghost { background: transparent; color: var(--accent); border-color: var(--line); }
.btn-ghost:hover { background: var(--accent-soft); }
.btn-ghost.danger { color: var(--danger); }

/* Outlined in the accent rather than the hairline .btn-ghost uses, and at 2px
   so it holds its own beside a filled button. The padding gives back the pixel
   the thicker border takes, so the button occupies the same box as a filled
   one. Its neighbour in the row would otherwise be stretched to match it and
   the whole row would grow — 47px to 49 — for a border nobody asked to be
   taller. */
.btn-outline {
  background: transparent; color: var(--accent);
  border: 2px solid var(--accent); padding: calc(.62rem - 1px) calc(1.1rem - 1px);
}
.btn-outline:hover { background: var(--accent-soft); }
.btn-row { display: flex; flex-wrap: wrap; gap: .6rem; margin: .8rem 0; }
/* A quiet secondary action reads as a link, not a third button competing
   with Cancel/Send for weight — no box, no background, just the site's
   ordinary link colour and underline. */
.link-btn {
  appearance: none; background: none; border: none; padding: 0; cursor: pointer;
  font: inherit; font-size: .86rem; color: var(--accent);
  text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 2px;
}
.link-btn:hover { filter: brightness(1.06); }
.cta-row { display: flex; flex-wrap: wrap; gap: .7rem; align-items: center; margin-top: 1.4rem; }
/* The icons only show where the row moves into the left column. */
.cta-row .btn { display: inline-flex; align-items: center; gap: .45rem; }
/* A free report has no full report to download: hidden must win over the row's display. */
.cta-row .btn[hidden] { display: none !important; }
.cta-row .cta-icon { display: block; flex: none; width: 18px; height: 18px; }
/* A full report on a phone: the three actions side by side across the
   screen, each an icon over a small label, in the card tools' own style. */
@media (max-width: 719px) {
  #view-profile.profile-paid .cta-row {
    /* A column per visible action, however many there are. */
    display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: .5rem; align-items: stretch;
  }
  /* The same height whichever label wraps, icons on one line along the top. */
  #view-profile.profile-paid .cta-row .btn {
    flex-direction: column; justify-content: flex-start; gap: .35rem; min-width: 0; margin: 0;
    padding: .7rem .35rem .6rem; border-radius: 14px; border: 1px solid var(--line);
    background: var(--bg-alt); color: var(--ink-soft); box-shadow: var(--shadow);
    font-size: .72rem; font-weight: 700; line-height: 1.2; text-align: center; white-space: normal;
  }
  #view-profile.profile-paid .cta-row .cta-icon { width: 22px; height: 22px; color: var(--accent); }
  #view-profile.profile-paid .cta-row .btn.danger { color: #b3261e; }
  #view-profile.profile-paid .cta-row .btn.danger .cta-icon { color: #b3261e; }
  #view-profile.profile-paid .cta-row .btn:active { transform: translateY(1px); }
}

/* ---------- upload ---------- */

/* The card is the drop target now — there is no box of its own. It shows only
   while something is actually being dragged over it, so the affordance appears
   exactly when it is usable and takes no space the rest of the time. */
.upload-card.is-over {
  border-color: var(--accent); background: var(--accent-soft);
  transition: border-color .15s ease, background .15s ease;
}
/* "(required)" / "(recommended)" beside a source's name. Inside the <strong>
   rather than after it so it wraps with the name on a narrow screen, but set
   back to normal weight and the soft ink so it reads as a note about the row
   rather than as part of what the row is called.

   var(--ink-soft), not var(--muted): the latter is what the *class* is called
   and there is no custom property of that name. An undefined var() is invalid
   at computed-value time, so the declaration was dropped and the colour simply
   inherited — the tag rendered in full-strength ink, indistinguishable from
   the name it was meant to sit behind, and nothing about the page looked
   broken. Caught by the check that compares the two computed colours. */
.mode-tag { font-weight: 500; font-size: .88em; color: var(--ink-soft); }

.upload-actions { margin: 1.1rem 0 .2rem; }
/* The status line under it is centred to match, or it hangs off one end of a
   button that now spans the whole card. */
.upload-actions .upload-loaded { text-align: center; }
/* Only present once something is loaded, which is when it has anything to
   say. Sits under the button rather than beside it so the button keeps the
   whole line at every width. */
.upload-loaded { margin: .6rem 0 0; font-size: .9rem; }

.switch-row { display: flex; gap: .7rem; align-items: flex-start; margin: 1.1rem 0 .4rem; font-size: .92rem; }
.switch-row input { margin-top: .35rem; flex: none; width: 18px; height: 18px; accent-color: var(--accent); }
.switch-row span span { display: block; margin-top: .2rem; font-size: .86rem; }

.alert {
  background: var(--warn-soft); border: 1px solid color-mix(in srgb, var(--warn) 35%, transparent);
  color: var(--warn); border-radius: 10px; padding: .7rem .9rem; margin: 1rem 0; font-size: .92rem;
}
.alert ul { margin: .4rem 0 0; padding-left: 1.2rem; }
.alert li { margin-bottom: .2rem; }

/* The words to look for in Instagram's own interface. Underlined in the text's
   own colour rather than the accent, because links on this site are accent
   text and these are not links — a reader who tries to tap one should not have
   been invited to. */
.ui-label {
  font-weight: 600; color: var(--ink);
  text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 2px;
}
/* Not on the FAQ. Everywhere else a .ui-label is a thing to go and tap while
   following instructions, and the weight is what makes it findable mid-step.
   The FAQ is read rather than followed — its answer to "what file do I need"
   names the same path, but as a sentence — and a run of five semi-bold phrases
   in one line reads as emphasis nobody asked for. The underline stays, so the
   labels are still marked as screen names rather than prose. */
#view-about .ui-label { font-weight: inherit; }
/* The long way round, kept for when a deep link stops working — Meta and
   Google move these URLs, and a reader who follows a dead one must not be
   stranded. Set smaller and quieter than the step it sits under, so it reads
   as a footnote rather than as a second instruction competing with the first. */
.step-fallback {
  display: block; margin-top: .3rem;
  font-size: .82rem; color: var(--ink-soft);
}
/* The fallback is a footnote, not a second instruction — its menu labels keep
   the underline .ui-label uses everywhere else (so they still read as "this
   is a button in someone else's UI"), but drop the bold weight that would
   otherwise make this quiet line compete with the numbered step above it. */
.step-fallback .ui-label { font-weight: 400; }

/* The hero's primary action scrolls here. Without a scroll margin the card
   lands flush at viewport top, which is underneath the sticky nav — so the
   reader arrives at the instructions with the heading hidden. */
.help-card { scroll-margin-top: 4.5rem; }

.help ol { padding-left: 1.2rem; margin-bottom: .8rem; }
.help li { margin-bottom: .35rem; }
.help-card h2 { margin-top: 0; }
.help-card p:last-child { margin-bottom: 0; }

/* The optional sources, collapsed, inside the Instagram instructions card
   rather than beside it — the two are one job, and a second card of the same
   weight read as a second required step.
   A hairline above it is what carries "same section, lesser thing": enough
   separation that the numbered list above clearly ends, without the gap and
   shadow of a card of its own.
   `list-item` display with `list-style: none` rather than the shorthand: that
   is what removes the UA triangle in Chrome and Safari while leaving the
   element a real summary. ::-webkit-details-marker covers older WebKit, which
   ignores the first rule. The chevron is drawn instead so it can rotate and
   follow the theme. */
.optional-card { border-top: 1px solid var(--line); margin-top: 1.2rem; }
.optional-card > summary {
  display: list-item; list-style: none; cursor: pointer;
  padding: 1rem 2rem 0 0; margin: 0; position: relative;
}
.optional-card > summary::-webkit-details-marker { display: none; }
.optional-card > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; border-radius: 8px; }
/* A step down from the card's own h2, because it is subordinate to it — but
   still heavier than the body text, so the row reads as a control. */
.optional-title { display: block; font-size: 1rem; font-weight: 650; letter-spacing: -.01em; }
/* Drawn rather than a glyph, so it inherits the accent and rotates cleanly.
   Pinned to the title's own line rather than centred on the whole box, so it
   stays put if the title ever wraps to two lines on a narrow phone. */
.optional-card > summary::after {
  content: ''; position: absolute; right: .2rem; top: 1.4rem;
  width: .5rem; height: .5rem; border-right: 2px solid var(--accent);
  border-bottom: 2px solid var(--accent); transform: rotate(45deg);
  transition: transform .15s ease;
}
.optional-card[open] > summary::after { transform: rotate(-135deg); }
.optional-card h3:first-of-type { margin-top: 1.3rem; }
.optional-card > :last-child { margin-bottom: 0; }

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

.progress { height: 10px; background: var(--accent-soft); border-radius: 999px; overflow: hidden; margin: 1rem 0 .6rem; }
.progress.slim { height: 5px; }
.progress-bar { height: 100%; width: 0; background: linear-gradient(90deg, var(--accent), var(--accent-2)); transition: width .25s ease; }
.progress-bar.indeterminate {
  width: 40%; transition: none;
  animation: sweep 1.6s ease-in-out infinite;
}
@keyframes sweep {
  0% { margin-left: -40%; }
  100% { margin-left: 100%; }
}

.confidence-meter { height: 8px; background: var(--accent-soft); border-radius: 999px; overflow: hidden; margin-bottom: .9rem; }
.confidence-fill { height: 100%; background: linear-gradient(90deg, var(--accent), var(--accent-2)); }
.caveats { color: var(--ink-soft); font-size: .89rem; padding-left: 1.1rem; margin: .6rem 0 0; }

/* The confidence card's "Data sources" subsection and its re-run button —
   see sourcesUsedHtml() in app.js. Compact: each row is a status line, not a
   card, and the whole list sits on one strip rather than one box per source —
   the section describes a fact about the report, not three separate actions,
   now that adding or replacing a source lives behind the button below it. */
.trust-sources { margin-top: 1rem; padding-top: .9rem; border-top: 1px solid var(--line); }
.trust-sources h3 { margin: 0 0 .35rem; font-size: 1rem; }
.trust-sources > p.muted { margin: 0 0 .6rem; font-size: .88rem; }
.source-list {
  list-style: none; margin: 0 0 .8rem; padding: 0; border: 1px solid var(--line); border-radius: 12px;
  overflow: hidden; background: var(--bg-alt);
}
.source-row {
  display: flex; align-items: center; justify-content: space-between; gap: .8rem;
  padding: .45rem .8rem; font-size: .92rem;
}
.source-row + .source-row { border-top: 1px solid var(--line); }
.source-name { font-weight: 600; }
.source-tick { color: var(--good); font-weight: 700; }
.source-cross { color: var(--danger); font-weight: 700; }

/* ---------- trait bars ---------- */

.trait-block { margin-bottom: .2rem; }
.trait-row { display: grid; grid-template-columns: minmax(11rem, 2fr) 3fr 2.5rem; gap: .7rem; align-items: center; margin-bottom: .45rem; }
.trait-label { font-size: .88rem; }
.bar { height: 9px; background: var(--accent-soft); border-radius: 999px; overflow: hidden; }
.bar-fill { height: 100%; background: linear-gradient(90deg, var(--accent), var(--accent-2)); border-radius: 999px; }
.trait-num { font-variant-numeric: tabular-nums; font-weight: 650; font-size: .88rem; text-align: right; }
.trait-reading { margin: .3rem 0 .4rem; font-size: .93rem; }
.trait-evidence { margin: 0 0 1.3rem; display: flex; flex-wrap: wrap; gap: .3rem; }
.ev { font-size: .78rem; padding: .12rem .55rem; border-radius: 999px; background: var(--accent-soft); color: var(--ink-soft); }

/* Definition lists carry most of the model's output: a short title and a
   paragraph of detail. */
.points { margin: .4rem 0 0; }
.points dt { font-weight: 650; margin-top: .9rem; }
.points dt:first-child { margin-top: 0; }
.points dd { margin: .15rem 0 0; color: var(--ink-soft); }

/* ---------- tags, pills, themes ---------- */

.tag-row { display: flex; flex-wrap: wrap; gap: .4rem; }
.tag { background: var(--accent-soft); color: var(--accent); border-radius: 999px; padding: .18rem .7rem; font-size: .84rem; }
.pill-row { display: flex; flex-wrap: wrap; gap: .45rem; margin-top: 1rem; }
.pill { border: 1px solid var(--line); border-radius: 999px; padding: .1rem .6rem; font-size: .74rem; font-weight: 600; color: var(--ink-soft); vertical-align: middle; }
code { font-size: .9em; background: var(--accent-soft); padding: .1rem .35rem; border-radius: 4px; }

.theme-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: .6rem; }
.theme-chip { border: 1px solid var(--line); border-radius: 12px; padding: .6rem .75rem; font-size: .9rem; }
.theme-score {
  display: inline-grid; place-items: center; width: 30px; height: 30px; margin-right: .4rem;
  border-radius: 8px; font-size: .78rem; font-weight: 700; color: #fff; vertical-align: middle;
  background: linear-gradient(180deg, var(--accent), var(--accent-2));
  opacity: calc(.45 + (var(--v) / 200));
}
.theme-src { display: block; font-size: .74rem; margin-top: .2rem; }

/* ---------- hour histogram ---------- */

.hours { display: flex; align-items: flex-end; gap: 2px; height: 90px; margin-bottom: .5rem; }
.hour { flex: 1; display: flex; flex-direction: column; justify-content: flex-end; height: 100%; }
.hour-bar { background: linear-gradient(180deg, var(--accent), var(--accent-2)); border-radius: 3px 3px 0 0; min-height: 2px; }
.hour-label { font-size: .65rem; color: var(--ink-soft); text-align: center; height: 1em; }

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

.question { border: 0; padding: 0; margin: 0 0 1.8rem; }
.question legend { font-weight: 650; font-size: 1.02rem; padding: 0; margin-bottom: .5rem; }
.req { font-size: .7rem; color: var(--accent-2); font-weight: 600; text-transform: uppercase; letter-spacing: .04em; }
.extra-badge {
  font-size: .68rem; font-weight: 600; color: var(--ink-soft); border: 1px solid var(--line);
  border-radius: 999px; padding: .05rem .5rem; white-space: nowrap;
}
.help-text { font-size: .87rem; margin: -.25rem 0 .6rem; }

input[type=text], select, textarea {
  width: 100%; font: inherit; color: var(--ink);
  background: var(--bg-alt); border: 1px solid var(--line); border-radius: 10px;
  padding: .55rem .75rem;
}
input[type=text]:focus, select:focus, textarea:focus { outline: 2px solid var(--accent); outline-offset: 1px; }
textarea { resize: vertical; }
.field { display: block; font-size: .9rem; color: var(--ink-soft); margin-bottom: .8rem; }
.field input, .field textarea { margin-top: .3rem; }
.note-field { margin-top: .7rem; }
.other-input { margin-top: .6rem; }

.chip-grid { display: flex; flex-wrap: wrap; gap: .45rem; }
.chip-grid.tight { gap: .35rem; }
.chip {
  display: inline-flex; align-items: center; gap: .35rem; cursor: pointer;
  border: 1px solid var(--line); border-radius: 999px; padding: .32rem .8rem;
  font-size: .9rem; background: var(--bg-alt); transition: all .12s ease; user-select: none;
}
.chip input { position: absolute; opacity: 0; width: 0; height: 0; }
.chip:hover { border-color: var(--accent); }
.chip.is-checked { background: var(--accent); border-color: var(--accent); color: #fff; }
.chip:has(input:disabled) { opacity: .42; cursor: not-allowed; }
.chip:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
.chip-badge { font-size: .6rem; font-weight: 700; letter-spacing: .04em; opacity: .75; border: 1px solid currentColor; border-radius: 4px; padding: 0 .2rem; }
.counter { margin: .35rem 0 0; }

.sliders { display: grid; gap: 1rem; }
.slider-row label { display: flex; justify-content: space-between; font-size: .9rem; margin-bottom: .2rem; }
.slider-row output { font-variant-numeric: tabular-nums; font-weight: 700; color: var(--accent); }
.slider-row input[type=range] { width: 100%; accent-color: var(--accent); }
.slider-ends { display: flex; justify-content: space-between; font-size: .76rem; color: var(--ink-soft); }

.grid-rows { display: grid; gap: .8rem; }
.grid-row { display: grid; grid-template-columns: minmax(8rem, 1fr) 2.4fr; gap: .6rem; align-items: center; }
.grid-label { font-size: .88rem; color: var(--ink-soft); }

/* ---------- profile ---------- */

.qr-panel { display: grid; grid-template-columns: auto 1fr; gap: 0 1.4rem; align-items: start; }
/* The heading sits above both columns rather than beside the code. */
.qr-title { grid-column: 1 / -1; margin-bottom: 1rem; }
.qr-holder { text-align: center; }
/* The card payload is a few hundred characters, so the code has many more
   modules than a short URL — it needs the size to stay scannable. */
/* Backed at 900px and displayed at this size, so module edges stay crisp on a
   high-DPI screen — the code has to survive being photographed off a phone. */
/* Any canvas in a QR panel, not just the profile page's — the scan page shows
   this person's own code too, in a second .qr-holder. */
.qr-holder canvas { width: 300px; max-width: 100%; height: auto; aspect-ratio: 1; border-radius: 12px; background: #fff; }
.facts { display: grid; grid-template-columns: minmax(7rem, auto) 1fr; gap: .3rem .9rem; margin: 0; }
.facts dt { color: var(--ink-soft); font-size: .88rem; }
.facts dd { margin: 0; }
.note { margin: .5rem 0 0; padding: .5rem .9rem; border-left: 3px solid var(--line); color: var(--ink-soft); font-size: .92rem; }

/* ---------- scanning ---------- */

#scan-video { width: 100%; max-height: 60vh; border-radius: 12px; background: #000; margin: .8rem 0; }

/* ---------- report ---------- */

.score-pair { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 1rem; margin-bottom: 1.2rem; }
.score-card { text-align: center; margin-bottom: 0; }
.score-card h2 { margin: .8rem 0 .1rem; }
/* One basis per report, so the score sits beside its verdict rather than
   stacked in a column beside a sibling. */
.score-single { display: flex; gap: 1.3rem; align-items: center; text-align: left; margin-bottom: 1.2rem; }
.score-single h2 { margin: 0 0 .1rem; }
.score-single .ring { flex: none; }
.band { font-weight: 700; color: var(--accent); margin-bottom: .6rem; }
.score-card p:last-child { font-size: .92rem; color: var(--ink-soft); margin: 0; }
.ring {
  --pct: 0;
  width: 108px; height: 108px; margin: 0 auto; border-radius: 50%;
  display: grid; place-items: center;
  background: conic-gradient(var(--accent) calc(var(--pct) * 1%), var(--accent-soft) 0);
}
.ring span {
  width: 82px; height: 82px; border-radius: 50%; background: var(--bg-alt);
  display: grid; place-items: center; font-size: 1.6rem; font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.tier-a .ring { --ring: var(--good); }
.tier-d .band { color: var(--warn); }

.playbook { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 1rem; }
.playbook h3 { margin-top: 0; }
.playbook ul, .card ul { padding-left: 1.15rem; }
.card li { margin-bottom: .5rem; }

.score-pill { display: inline-block; min-width: 2.4rem; text-align: center; border-radius: 999px; padding: .1rem .5rem; font-weight: 700; font-size: .84rem; color: #fff; }
.score-pill.s-a { background: var(--good); }
.score-pill.s-b { background: var(--accent); }
.score-pill.s-c { background: var(--warn); }
.score-pill.s-d { background: var(--danger); }

/* ---------- tables ---------- */

.table-scroll { overflow-x: auto; }
table { border-collapse: collapse; width: 100%; font-size: .92rem; }
/* Let the wrapper scroll rather than letting narrow screens squeeze a column
   down to a single wrapped character. */
th, td { text-align: left; padding: .5rem .6rem; border-bottom: 1px solid var(--line); white-space: nowrap; }
th { color: var(--ink-soft); font-weight: 600; font-size: .84rem; }

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

/* ---------- MBTI ---------- */

.type-nickname { font-weight: 500; color: var(--ink-soft); font-size: .82em; }

.axes { display: grid; gap: .7rem; margin: 1rem 0 1.4rem; }
.axis { display: grid; grid-template-columns: 2.4rem 1fr; gap: .8rem; align-items: start; }
.axis-letter {
  display: grid; place-items: center; width: 2.4rem; height: 2.4rem;
  border-radius: 12px; background: var(--accent-soft); color: var(--accent);
  font-weight: 700; font-size: 1.1rem;
}
.axis-name { font-weight: 650; margin-right: .35rem; }
.axis-against { color: var(--ink-soft); font-size: .86rem; margin-right: .4rem; }
.axis p { margin: .2rem 0 0; font-size: .92rem; }
/* A slight lean should look slighter than a clear one at a glance. */
.pill-slight { opacity: .6; }
.pill-clear { border-color: var(--accent); color: var(--accent); }

.takeaways { margin: .4rem 0 0; padding-left: 1.1rem; }
.takeaways li { margin-bottom: .45rem; }

.implications dt { font-weight: 600; color: var(--ink); }

/* ---------- report sections ---------- */
/*
 * The profile is a dozen long cards in a row. Without a repeating header
 * shape it reads as one undifferentiated wall, so every section opens with a
 * glyph, a title and a line of purpose, and carries a hairline accent at the
 * top to mark where one ends and the next begins.
 */

/* Opening a section scrolls its heading to the top of the screen (see the
   accordion handler in app.js). The nav is sticky and would sit over that
   heading, so the scroll stops this far short of it — measured against a nav
   that is 45px on a phone and 50px on a laptop, leaving a small gap at both.
   Kept here rather than as a pixel figure in the JS so the offset moves with
   the nav if the nav ever changes height. */
.section-card { position: relative; overflow: hidden; scroll-margin-top: 4rem; }
/* Inside the sample dialog the scrolling box is the dialog body, whose own
   head sits outside it and overlaps nothing — so the same scroll only needs
   enough margin to not look flush against the top edge. */
.sample-dialog-body .section-card { scroll-margin-top: .75rem; }
.section-card::before {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 3px;
  background: linear-gradient(90deg, var(--accent), var(--accent-2));
  opacity: .8;
}

.card-head { display: flex; gap: .8rem; align-items: flex-start; margin-bottom: 1.1rem; }
.card-head h2 { margin: 0; }
/* The text block beside the glyph, so the chevron below can push to the far
   edge of the card rather than sitting against the end of the title. */
.card-head > div { flex: 1; min-width: 0; }

/*
 * Collapsible sections.
 *
 * The report opens with every section shut, so the whole of it is a short list
 * of headings a reader can scan and pick from rather than one long scroll they
 * have to travel. See collapseSections() in app.js for the state, and the
 * delegated handler beside it for the toggle.
 *
 * Hiding works by sibling rather than by wrapping the body in a container:
 * every section is already `.card-head` followed by its content, so "everything
 * that is not the head" names the body exactly, and no builder had to be
 * restructured to introduce a wrapper element around it.
 *
 * Screen only. In print there is no one to click anything, and a report that
 * printed as ten headings and nothing else would be worthless — so the collapse
 * simply does not exist there, and the chevrons go with the other controls.
 */
.card-toggle {
  display: flex; align-items: center; gap: .6rem; width: 100%;
  justify-content: space-between; padding: 0; border: 0; background: none;
  font: inherit; color: inherit; text-align: left; cursor: pointer;
}
.card-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; border-radius: 6px; }
.card-toggle-text { min-width: 0; }
.card-chevron {
  flex: none; width: .62rem; height: .62rem; margin-right: .15rem;
  border-right: 2.5px solid var(--ink-soft); border-bottom: 2.5px solid var(--ink-soft);
  border-radius: 1px;
  /* Points down when open, right when shut — the rotation is the animation, so
     it reads as the same arrow turning rather than one glyph swapped for another. */
  transform: rotate(45deg); transform-origin: center;
  transition: transform .18s ease;
}
.card-head-toggle:hover .card-chevron { border-color: var(--accent); }

@media screen {
  .section-card.is-collapsed > :not(.card-head) { display: none; }
  /* The head's bottom margin is spacing between it and a body that is no
     longer there, and leaving it in is most of what stops a shut card from
     actually looking shut. */
  .section-card.is-collapsed .card-head { margin-bottom: 0; }
  .section-card.is-collapsed { padding-top: .9rem; padding-bottom: .9rem; margin-bottom: .55rem; }
  .section-card.is-collapsed .card-chevron { transform: rotate(-45deg); }
  /* One line each while shut, so every row in the list is the same height and
     the whole thing scans as an index. The four paid sections have the longest
     sub-lines by far — three lines apiece — and left alone they make their own
     rows twice the height of the free ones for text a reader is about to see
     in full anyway the moment they open the section. */
  .section-card.is-collapsed .card-sub {
    display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 1;
    line-clamp: 1; overflow: hidden;
  }
}

@media (prefers-reduced-motion: reduce) {
  .card-chevron { transition: none; }
}
.card-icon {
  flex: none; display: grid; place-items: center; width: 2.3rem; height: 2.3rem;
  border-radius: 12px; background: var(--accent-soft); font-size: 1.15rem; line-height: 1;
}
.card-sub { margin: .25rem 0 0; color: var(--ink-soft); font-size: .9rem; }

/* Strengths beside weaknesses reads faster than one above the other. */
.split { display: grid; grid-template-columns: 1fr 1fr; gap: 1.2rem; margin-bottom: .4rem; }
.split h3 { margin-top: 0; }
.h-good { color: var(--good); }
.h-warn { color: var(--warn); }

.callout {
  background: var(--accent-soft); border-radius: 12px;
  padding: .9rem 1.1rem; margin: 1.2rem 0 .4rem;
}
.callout h3 { margin-top: 0; }
.callout p:last-child { margin-bottom: 0; }
/* The one warm line about what this attachment style is good at, sitting
   directly under its name. Weighted a little heavier than the reasoning that
   follows so it reads as part of the heading rather than as the first
   sentence of the argument — a reader who has just been handed one of four
   loaded words should meet this before they meet the working. */
.attachment-tone { font-weight: 600; margin-top: -.2rem; }

ul.ticks { list-style: none; padding-left: 0; margin: .4rem 0 0; }
ul.ticks li { position: relative; padding-left: 1.5rem; margin-bottom: .4rem; }
ul.ticks li::before { content: "\2713"; position: absolute; left: 0; color: var(--accent); font-weight: 700; }

/* A tick beside a prohibition reads as approval, so rules get a cross. */
ul.nots { list-style: none; padding-left: 0; margin: .4rem 0 0; }
ul.nots li { position: relative; padding-left: 1.5rem; margin-bottom: .4rem; }
ul.nots li::before { content: "\2715"; position: absolute; left: 0; color: var(--danger); font-weight: 700; }

/* The counterpart to .ticks: things that go out rather than stay put. */
ul.sends { list-style: none; padding-left: 0; margin: .4rem 0 0; }
ul.sends li { position: relative; padding-left: 1.5rem; margin-bottom: .4rem; }
ul.sends li::before { content: "\2192"; position: absolute; left: 0; color: var(--warn); font-weight: 700; }


/* The one-noun opener. It is the first thing on the report, so it gets to be
   the biggest thing on the report. */
.essence {
  display: flex; gap: 1rem; align-items: center;
  padding: 1rem 1.1rem; margin-bottom: 1.3rem;
  border-radius: 14px;
  background: linear-gradient(120deg, var(--accent-soft), color-mix(in srgb, var(--accent-soft) 35%, transparent));
  border: 1px solid color-mix(in srgb, var(--accent) 22%, transparent);
}
/* A medallion rather than a loose glyph: the emoji stands in for a character,
   so it needs to read as a portrait slot rather than as punctuation. */
.essence-icon {
  flex: none; font-size: 2.4rem; line-height: 1;
  width: 4.4rem; height: 4.4rem; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--bg-alt);
  border: 2px solid color-mix(in srgb, var(--accent) 30%, transparent);
}
.essence-label {
  margin: 0; font-size: .74rem; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--ink-soft);
}
.essence-noun {
  margin: .1rem 0 .35rem; font-size: 1.55rem; font-weight: 700; line-height: 1.15;
  letter-spacing: -.02em;
  background: linear-gradient(100deg, var(--accent), var(--accent-2));
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
/* Name and franchise share a baseline, wrapping only if the name is long. */
.essence-name { display: flex; align-items: baseline; gap: .55rem; flex-wrap: wrap; }
.essence-franchise {
  font-size: .8rem; font-weight: 600; color: var(--ink-soft); white-space: nowrap;
}
.essence-why { margin: 0; font-size: .93rem; }
.callout .essence-label { margin-top: .9rem; }

/* The four headline findings, so someone who reads only the first section
   still leaves with the answers. */
.glance {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: .9rem 1rem; margin: 0 0 1.3rem;
  border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
  padding: .9rem 0;
}
.glance-item { display: flex; flex-direction: column; gap: .1rem; }
.glance-label { font-size: .72rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--ink-soft); }
.glance-value { font-weight: 650; font-size: 1rem; line-height: 1.2; }
.glance-note { font-size: .8rem; color: var(--ink-soft); }
/* Chips inside the callout share its background, so give them their own. */
.callout .ev { background: var(--bg-alt); }
.callout .fineprint { margin-top: .9rem; }

/* ---------- love languages ---------- */

.love-head { margin-top: 1.6em; }
.love-split { margin-top: .2rem; }
.love-blurb { font-size: .86rem; margin-bottom: .7rem; }

.love-row {
  display: flex; gap: .7rem; align-items: flex-start;
  border: 1px solid var(--line); border-left-width: 3px; border-radius: 12px;
  padding: .7rem .8rem; margin-bottom: .55rem;
}
/* The ranking should be visible without reading the pill. */
.love-primary { border-left-color: var(--accent); background: color-mix(in srgb, var(--accent-soft) 55%, transparent); }
.love-secondary { border-left-color: color-mix(in srgb, var(--accent) 45%, transparent); }
.love-minor { border-left-color: var(--line); opacity: .8; }

.love-icon { font-size: 1.5rem; line-height: 1.2; flex: none; }
.love-row h4 { margin: 0 0 .25rem; font-size: .96rem; display: flex; flex-wrap: wrap; gap: .4rem; align-items: baseline; }
.love-row p { margin: 0; font-size: .9rem; }
.love-why { color: var(--ink-soft); font-size: .83rem; margin-top: .3rem !important; }
.pill-primary { border-color: var(--accent); color: var(--accent); }
.pill-minor { opacity: .65; }

/* ---------- tiles ---------- */

.tile-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); gap: .7rem; margin: .5rem 0 0; }
.tile {
  border: 1px solid var(--line); border-radius: 12px; padding: .8rem .9rem;
  background: color-mix(in srgb, var(--accent-soft) 40%, transparent);
}
.tile h4 { margin: 0 0 .3rem; font-size: .96rem; display: flex; flex-wrap: wrap; gap: .4rem; align-items: baseline; }
.tile p { margin: 0 0 .35rem; font-size: .9rem; }
.tile-ev { color: var(--ink-soft); font-size: .82rem; margin-bottom: 0 !important; }
/* A core interest should be visibly heavier than a casual one. */
.tile-core { border-color: var(--accent); background: var(--accent-soft); }
.pill-core { border-color: var(--accent); color: var(--accent); }
.pill-casual { opacity: .65; }

/* The trajectory chip: how a thing has moved across the years, beside the
   intensity chip that says how much of it there is. Two questions, two pills —
   folding them into one would make a reader scanning for "do I still do this?"
   parse "how into it am I?" first.

   Dormant and phasic are the ones a reader is most likely to want to argue
   with, so they are the ones that must not be quiet: --danger is the same red
   the missing-source cross uses, which is the right register for "the evidence
   stopped" without being alarming. Fading sits between that and nothing.
   Throughout and Ongoing are deliberately unstyled — the common, unsurprising
   cases should not compete with the heading they sit next to. */
.pill-traj { margin-left: .35rem; }
.pill-traj-dormant, .pill-traj-phasic { border-color: var(--danger); color: var(--danger); }
.pill-traj-declining { opacity: .8; border-style: dashed; }
.pill-traj-rising { border-color: var(--accent); color: var(--accent); }

/* ---------- behaviour facets ---------- */

/* `align-items: start` so each facet is only as tall as its own text. Stretched
   to the row height instead, the left rule on a short facet ran on for a couple
   of hundred pixels past the end of its paragraph — which reads as a rendering
   fault rather than a divider. It only became visible with the fourth facet,
   whose paragraph is much longer than the other three. */
.facet-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  align-items: start; gap: .7rem; margin-bottom: 1.2rem;
}
.facet { border-left: 3px solid var(--accent); padding: .1rem 0 .1rem .8rem; }
.facet-label { font-size: .76rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--ink-soft); }
.facet h4 { margin: .1rem 0 .3rem; font-size: 1rem; color: var(--accent); }
.facet p { margin: 0; font-size: .9rem; }

/* "What you take in" had its own full-width block while it carried a list of
   accounts and a second reading. Those were cut, leaving it the same shape as
   the three beside it, so it is an ordinary .facet now and needs no rules of
   its own. */

/* ---------- mental wellness ---------- */

/* Two columns on a laptop, one on a phone — the same shape as .facet-grid
   above, which this sits directly beneath and reads as a continuation of. */
.wellness-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  gap: 1rem 1.4rem; margin-bottom: 1.2rem;
}
.wellness-facet { border-top: 1px solid var(--line); padding-top: .7rem; }
/* Same treatment as .facet-label above, deliberately as its own class rather
   than by reusing that one: a check counts `.facet-label` across the whole
   report to assert the behaviour grid is exactly four facets, so sharing the
   class would make this section quietly break it. */
.wellness-label {
  font-size: .76rem; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--ink-soft);
}
.wellness-facet p { margin: .35rem 0; }
.wellness-head { display: flex; align-items: baseline; gap: .5rem; flex-wrap: wrap; margin-bottom: .2rem; }
.wellness-confidence { font-size: .8rem; color: var(--ink-soft); margin-top: .4rem; }

/* Deliberately not a red/amber/green ramp. These are descriptions of a
   pattern, not grades, and traffic lights would turn "under strain" into a
   failure and "steady" into a pass — which is exactly the health-verdict
   reading the whole section is built to avoid. "Not enough evidence" is
   styled as the most neutral of the four rather than the worst, because on a
   thin dimension it is the honest answer and should not look like a low
   score. */
.wellness-pill { border: 1px solid var(--line); background: var(--bg); }
.wellness-steady { border-color: var(--accent); color: var(--accent); }
.wellness-mixed { color: var(--ink-soft); }
.wellness-under-strain { border-color: var(--warn); color: var(--warn); }
.wellness-not-enough-evidence { color: var(--ink-soft); opacity: .75; font-style: italic; }

/* Same treatment as .bonus-caveat, and the same reason: fixed app copy the
   reader should meet with the writing rather than only in fine print they
   have already scrolled past. */
.wellness-caveat {
  border-left: 3px solid var(--warn); padding: .1rem 0 .1rem .8rem; margin-top: 1.3rem;
}

/* ---------- career assessment ---------- */

/* Two facets side by side under the edge callout — what is unused and what is
   costing them. Same shape as .wellness-grid and .facet-grid above, since all
   three are "a couple of short reads in a row". */
.career-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  gap: 1rem 1.4rem; margin: 1.2rem 0;
}
/* Its own class rather than reusing .facet-label, for the same reason
   .wellness-label has one: a check counts `.facet-label` across the whole
   report to assert the behaviour grid is exactly four facets. */
.career-label {
  font-size: .76rem; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--ink-soft);
}
.career-facet h4 { margin: .2rem 0 .35rem; }
.career-facet p { margin: 0 0 .5rem; }
/* The edge is the finding this section exists to deliver, so it gets the
   accent border the rest of the card does not. */
.career-edge { border-left: 3px solid var(--accent); }
.career-edge h3 { margin: .2rem 0 .4rem; }

/* The horizon pill sits inline before the action title, so "this week" is
   readable at a glance — the prompt requires at least one action to be
   startable now, and that is only worth requiring if it is visible. */
.career-actions dt { display: flex; align-items: baseline; gap: .5rem; flex-wrap: wrap; }
.horizon-pill { border: 1px solid var(--line); background: var(--bg); font-weight: 600; }
.horizon-this-week { border-color: var(--accent); color: var(--accent); }
.horizon-this-quarter { color: var(--ink-soft); }
.horizon-this-year { color: var(--ink-soft); opacity: .8; }

/* The four paid sections' cover, shown while nothing is unlocked yet. Greyed
   and inert-looking on purpose: it should read as something switched off
   rather than as another card to scroll past. The writing is not in the
   document at all until a real result arrives, because a CSS blur would
   hide nothing from select-all, a screen reader or view-source. */
.premium-cover {
  position: relative; overflow: hidden;
  border: 1px dashed var(--line); border-radius: var(--radius);
  background: repeating-linear-gradient(
    135deg, var(--bg) 0 10px, color-mix(in srgb, var(--ink-soft) 7%, var(--bg)) 10px 20px);
  padding: 1.4rem clamp(1rem, 4vw, 1.8rem); text-align: center;
}
.premium-cover h3 { margin-top: 0; color: var(--ink-soft); }
.premium-cover p { max-width: 54ch; margin-inline: auto; }
.premium-unlock { margin: 0; }
.premium-body h3:first-of-type { margin-top: 0; }

/* The roast's own cover — a consent gate, not a paywall, so it reuses
   .premium-cover's look (same reasoning: greyed, inert, and the writing
   genuinely absent until clicked open) under its own class rather than the
   paid one, so nothing here is mistaken for something behind the S$1.99
   unlock. See roastBlock()/revealRoast()/hideRoast() in docs/app.js. */
.bonus-cover {
  position: relative; overflow: hidden;
  border: 1px dashed var(--line); border-radius: var(--radius);
  background: repeating-linear-gradient(
    135deg, var(--bg) 0 10px, color-mix(in srgb, var(--ink-soft) 7%, var(--bg)) 10px 20px);
  padding: 1.4rem clamp(1rem, 4vw, 1.8rem); text-align: center;
}
.bonus-cover h3 { margin-top: 0; color: var(--ink-soft); }
.bonus-cover p { max-width: 54ch; margin-inline: auto; }
.bonus-reveal { margin: 0; }
.bonus-body h3:first-of-type { margin-top: 0; }
/* Sits above the writing it belongs to, and stays there — it is most needed
   while the section is being read, not on a cover the reader has already
   dismissed. */
.bonus-caveat {
  border-left: 3px solid var(--warn); padding: .1rem 0 .1rem .8rem; margin-bottom: 1.2rem;
}

/* The payment dialog. A labelled field over a status line, the shape any dialog
   for one thing before letting something through. The button element Stripe
   mounts sizes itself to its container, so this just gives it room to sit
   apart from the blurb above it and the cancel row below. #premium-retry
   reuses the mock button's spacing rather than needing its own rule — the
   two are never shown at once. */
#premium-payment-request-button { margin: 1rem 0; min-height: 40px; }
/* Nothing mounted (no wallet on this browser, or mock mode): no empty band under the title. */
#premium-payment-request-button:empty { min-height: 0; margin: .6rem 0 0; }
#premium-mock-pay, #premium-retry { margin: 0 0 1rem; }

/* The card fallback, mounted only when this browser cannot use a wallet (see
   mountCardFallback in app.js). #premium-card-element is where Stripe mounts
   its own iframe, boxed the same way input[type=text] is elsewhere on this
   page so it reads as one more field rather than a foreign widget. */
.premium-card-fallback { margin: 1rem 0; }
.premium-card-label { font-size: .85rem; color: var(--ink-soft); margin: 0 0 .4rem; }
#premium-card-element {
  background: var(--bg-alt); border: 1px solid var(--line); border-radius: 10px;
  padding: .7rem .75rem;
}
.premium-card-error { margin: .5rem 0 0; font-size: .85rem; color: var(--danger); }
#premium-card-pay { margin: .8rem 0 0; }
.premium-status { margin: .5rem 0 0; font-size: .9rem; }
.premium-status.is-bad { color: var(--danger); }
.premium-status.is-good { color: var(--good); }

/* The live seconds count while the paid model call is in flight. Reuses the
   ordinary .progress/.progress-bar.indeterminate pair the upload page's own
   progress bar uses — there is no real percentage to report for a single
   request/response call, only elapsed time, so the bar sweeps rather than
   fills and the number beside it is what actually moves. */
.premium-progress { margin: 1rem 0; }
.premium-progress-time { margin: 0; font-size: .85rem; color: var(--ink-soft); text-align: center; }

/* The promo-code row at the foot of the dialog — a second, independent way
   to authorise the same unlock, for a reader who should not need to pay at
   all rather than a discount applied to a real charge. Sits below the status
   line and above Cancel, set off with a rule so it does not read as part of
   the payment flow above it. */
.premium-promo {
  margin-top: 1.1rem; padding-top: 1rem; border-top: 1px solid var(--line);
}
.premium-promo-label { display: block; font-size: .85rem; color: var(--ink-soft); margin-bottom: .4rem; }
.premium-promo-row { display: flex; gap: .5rem; }
.premium-promo-input { flex: 1; min-width: 0; }
.premium-promo-row .btn { flex: none; margin: 0; }
/* The dialog itself is given `tabindex="-1"` and focused programmatically
   (docs/app.js's openPremiumDialog) so opening it does not land focus on the
   promo input and pull up a keyboard nobody asked for. That focus is parked
   rather than chosen by the reader, so it should not draw an outline either. */
#premium-dialog:focus { outline: none; }



/* ---------- basis picker ---------- */

.mode-dialog {
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--bg-alt); color: var(--ink);
  box-shadow: var(--shadow); padding: clamp(1.1rem, 3vw, 1.6rem);
  width: min(30rem, calc(100vw - 2rem)); max-height: calc(100vh - 2rem);
}
.mode-dialog::backdrop { background: rgba(20, 14, 26, .55); backdrop-filter: blur(2px); }
.mode-dialog h2 { margin-bottom: .2rem; }
.mode-dialog > .muted { font-size: .92rem; margin-bottom: 1rem; }
.mode-dialog .fineprint { margin: .2rem 0 0; }
.mode-dialog .btn-row { margin-bottom: .4rem; }

/* The QR code and its actions need more elbow room side by side than the
   30rem default gives most mode-dialogs. Width only — position is left at
   the UA stylesheet's own `fixed`, which is what actually centres a modal
   dialog on the viewport regardless of where the page was scrolled to.
   Overriding it to `relative` was tried first, on the reasoning that the
   close cross needs a positioning root — but `position: fixed` is already a
   valid containing block for an `absolute` child, so nothing needed the
   override, and it broke the one thing `fixed` was doing: the dialog no
   longer being pinned to the viewport is what left it laid out at its
   in-flow position for anything that measured it while scrolled, which is
   what the reader actually saw as the page jumping under the dialog. */
.compat-dialog { width: min(38rem, calc(100vw - 2rem)); }
.compat-dialog .qr-title { padding-right: 2.2rem; }
.compat-dialog-close {
  position: absolute; top: 14px; right: 14px; width: 32px; height: 32px;
  border-radius: 50%; border: 1px solid var(--line); background: var(--bg);
  color: var(--ink-soft); font-size: .95rem; line-height: 1; cursor: pointer;
}
.compat-dialog-close:hover, .compat-dialog-close:focus-visible {
  background: var(--accent-soft); color: var(--ink); outline: none;
}

.mode-options { display: grid; gap: .55rem; margin-bottom: 1rem; }
/* A row the current entry point does not offer — the welcome page hides
   Facebook. Written out because the `display: flex` below beats the user
   agent's own `[hidden] { display: none }` outright, specificity never
   entering into it: without this the row would be set `hidden`, excluded from
   every list in askDataSources, and still drawn on the screen. The same trap
   .mode-progress[hidden] already carries a note about. */
.mode-option[hidden] { display: none; }
.mode-option {
  display: flex; gap: .8rem; align-items: flex-start; text-align: left;
  width: 100%; font: inherit; color: inherit; cursor: pointer;
  background: var(--bg); border: 1px solid var(--line); border-radius: 12px;
  padding: .8rem .9rem;
  transition: border-color .12s ease, background .12s ease;
}
.mode-option:hover, .mode-option:focus-visible {
  border-color: var(--accent); background: var(--accent-soft); outline: none;
}
.mode-option strong { display: block; margin-bottom: .1rem; }
.mode-option .muted { font-size: .85rem; line-height: 1.4; }
.mode-icon { font-size: 1.4rem; line-height: 1.2; flex: none; }

/* An option that is shown but not yet on sale. The dimming is applied to the
   icon, name and description rather than to the whole button, because the two
   things such a row exists to say — the price and "Coming soon" — are the
   parts that must stay readable. Fading the button as a whole would take them
   down with it: opacity composites the element and its children together, so a
   child cannot be less transparent than its parent. */
.mode-option:disabled { cursor: not-allowed; background: var(--bg-alt); }
.mode-option:disabled:hover, .mode-option:disabled:focus-visible {
  border-color: var(--line); background: var(--bg-alt);
}
.mode-option:disabled .mode-icon,
.mode-option:disabled .mode-title strong,
.mode-option:disabled .muted { opacity: .45; }

.mode-title { display: flex; align-items: baseline; gap: .4rem; flex-wrap: wrap; }
.mode-title strong { margin-bottom: 0; }
.mode-badge {
  font-size: .68rem; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  color: var(--accent); background: var(--accent-soft); white-space: nowrap;
  border: 1px solid var(--line); border-radius: 999px; padding: .1rem .45rem;
}
/* The roast's badge, built on .mode-badge so the two are the same object at
   the same size — a "Bonus" that sat differently from "Premium" would read as
   a different kind of thing rather than the other value of the same one.
   Only the colour changes, to the "good" accent: these two badges make
   opposite promises, and a reader scanning the page should be able to tell
   which is which without reading either. */
.bonus-badge {
  color: var(--good);
  background: var(--good-soft);
}

.mode-price { display: block; margin-top: .35rem; font-size: .85rem; font-weight: 600; }

/* The supplement dialog's one live region: progress while a second archive is
   read, "✓ Added …" once it is, or the error when it will not parse. It sits
   above the buttons rather than replacing the options, because a failed
   supplement leaves the dialog open and both sources still offered. */
.supplement-status {
  margin: 0 0 .6rem; padding: .5rem .7rem; border-radius: 10px;
  font-size: .88rem; line-height: 1.45;
  background: var(--accent-soft); color: var(--ink-soft);
}
.supplement-status.is-good { color: var(--good); background: var(--good-soft); font-weight: 600; }
.supplement-status.is-bad { color: var(--warn); background: var(--warn-soft); }
.mode-option:disabled .mode-icon,
.mode-option:disabled strong { opacity: .45; }

/* A source that has been added, as opposed to one that is merely unavailable.
   It stays `disabled`, because adding the same export twice makes no sense and
   the attribute is what actually stops that — but the greying that comes with
   `disabled` says "you cannot use this", where the truth here is "you already
   did". So the row goes green at full contrast and takes a tick, and this is
   the only thing that says so now that the green summary line underneath is
   gone. `:disabled` in the selector rather than `.is-added` alone so it beats
   the greying rules above on specificity rather than on source order. */
.mode-option.is-added:disabled {
  cursor: default;
  background: var(--good-soft);
  border-color: var(--good);
}
.mode-option.is-added:disabled:hover,
.mode-option.is-added:disabled:focus-visible {
  background: var(--good-soft);
  border-color: var(--good);
}
.mode-option.is-added:disabled .mode-icon,
.mode-option.is-added:disabled .mode-title strong,
.mode-option.is-added:disabled strong,
.mode-option.is-added:disabled .muted { opacity: 1; }

/* The per-source reading bar. Same gradient, radius and easing as the main
   progress bar on the upload page — it is the same event, so it should not look
   like a different one — at the .slim height, because this one sits inside a
   row rather than across the page. The spans need display:block to lay out at
   all, since the button they live in only permits phrasing content. */
.mode-body { min-width: 0; }
.mode-progress { display: block; margin-top: .55rem; }
/* `display:block` above is an author rule and so beats the UA's
   `[hidden]{display:none}` outright — specificity never enters into it. Without
   this line every row sits with an empty track under its description, which is
   exactly what shipped until a screenshot caught it. */
.mode-progress[hidden] { display: none; }
.mode-progress .progress { display: block; margin: 0 0 .3rem; }
.mode-progress .progress-bar { display: block; }
.mode-progress-label { display: block; font-size: .8rem; }

/* setBusy() disables every row while one is reading, but the row doing the
   reading has to stay legible — the greying that says "you cannot use this" is
   wrong for the one source actively in use. Same reasoning and the same
   `:disabled` specificity trick as .is-added below. */
.mode-option.is-loading:disabled {
  cursor: progress;
  background: var(--accent-soft);
  border-color: var(--accent);
}
.mode-option.is-loading:disabled .mode-icon,
.mode-option.is-loading:disabled strong,
.mode-option.is-loading:disabled .muted { opacity: 1; }

/* Pushed to the right edge by the auto margin rather than positioned, so it
   cannot collide with a description that wraps to two lines. */
.mode-added {
  margin-left: auto; align-self: center; flex: none;
  color: var(--good); font-size: 1.2rem; font-weight: 700; line-height: 1;
}

/* The repeated download instructions, collapsed. Everything here is in service
   of one constraint: the dialog is capped at the viewport height, so opening a
   page of instructions must not move the buttons underneath it. The body gets
   its own max-height and scrolls, which keeps the dialog the same height open
   or shut, and the type is stepped down a notch because this is reference
   material a reader consults rather than prose they read through. */
.supplement-help { margin: 0 0 .9rem; border-top: 1px solid var(--line); padding-top: .7rem; }
.supplement-help > summary {
  display: list-item; list-style: none; cursor: pointer;
  font-size: .88rem; font-weight: 650; color: var(--accent);
  padding-right: 1.2rem; position: relative;
}
.supplement-help > summary::-webkit-details-marker { display: none; }
.supplement-help > summary:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 6px;
}
.supplement-help > summary::after {
  content: ''; position: absolute; right: .2rem; top: .35em;
  width: .4rem; height: .4rem;
  border-right: 2px solid var(--accent); border-bottom: 2px solid var(--accent);
  transform: rotate(45deg); transition: transform .15s ease;
}
.supplement-help[open] > summary::after { transform: rotate(-135deg); }

.supplement-help-body {
  max-height: 11rem; overflow-y: auto; margin-top: .6rem;
  padding: .1rem .7rem .1rem .1rem;
  font-size: .8rem; line-height: 1.5; color: var(--ink-soft);
}
.supplement-help-body h3 { font-size: .82rem; margin: .7rem 0 .3rem; color: var(--ink); }
.supplement-help-body h3:first-child { margin-top: 0; }
.supplement-help-body ol { margin: 0; padding-left: 1.1rem; }
.supplement-help-body li { margin-bottom: .3rem; }
.supplement-help-body li:last-child { margin-bottom: 0; }
.supplement-help-body .ui-label { font-size: .78rem; }

/* Back sits left, the forward action right. The spacer rather than
   justify-content: space-between because the row holds three children and only
   two are ever visible at once — space-between would centre the odd one out
   and shift the forward button sideways as Skip gives way to Continue. */
.supplement-actions { align-items: center; }
.supplement-actions .btn-spacer { flex: 1 1 auto; }

/* ---------- pre-send review ---------- */

/* A fixed, modest card rather than one that grows to fit its content: 30rem
   keeps the dialog itself compact on any screen, including a short one,
   which is also what "make it fit mobile better" turns out to mean here.
   `overflow: hidden` is what stops the dialog element getting a scrollbar of
   its own — a <dialog> shown with showModal() gets `overflow: auto` from the
   browser's own stylesheet by default, and with a scrollable list already
   inside it, that produced two nested scrollbars competing for the same
   gesture. Flex column, so the title, subtitle and buttons keep their
   natural size and the list absorbs whatever height is left over. */
.review-dialog[open] { display: flex; flex-direction: column; max-height: min(42rem, calc(100vh - 2rem)); overflow: hidden; }
/* `min-height: 0` is the part that actually makes the list shrink: a flex
   child defaults to never sizing below its content's natural height no
   matter what its parent allows, which is exactly the outer-scrollbar bug
   above, moved one level down. With it unset, the fixed 30rem card and a
   full digest's worth of rows means this scrolls on every ordinary screen,
   not just a short one — which is the point, since the alternative is the
   card growing to match its content instead. */
.review-list { display: flex; flex-direction: column; gap: .1rem; margin-bottom: 1rem; flex: 1 1 auto; min-height: min(14rem, 35vh); overflow-y: auto; }
/* Every row here is a real checkbox — .switch-row is the exact control the
   main page used to show for direct messages and photos, reused for all
   seven rows so the list reads as one control rather than a description with
   two toggles glued to the end of it. */
.review-list .switch-row { margin: 0; padding: .55rem 0; border-bottom: 1px solid var(--line); }
/* The download link is the list's real last child now, not Photos — the
   border this removes would otherwise sit under a link with nothing below
   it, and the border above the link is what actually separates it from
   Photos. Centred and given breathing room so it reads as a small utility
   at the foot of the list rather than an eighth row trying to pass as one
   of the seven checkboxes above it. */
.review-list .switch-row:last-child { border-bottom: 0; }
.review-download-row { margin: 0; padding: .8rem 0 .3rem; text-align: center; }

/* ---------- print / PDF export ---------- */
/*
 * Export is the browser's own print-to-PDF, so this stylesheet *is* the PDF.
 * Two things drive every rule below.
 *
 * Backgrounds do not print unless the reader ticks a box, so nothing may
 * depend on a fill: every accent is a text colour or a border, both of which
 * always print. And a section split across a page break is the thing that
 * makes a report look thrown together, so cards are unbreakable and the
 * typography is tightened until they fit inside one page.
 */

.print-only { display: none; }

@media print {
  .no-print, .nav, .footer, .btn, .btn-row, .cta-row, #server-status,
  .screen-only, .card-chevron { display: none !important; }
  .print-only { display: block; }

  :root {
    --bg: #fff; --bg-alt: #fff; --ink: #14101a; --ink-soft: #4a4453; --line: #cfc7d6;
    --accent: #5b2d78; --accent-2: #5b2d78; --accent-soft: #fff;
    --shadow: none;
  }

  body { background: #fff; color: var(--ink); font-size: 9.6pt; line-height: 1.4; }
  .container { max-width: none; padding: 0; }
  .view[hidden] { display: none !important; }
  a { color: inherit; text-decoration: none; }
  a[href]::after { content: ""; }

  /* ---- letterhead ---- */

  .letterhead { margin: 0 0 10mm; break-after: avoid; }
  .letterhead-brand { display: flex; align-items: center; gap: .45rem; color: var(--accent); }
  .letterhead-mark { width: 26px; height: 26px; flex: none; }
  .letterhead-word { font-size: 13pt; font-weight: 700; letter-spacing: -.01em; }
  .letterhead-rule { flex: 1; height: 1px; background: var(--accent); }
  .letterhead-kind { font-weight: 700; letter-spacing: .16em; text-transform: uppercase; }
  .letterhead-name { margin: 5mm 0 1mm; letter-spacing: -.02em; }
  .letterhead-meta { color: var(--ink-soft); margin: 0; }

  /* ---- sections ---- */
  /*
   * One section, one block. break-inside is honoured only when the block can
   * actually fit on a page, so the padding and type sizes above exist to keep
   * every card comfortably under that ceiling — the UI suite measures them.
   */

  /*
   * Sections flow and pack: two short ones share a page rather than each
   * taking a sheet of its own. Only the items inside them are unbreakable
   * (further down), so a break lands in a gap rather than through a thought.
   */
  .card {
    box-shadow: none; border: 0; border-top: 1.5pt solid var(--accent);
    border-radius: 0; padding: 3.5mm 0 5mm; margin: 0 0 4mm;
  }
  .card:last-of-type { border-bottom: 0; }
  .section-card::before { display: none; }
  .qr-panel { margin-top: 4mm; }
  /* The compatibility QR code is a popout on screen now, closed by default —
     but Ctrl+P should still carry it the way it always did, so printing
     forces it back into the page rather than leaving a gap where it used to
     be. The UA stylesheet hides a <dialog> without [open] at the element
     level; #compat-dialog here outranks that on specificity alone, but
     !important keeps it that way regardless of load order. Restyled with the
     same divider .card gave every other printed section, since .card itself
     was dropped from this element on screen to avoid boxing it twice inside
     the dialog's own border. */
  #compat-dialog {
    display: block !important; position: static; width: auto; max-height: none;
    box-shadow: none; border: 0; border-top: 1.5pt solid var(--accent);
    border-radius: 0; padding: 3.5mm 0 5mm; margin: 0 0 4mm; background: transparent;
    color: inherit;
  }
  .compat-dialog-close { display: none; }

  .card-head { gap: .5rem; margin-bottom: 3mm; align-items: center; }
  /* The glyph is an emoji on a tinted tile. The tint is what showed up as a
     pale marking at the top left of every heading whenever the reader had
     "Background graphics" ticked, and the emoji itself is a colour bitmap
     that smears at this size. The heading carries the section on paper. */
  .card-icon { display: none; }
  .card-head h2 { color: var(--accent); letter-spacing: -.01em; }

  h1, h2, h3, h4, .card-head { break-after: avoid; page-break-after: avoid; }
  /* Hierarchy comes from weight, case and colour rather than from size, so
     the body of the document is set at one size throughout. */
  h3 { text-transform: uppercase; letter-spacing: .09em; color: var(--ink-soft); margin-top: 4mm; }
  h3.h-good, h3.h-warn { color: var(--ink-soft); }
  p, li, dd { orphans: 3; widows: 3; }

  #view-profile, #view-profile * { font-size: 10pt; }
  #view-profile .letterhead-name { font-size: 24pt; }
  #view-profile .letterhead-word { font-size: 13pt; }
  #view-profile .card-head h2, #view-profile .qr-title { font-size: 14pt; }
  #view-profile .essence-noun { font-size: 14pt; }
  /* Not type — a glyph and a letter tile, sized to their boxes. */
  #view-profile .axis-letter { font-size: 11pt; }
  #view-profile .essence-icon { font-size: 15pt; }
  #view-profile .love-icon { font-size: 12pt; }

  /* ---- components ---- */

  .essence { background: none; border: 0; border-left: 2pt solid var(--accent); border-radius: 0; padding: 0 0 0 4mm; margin-bottom: 4mm; }
  .essence-noun { -webkit-text-fill-color: currentColor; color: var(--accent); background: none; font-size: 17pt; }
  .accent { -webkit-text-fill-color: currentColor; color: var(--accent); background: none; }

  .glance { border-color: var(--line); padding: 2.5mm 0; margin-bottom: 4mm; }

  .bar, .confidence-meter { border: .5pt solid var(--line); background: none; height: 6px; }
  .bar-fill, .confidence-fill { background: var(--accent) !important; }
  .trait-row { margin-bottom: .3rem; }

  .axis-letter { border: 1pt solid var(--accent); background: none; color: var(--accent); width: 2rem; height: 2rem; font-size: 1rem; }
  .axis { grid-template-columns: 2rem 1fr; }

  .ev, .pill { border: .5pt solid var(--line); background: none; }
  .tile, .callout, .love-row {
    background: none; border: .5pt solid var(--line);
  }
  .love-row { border-left-width: 2pt; border-left-color: var(--accent); }
  .love-minor { border-left-color: var(--line); opacity: 1; }

  /*
   * A long section cannot always fit on one page — a Big Five with real
   * evidence runs past it however tight the type is. When a break is
   * unavoidable it has to land between whole items rather than through one,
   * so every item that reads as a single thought is atomic here: a trait with
   * its bar and evidence, an MBTI axis, a tile, a facet, a love language, and
   * a term with its definition.
   */
  .trait-block, .axis, .tile, .facet, .callout, .love-row, .essence, .glance {
    break-inside: avoid; page-break-inside: avoid;
  }
  .points dt { break-after: avoid; page-break-after: avoid; }
  .points dd { break-before: avoid; page-break-before: avoid; }
  .love-blurb, .card-sub { break-after: avoid; page-break-after: avoid; }

  /* ---- the code ---- */

  .qr-panel { grid-template-columns: auto 1fr; gap: 0 6mm; align-items: center; break-inside: avoid; }
  .qr-title { grid-column: 1 / -1; }
  #qr-canvas { width: 150px; max-width: none; }
}

/* A4 with a generous outer margin, so the letterhead has room to breathe and
   nothing sits against the trim. */
@page { size: A4; margin: 16mm 15mm 14mm; }

/* ---------- responsive ---------- */

@media (max-width: 620px) {
  .qr-panel { grid-template-columns: 1fr; gap: .9rem; }
  .qr-holder { justify-self: center; }
  .qr-title { margin-bottom: 0; }
  .trait-row { grid-template-columns: 1fr 2.5rem; grid-template-areas: "label num" "bar bar"; }
  .trait-label { grid-area: label; }
  .trait-num { grid-area: num; }
  .bar { grid-area: bar; }
  .grid-row { grid-template-columns: 1fr; }
  .facts { grid-template-columns: 1fr; }
  .facts dd { margin-bottom: .5rem; }
  .split { grid-template-columns: 1fr; gap: .2rem; }
  .tile-grid, .facet-grid { grid-template-columns: 1fr; }
}

/* ---------- the psyche card ---------- */
/*
   Laid out at a fixed 880x1320 and scaled to fit by app.js. Everything in here
   is therefore in card pixels, not screen pixels: a font-size of 30 means 30 at
   scale 1, and the whole thing shrinks together. That is the point — a card
   that reflowed would fit every screen and look composed on none, and the
   requirement is one screen with no scrolling on a phone and a laptop alike.
*/
.psyche-card-slot {
  display: block; width: 100%; padding: 0; margin: 0 0 1.1rem; border: 0;
  background: none; cursor: zoom-in; font: inherit; color: inherit;
}
/* Belt and braces against the 880px card escaping its column: the frame's real
   size is set in JS from the measured width, but a frame that has not been
   measured yet must clip rather than push the page sideways. */
.psyche-card-frame { margin: 0 auto; overflow: hidden; max-width: 100%; width: 100%; }
.psyche-card {
  width: 1000px; transform-origin: top left;
  display: flex; flex-direction: column; gap: 22px;
  padding: 54px 56px; border-radius: 34px;
  background: var(--bg); color: var(--ink);
  box-shadow: 0 2px 3px rgba(36, 26, 46, .05), 0 18px 50px rgba(36, 26, 46, .10);
}
.psyche-card-hint {
  display: block; text-align: center; margin-top: .5rem;
  font-size: .82rem; color: var(--ink-soft);
}
.psyche-card-slot:hover .psyche-card-hint,
.psyche-card-slot:focus-visible .psyche-card-hint { color: var(--accent); }

.pc-hero {
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  border-radius: 26px; padding: 38px 40px; color: #fff;
}
.pc-kicker {
  font-size: 25px; letter-spacing: .18em; text-transform: uppercase;
  opacity: .85; margin: 0 0 10px;
}
.pc-name { display: flex; align-items: center; gap: 14px; }
.pc-icon { font-size: 52px; line-height: 1; flex: none; }
.pc-name h2 {
  font-size: 54px; font-weight: 800; letter-spacing: -.025em; line-height: 1.02;
  margin: 0; color: #fff;
}
.pc-blurb { font-size: 25px; line-height: 1.48; margin: 16px 0 0; opacity: .96; }

.pc-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
.pc-stat {
  background: var(--bg-alt); border: 1px solid var(--line); border-radius: 20px;
  padding: 22px 24px; min-width: 0;
}
.pc-lab {
  display: flex; align-items: center; gap: 10px;
  font-size: 21px; letter-spacing: .18em; text-transform: uppercase;
  color: var(--ink-soft); margin: 0 0 12px; font-weight: 600;
}
/* The label text is a flex item, which defaults to a content-based minimum
   width and refuses to shrink below it — so "ENNEAGRAM" at this uppercase
   letter-spacing simply overflowed the box rather than wrapping, on a narrow
   card's three-column stat row. Same fix as .pc-trait-label: let it shrink
   and break instead. */
.pc-lab-text { min-width: 0; overflow-wrap: break-word; }
.pc-big { font-size: 46px; font-weight: 800; letter-spacing: -.01em; margin: 0; }
.pc-sub { font-size: 24px; color: var(--ink-soft); margin: 6px 0 0; }
/* The type's own one-line definition, under the nickname — smaller and
   lighter than .pc-sub since it is a full sentence rather than a label, and
   likely to wrap. */
.pc-desc { font-size: 20px; line-height: 1.35; color: var(--ink-soft); margin: 8px 0 0; }
/* Two columns rather than one row of four: "moderate" is the longest of the
   three strength words, and a reading with three or four of them in a row
   (not rare — most people are not extreme on every axis) pushed the row
   wider than the card and spilled past its right edge. Two per row halves
   the worst case. min-width: 0 is still needed alongside it — a flex or grid
   item defaults to a content-based minimum width, which "moderate" alone
   already exceeds at this column's share of the card, and that default wins
   over the grid track unless it is overridden. */
.pc-letters { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px 16px; margin-top: 4px; }
.pc-letter { display: flex; flex-direction: column; align-items: center; min-width: 0; }
.pc-letter b { font-size: 40px; font-weight: 800; letter-spacing: -.01em; }
.pc-letter i {
  font-style: normal; font-size: 17px; color: var(--ink-soft);
  letter-spacing: .04em; margin-top: 2px;
}
/* The Big Five block is stretched to match whichever of the three top stats
   is tallest (grid rows default to align-items: stretch), which used to
   leave four cramped rows sitting at the top of a box with empty space
   below them. Its own column, so the fixed height can actually be used: the
   label stays pinned at the top, and the row list is a second flex column
   with flex: 1 to claim the rest of the box, spreading its rows across it
   with space-between rather than leaving them bunched under the label. */
.pc-stat-bigfive { display: flex; flex-direction: column; }
.pc-trait-list { flex: 1; display: flex; flex-direction: column; justify-content: space-between; }
/* gap, not just space-between within the row: a long trait name that wraps
   would otherwise run straight into its own score. Slightly smaller than the
   other stat blocks' body text so "Openness to experience" has a better
   chance of fitting one line, and a bullet stands in for the icon each other
   list on the card already gets, which this row never had one of. */
.pc-trait {
  font-size: 24px; margin: 0; display: flex; gap: 14px;
  justify-content: space-between; align-items: baseline; line-height: 1.3;
}
/* A flex item's default min-width is its content's own width, not 0 — so
   without this, a long trait label could refuse to shrink below its own full
   width and overflow the card rather than wrapping. The bullet lives on this
   span rather than on .pc-trait itself, so it travels with the label as one
   flex item instead of becoming a third item that space-between would push
   away from it.
   min-width: 0 alone only lets the box shrink — it does not let one-word
   labels like "Conscientiousness" or "Agreeableness" break. With no space to
   wrap at, normal text wrapping leaves the word intact and it overflows
   straight past its own box into the score sitting beside it, which is
   exactly what the narrow card's three-column stat row does at its width.
   overflow-wrap lets the browser break inside the word once it has to. */
.pc-trait-label { min-width: 0; overflow-wrap: break-word; }
.pc-trait-label::before { content: '•'; color: var(--accent); font-weight: 700; margin-right: 10px; }
.pc-trait b { font-variant-numeric: tabular-nums; }

.pc-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
.pc-row-2 { grid-template-columns: 1fr 1fr; }
/* Applied by fitCard when the screen is much taller than it is wide. Padding is
   the only thing here that can be cut without losing content, and on a narrow
   screen the card is height-bound, so every pixel of it comes straight back as
   a larger scale factor and bigger type. */
.psyche-card.pc-narrow { padding: 34px 32px; gap: 14px; }
.psyche-card.pc-narrow .pc-row-2 { grid-template-columns: 1fr; }
/* Except the love pair, which is read across rather than down — stacking it
   loses the giving-against-receiving comparison the row exists to make. */
.psyche-card.pc-narrow .pc-love-row { grid-template-columns: 1fr 1fr; }
.psyche-card.pc-narrow .pc-hero { padding: 26px 28px; }
.psyche-card.pc-narrow .pc-stat,
.psyche-card.pc-narrow .pc-col,
.psyche-card.pc-narrow .pc-half { padding: 15px 17px; }
.psyche-card.pc-narrow .pc-lab { margin-bottom: 8px; }
/* Three equal columns on a narrow card leave each stat label about a sixth
   of the card's width. "Enneagram" is one word and, at the label's usual
   letter-spacing, needs more than that — .pc-lab-text's overflow-wrap keeps
   it from spilling into the box beside it, but breaking wherever the browser
   likes reads worse than the labels beside it. Tightening the spacing here
   is enough for it to fit on its own line instead. */
.psyche-card.pc-narrow .pc-stats .pc-lab { font-size: 16px; letter-spacing: .06em; }
/* "Agreeableness" is one unbreakable word, so at the narrow card's column width
   it pushed its own score past the card edge and the frame clipped it. Smaller
   type in this mode only, and grid children allowed to shrink below their
   min-content width, which they do not do by default. */
.psyche-card.pc-narrow .pc-trait { font-size: 23px; gap: 8px; }
.psyche-card.pc-narrow .pc-desc { font-size: 17px; margin-top: 6px; }
/* Four strength words across one narrow column: "moderate" twice will not fit
   at full size, and the last one ran into the block beside it. */
/* Big Five stays in the same row as type and Enneagram on a narrow card too —
   dropping to two columns left it orphaned alone at half width on its own
   row, which read as broken rather than intentional. Three narrow columns
   squeeze the TYPE block's letter pair, so the letters stack one per row
   below instead of 2x2: the box grows taller to hold them rather than the
   row losing a column to make them wider. */
.psyche-card.pc-narrow .pc-letters {
  grid-template-columns: 1fr; gap: 4px;
}
.psyche-card.pc-narrow .pc-letter b { font-size: 34px; }
.psyche-card.pc-narrow .pc-letter i { font-size: 13px; }
.psyche-card.pc-narrow .pc-lab-icon { font-size: 20px; }
.psyche-card.pc-narrow .pc-blurb { margin-top: 10px; }
/* The hero is the tightest block on a narrow card, and the character's name is
   the largest thing in it — so it comes down another step there rather than
   the paragraph beneath it losing sentences. */
.psyche-card.pc-narrow .pc-icon { font-size: 42px; }
.psyche-card.pc-narrow .pc-name h2 { font-size: 44px; }
.psyche-card.pc-narrow .pc-kicker { font-size: 22px; }
.psyche-card.pc-narrow .pc-list li { margin-bottom: 2px; }
.pc-col, .pc-half {
  background: var(--bg-alt); border: 1px solid var(--line); border-radius: 20px;
  padding: 20px 22px;
}
.pc-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.pc-chip {
  background: var(--accent-soft); color: var(--accent); border-radius: 12px;
  padding: 10px 17px; font-size: 24px; font-weight: 500; line-height: 1.2;
}
.pc-list { margin: 0; padding-left: 22px; }
.pc-list li { font-size: 27px; line-height: 1.4; margin-bottom: 7px; }
.pc-foot {
  margin: 4px 0 0; text-align: center; font-size: 20px;
  letter-spacing: .3em; text-transform: uppercase; color: var(--ink-soft);
}

/* Full screen. The dialog is the backdrop, so a click landing on it rather
   than on the card counts as a click outside and closes. */
.card-dialog {
  width: 100vw; max-width: 100vw; height: 100dvh; max-height: 100dvh;
  padding: 0; border: 0; background: rgba(20, 14, 28, .92);
  display: none; place-items: center; overflow: hidden;
}
/* Two rows: the card centred in whatever is left, and the bar in normal flow
   beneath it. The bar used to be position:fixed, which floated it over the
   card's last row — reserving height for it does not help while the card is
   centred in the whole viewport. */
.card-dialog[open] { display: grid; grid-template-rows: 1fr auto; }
.card-dialog::backdrop { background: rgba(20, 14, 28, .92); }
.card-dialog-frame { overflow: hidden; place-self: center; }
/* Bottom right, on the same line as the download and share buttons rather
   than diagonally opposite them in the top corner.
 *
 * Positioned against the bar rather than the viewport, which is what stops it
 * colliding with those two: the actions row is centred, and on a narrow phone
 * a pair of pill buttons centred in the full width reaches close enough to the
 * right edge to run underneath a viewport-fixed cross. Making the bar the
 * containing block and reserving a matching gutter on the actions row means
 * the layout guarantees the gap instead of it being a coincidence of widths.
 *
 * Vertically centred on the actions row, not on the bar: the bar also holds
 * the status line, which appears only after a download or a share, and
 * centring on the whole bar would make the cross jump the moment that text
 * arrived. */
/* Full width, not shrink-wrapped. The dialog sets `place-items: center`,
   which centres *and* shrinks both of its grid rows — fine for the card in the
   row above, wrong for the bar, whose close button is supposed to reach the
   corner of the screen. Without this the cross landed a third of the way in
   from the right on a laptop and only looked correct on a phone, where the
   content happened to fill the width anyway. */
.card-dialog-bar { position: relative; justify-self: stretch; }
.card-dialog-close {
  position: absolute; right: 14px; top: 14px; z-index: 2; width: 44px; height: 44px;
  /* Nudged up by half the difference between it and the pill buttons beside
     it, so the two sit on one optical line rather than the cross hanging low. */
  margin-top: -2px;
  border-radius: 50%; border: 1px solid rgba(255, 255, 255, .3);
  background: rgba(255, 255, 255, .12); color: #fff; font-size: 1.1rem;
  cursor: pointer; line-height: 1;
}
.card-dialog-close:hover, .card-dialog-close:focus-visible {
  background: rgba(255, 255, 255, .24); outline: none;
}
/* The sample card's full screen view has no bar, so its cross sits in the
   screen's own top right corner, faint until it is pointed at. */
.sample-card-close {
  position: fixed; top: max(12px, env(safe-area-inset-top)); right: max(12px, env(safe-area-inset-right)); z-index: 3;
  width: 40px; height: 40px; border-radius: 50%; border: 1px solid rgba(255, 255, 255, .28);
  background: rgba(255, 255, 255, .1); color: rgba(255, 255, 255, .78); font-size: 1rem; line-height: 1; cursor: pointer;
}
.sample-card-close:hover, .sample-card-close:focus-visible { background: rgba(255, 255, 255, .24); color: #fff; outline: none; }

.pc-lab-icon { font-size: 24px; line-height: 1; flex: none; }
.pc-love { list-style: none; padding-left: 0; }
.pc-love li { display: flex; align-items: center; gap: 10px; }
.pc-love-icon { font-size: 26px; line-height: 1; flex: none; }

/* The card's masthead: the lockup on the left, whose card it is in the
   middle, how confident the reading is on the right. Three fixed grid
   columns rather than a two-child flex row, so each slot is pinned to its
   own column and stays put whether or not the other two are present —
   the owner name and the confidence badge vary in length independently, and
   neither should be able to push the brand mark off the corner. */
.pc-top { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 16px; }
.pc-brand {
  grid-column: 1; justify-self: start;
  display: flex; align-items: center; gap: 12px; color: var(--accent);
}
.pc-brand-mark { width: 40px; height: 40px; flex: none; }
.pc-brand span { font-size: 27px; font-weight: 700; letter-spacing: -.01em; color: var(--ink); }
.pc-owner {
  grid-column: 2; justify-self: center;
  font-size: 24px; font-weight: 600; color: var(--ink-soft);
  letter-spacing: .02em; text-align: center; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pc-confidence {
  grid-column: 3; justify-self: end;
  display: flex; align-items: baseline; gap: 6px;
  font-size: 24px; font-weight: 700; color: var(--ink); white-space: nowrap;
}
.pc-confidence-icon { font-size: 22px; line-height: 1; }
.pc-confidence-max { font-size: 16px; font-weight: 500; color: var(--ink-soft); }
.psyche-card.pc-narrow .pc-brand-mark { width: 32px; height: 32px; }
.psyche-card.pc-narrow .pc-brand span { font-size: 23px; }
.psyche-card.pc-narrow .pc-owner { font-size: 20px; }
.psyche-card.pc-narrow .pc-confidence { font-size: 20px; }
.psyche-card.pc-narrow .pc-confidence-icon { font-size: 18px; }
.psyche-card.pc-narrow .pc-confidence-max { font-size: 14px; }

/* The section the card now lives in on the profile page. Every other
   .section-card spans the full container because its content — paragraphs,
   trait bars — actually wants that width. This one does not: the preview is
   capped at PREVIEW_MAX_H and stops growing once it is tall enough, so on a
   wide screen the frame stalls out narrow while the box around it stays full
   width, leaving a slab of empty white down each side. width: fit-content
   makes the box hug whichever child is widest — the frame, ordinarily — and
   margin: auto centres what is left, so the fix is one box sizing rule rather
   than a breakpoint to maintain. On a phone the frame already runs close to
   the full slot width, so fit-content lands on essentially the same box the
   old full-width rule produced — nothing to guard with a media query. */
#psyche-card-section { width: fit-content; max-width: 100%; margin-left: auto; margin-right: auto; }

/* Download and share, pinned under the card in the full-screen view. Download
   sits left and share sits right — the order a reader meets them left to
   right — with a visible gap between them rather than the pair being spread
   to the edges of a shared row, so each reads as its own control. The status
   line below is shared by both: neither carries an error-specific label of
   its own, so a failure from either one shows up there instead of inside
   the button. */
.card-dialog-bar {
  display: flex; flex-direction: column; align-items: center; gap: 12px;
  padding: 14px 0 22px;
}
.card-dialog-actions {
  display: flex; justify-content: center; align-items: center;
  gap: 20px; width: 100%;
  /* Symmetric, and wide enough on the right for the close button that now
     sits there — 44px of button plus its 14px offset, rounded up. Applied to
     both sides so the pair stays visually centred in the row rather than
     drifting left by the width of the gutter. */
  padding: 0 68px;
}
.card-dialog-bar .btn { box-shadow: 0 6px 24px rgba(0, 0, 0, .35); }
/* Icon plus a small label, in a pill rather than a plain circle — a bare
   glyph with no text reads as unfinished, and the label is what actually
   tells a reader what each button does before they tap it. */
.btn-icon-label {
  display: inline-flex; align-items: center; gap: 8px;
  padding: .68rem 1.3rem .68rem 1.1rem; border-radius: 999px;
  font-size: .92rem; font-weight: 600; letter-spacing: .01em;
}
.btn-icon-glyph { font-size: 1.15rem; line-height: 1; }
.card-dialog-status { margin: 0; color: #fff; font-size: .85rem; text-align: center; }

/* ---------- the report-by-mail dialog ---------- */

/* ---------- the structured report layout ----------
 *
 * Only drawn when the server writes the structured layout (or the page is
 * opened with ?layout=structured). Every class here is new, so the classic
 * report is untouched by this block. */

.report-part {
  margin: 2.4rem 0 .9rem;
  padding-top: 1rem;
  border-top: 2px solid var(--accent);
}
.report-part:first-child { margin-top: .4rem; }
.part-label {
  display: block;
  font-size: .72rem; font-weight: 700; letter-spacing: .12em; text-transform: uppercase;
  color: var(--accent);
}
.part-title { margin: .15rem 0 .2rem; font-size: 1.45rem; line-height: 1.25; }
.part-intro { margin: 0; color: var(--ink-soft); font-size: .95rem; }

.section-meta { margin: 0 0 .6rem; color: var(--ink-soft); font-size: .85rem; }
.subsection { margin-top: 1.6rem; padding-top: 1rem; border-top: 1px solid var(--line); }

.about-list dt { font-weight: 650; margin-top: .8rem; }
.about-list dt:first-child { margin-top: 0; }
.about-list dd { margin: .15rem 0 0; color: var(--ink-soft); }

/* Signature patterns, and the chips that point back at them. */
.pattern-list { display: grid; gap: .9rem; }
.pattern {
  display: grid; grid-template-columns: 2.1rem 1fr; gap: .8rem;
  padding: .9rem 1rem; border: 1px solid var(--line); border-radius: 12px; background: var(--bg);
  transition: box-shadow .3s ease, border-color .3s ease;
}
.pattern.is-highlighted { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.pattern h3 { margin: 0 0 .25rem; font-size: 1.05rem; }
.pattern p { margin: 0 0 .5rem; }
.pattern-badge, .pattern-dot {
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 999px; background: var(--accent); color: var(--bg-alt); font-weight: 700;
}
.pattern-badge { width: 2.1rem; height: 2.1rem; font-size: 1rem; }
.pattern-dot { width: 1.15rem; height: 1.15rem; font-size: .68rem; margin-right: .35rem; flex: none; }
.pattern-where, .origin, .motive-top { display: flex; flex-wrap: wrap; align-items: center; gap: .35rem; margin: .4rem 0 0; }
.connects-label {
  font-size: .7rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink-soft); margin-right: .25rem;
}
p.connects-label { margin: .8rem 0 .2rem; }
.section-chip {
  font-size: .78rem; padding: .1rem .55rem; border-radius: 999px;
  border: 1px solid var(--line); color: var(--ink-soft);
}

/* Big Five on a spectrum: both poles named, the typical band shaded. */
.scale-legend { display: flex; flex-wrap: wrap; align-items: center; gap: .45rem; font-size: .8rem; color: var(--ink-soft); margin: 0 0 1rem; }
.legend-band { width: 1.6rem; height: .7rem; border-radius: 3px; background: var(--accent-soft); border: 1px solid var(--line); }
.legend-marker { width: .7rem; height: .7rem; border-radius: 999px; background: var(--accent); margin-left: .6rem; }
.bipolar { margin-bottom: 1.4rem; }
.bipolar-head { display: flex; justify-content: space-between; align-items: baseline; gap: .6rem; }
.bipolar-track {
  position: relative; height: 12px; margin: .45rem 0 .3rem;
  border-radius: 999px; background: var(--bg); border: 1px solid var(--line);
}
.bipolar-band { position: absolute; top: 0; bottom: 0; background: var(--accent-soft); }
.bipolar-mid { position: absolute; left: 50%; top: -3px; bottom: -3px; width: 1px; background: var(--line); }
.bipolar-marker {
  position: absolute; top: 50%; width: 16px; height: 16px; margin: -8px 0 0 -8px;
  border-radius: 999px; background: linear-gradient(135deg, var(--accent), var(--accent-2));
  border: 2px solid var(--bg-alt); box-shadow: 0 1px 3px rgba(36, 26, 46, .3);
}
.bipolar-poles { display: flex; justify-content: space-between; gap: 1rem; font-size: .76rem; color: var(--ink-soft); }
.bipolar-poles span:last-child { text-align: right; }

/* Motivators: Schwartz's ten, grouped by his four higher-order values. */
/* Four tints of the one results colour, darkest to lightest around the
   circle — not green, amber or red, which mean something else here. */
:root {
  --motive-openness: #b78ad6;
  --motive-enhancement: #9a5fc2;
  --motive-conservation: #6a3290;
  --motive-transcendence: #4a1f68;
}
@media (prefers-color-scheme: dark) {
  :root {
    --motive-openness: #e2cdf3;
    --motive-enhancement: #c79bec;
    --motive-conservation: #a36dd0;
    --motive-transcendence: #7d48a8;
  }
}
.motive-chart { display: grid; gap: 1rem; margin: .9rem 0; }
.motive-group { border-left: 3px solid var(--motive-colour, var(--accent)); padding-left: .8rem; }
.motive-openness { --motive-colour: var(--motive-openness); }
.motive-enhancement { --motive-colour: var(--motive-enhancement); }
.motive-conservation { --motive-colour: var(--motive-conservation); }
.motive-transcendence { --motive-colour: var(--motive-transcendence); }
.motive-group-name {
  margin: 0 0 .35rem; font-size: .72rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: var(--motive-colour);
}
.motive-row {
  display: grid; grid-template-columns: minmax(9rem, 2fr) 3fr 2.3rem; gap: .2rem .7rem; align-items: center;
  margin-bottom: .55rem;
}
.motive-label { font-size: .88rem; font-weight: 600; line-height: 1.25; }
.motive-label small { display: block; font-weight: 400; color: var(--ink-soft); font-size: .74rem; }
.motive-bar .bar-fill { background: var(--motive-colour); }
.motive-line { grid-column: 1 / -1; margin: 0; font-size: .82rem; color: var(--ink-soft); }
@media (max-width: 460px) {
  .motive-row { grid-template-columns: 1fr 2.3rem; }
  .motive-row .motive-bar { grid-column: 1 / -1; grid-row: 2; }
}

/* Development plan and pressure points. */
.dev-list, .pressure-list { display: grid; gap: .8rem; margin-bottom: 1rem; }
.dev-item, .pressure-item { padding: .85rem 1rem; border: 1px solid var(--line); border-radius: 12px; background: var(--bg); }
.dev-build { border-left: 3px solid var(--good); }
.dev-develop { border-left: 3px solid var(--warn); }
.dev-item h4, .pressure-item h4 { margin: 0 0 .3rem; }
.dev-item p, .pressure-item p { margin: .3rem 0; }
.dev-actions { list-style: none; padding: 0; margin: .6rem 0; display: grid; gap: .4rem; }
.dev-actions li { display: flex; gap: .55rem; align-items: baseline; }
.dev-actions .pill { flex: none; }
.reflect { font-style: italic; }
.reflect .connects-label { font-style: normal; }
.pressure-head { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: .5rem; }
.pressure-arrow {
  font-weight: 400; color: var(--ink-soft); font-size: .82rem; margin: 0 .45rem;
}
.pressure-arrow::before { content: '→ '; }
.pressure-level { display: inline-flex; align-items: center; gap: 3px; }
.pressure-step { width: 18px; height: 7px; border-radius: 2px; background: var(--line); }
.pressure-step.is-on { background: var(--warn); }
.pressure-word { margin-left: .4rem; font-size: .76rem; font-weight: 600; color: var(--warn); }

.method-list { display: grid; grid-template-columns: max-content 1fr; gap: .3rem 1rem; margin: 1rem 0; font-size: .9rem; }
.method-list dt { font-weight: 650; }
.method-list dd { margin: 0; color: var(--ink-soft); overflow-wrap: anywhere; }
@media (max-width: 430px) {
  .method-list { grid-template-columns: 1fr; }
  .method-list dd { margin-bottom: .4rem; }
}

/* The structured layout's card: the signature patterns in the column the
   Enneagram used to take. Numbered like the patterns in the report, so the
   reader can match the card to the section that explains it. */
.pc-patterns { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px; }
.pc-patterns li { display: flex; gap: 12px; align-items: baseline; font-size: 23px; line-height: 1.3; font-weight: 600; }
.pc-pattern-num {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; border-radius: 999px; background: var(--accent); color: var(--bg-alt);
  font-size: 18px; font-weight: 800; transform: translateY(4px);
}
.psyche-card.pc-narrow .pc-patterns li { font-size: 19px; gap: 8px; }
.psyche-card.pc-narrow .pc-pattern-num { width: 26px; height: 26px; font-size: 15px; }

/* ---------- the structured layout, round two: colour, charts, a plan ----------
 *
 * Everything below is scoped to classes only the structured renderer writes,
 * or to .layout-structured, which watchPartNav puts on the report container
 * when that renderer ran. The classic report never matches any of it. */

.visually-hidden {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* Each signature pattern has its own colour, carried by everything that
   comes from it, so the thread through the report is visible. */
:root { --pat-p1: var(--accent); --pat-p2: var(--accent); --pat-p3: var(--accent); }
.pat-p1 { --pat: var(--pat-p1); }
.pat-p2 { --pat: var(--pat-p2); }
.pat-p3 { --pat: var(--pat-p3); }
.pattern-dot.pat-p1, .pattern-dot.pat-p2, .pattern-dot.pat-p3,
.pat-p1 .pattern-dot, .pat-p2 .pattern-dot, .pat-p3 .pattern-dot,
.pattern.pat-p1 .pattern-badge, .pattern.pat-p2 .pattern-badge, .pattern.pat-p3 .pattern-badge { background: var(--pat); }
.pattern.pat-p1, .pattern.pat-p2, .pattern.pat-p3 { border-top: 4px solid var(--pat); }
/* Development items and pressure points keep their own meaning — green to
   build on, amber to work on or watch — rather than a pattern's colour. */
.pressure-item { border-left: 3px solid var(--warn); }

/* Part headings: a large numeral beside the title. */
.report-part { display: grid; grid-template-columns: auto 1fr; gap: .2rem 1rem; align-items: center; }
.part-num {
  font-size: clamp(2.6rem, 8vw, 3.6rem); font-weight: 800; line-height: .9; letter-spacing: -.04em;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.report-part { scroll-margin-top: 5rem; }

/* What a section found, in its head — shown even when it is shut, so the
   closed report reads as a summary. The definition shows only when open. */
.card-result { margin: .2rem 0 0; font-size: .88rem; font-weight: 600; color: var(--accent); }
.layout-structured .section-card.is-collapsed .card-sub { display: none; }

/* The headline as a pull-quote. */
.pull-quote {
  margin: .2rem 0 1rem; padding: .2rem 0 .2rem 1rem; border-left: 4px solid var(--accent);
  font-size: clamp(1.15rem, 3vw, 1.4rem); font-weight: 650; line-height: 1.35; font-style: italic;
}

/* Patterns side by side. */
@media (min-width: 760px) {
  .pattern-list { grid-template-columns: repeat(3, 1fr); }
  .pattern { grid-template-columns: 1fr; align-content: start; }
}
/* "More" disclosures: one sentence in view, the rest a tap away. */
details.more { margin: .3rem 0 .6rem; }
details.more > summary {
  cursor: pointer; display: inline-block; font-size: .8rem; font-weight: 650; color: var(--accent);
  list-style: none;
}
details.more > summary::-webkit-details-marker { display: none; }
details.more > summary::after { content: ' ▾'; }
details.more[open] > summary::after { content: ' ▴'; }

/* Big Five: the highest and lowest flagged. */
.trait-flag {
  font-size: .68rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  padding: .05rem .45rem; border-radius: 999px; margin-left: .3rem; vertical-align: middle;
  background: var(--accent-soft); color: var(--accent);
}
.bipolar.is-low .trait-flag { background: transparent; border: 1px solid var(--accent); }

/* MBTI: the type large, then four sliders. */
.type-hero { display: flex; align-items: baseline; flex-wrap: wrap; gap: .3rem .9rem; margin: .2rem 0 1rem; }
.type-code {
  font-size: clamp(2.6rem, 9vw, 3.4rem); font-weight: 800; letter-spacing: .04em; line-height: 1;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.type-name { font-size: 1.1rem; color: var(--ink-soft); font-weight: 600; }
.mbti-sliders { display: grid; gap: 1.1rem; }
.slider-ends { display: flex; justify-content: space-between; align-items: center; gap: .5rem; font-size: .88rem; color: var(--ink-soft); }
.slider-ends .is-chosen { color: var(--ink); font-weight: 650; }
.slider-ends b { font-size: 1.05rem; }
.slider-track {
  position: relative; height: 10px; margin: .5rem 0 .4rem; border-radius: 999px;
  background: linear-gradient(90deg, var(--accent-soft), var(--bg), var(--accent-soft)); border: 1px solid var(--line);
}
.slider-mid { position: absolute; left: 50%; top: -4px; bottom: -4px; width: 1px; background: var(--line); }
.slider-marker {
  position: absolute; top: 50%; width: 1.7rem; height: 1.7rem; margin: -.85rem 0 0 -.85rem;
  display: flex; align-items: center; justify-content: center; border-radius: 999px;
  background: linear-gradient(135deg, var(--accent), var(--accent-2)); color: #fff;
  font-weight: 800; font-size: .85rem; border: 2px solid var(--bg-alt); box-shadow: 0 1px 4px rgba(36, 26, 46, .3);
}
.slider-practice { margin: .3rem 0 0; font-size: .92rem; }

/* Motivators: Schwartz's circle beside the reading. */
.motive-top-grid { display: grid; gap: 1rem; align-items: center; }
@media (min-width: 760px) { .motive-top-grid { grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr); } }
.motive-rose { width: 100%; max-width: 380px; height: auto; display: block; margin: 0 auto; }
.rose-ring { fill: none; stroke: var(--line); stroke-width: 1; }
.rose-wedge { opacity: .9; stroke: var(--bg-alt); stroke-width: 1; }
.rose-openness { fill: var(--motive-openness); }
.rose-enhancement { fill: var(--motive-enhancement); }
.rose-conservation { fill: var(--motive-conservation); }
.rose-transcendence { fill: var(--motive-transcendence); }
.rose-label { fill: var(--ink-soft); font-size: 11px; font-family: var(--font); }
.rose-legend { display: flex; flex-wrap: wrap; gap: .4rem .9rem; margin: 0 0 .7rem; font-size: .8rem; color: var(--ink-soft); }
.rose-key::before { content: ''; display: inline-block; width: .7rem; height: .7rem; border-radius: 3px; margin-right: .35rem; vertical-align: -.05rem; }
.rose-key-openness::before { background: var(--motive-openness); }
.rose-key-enhancement::before { background: var(--motive-enhancement); }
.rose-key-conservation::before { background: var(--motive-conservation); }
.rose-key-transcendence::before { background: var(--motive-transcendence); }

/* Interests: one marker for where each is heading. */
.trend { margin: .1rem 0 .4rem; font-size: .8rem; font-weight: 650; color: var(--ink-soft); }
.trend-rising { color: var(--accent); }


/* Attachment: the two-axis map beside the read. */
.attach-top { display: grid; gap: 1rem; align-items: start; }
@media (min-width: 640px) { .attach-top { grid-template-columns: 220px 1fr; } }
.attach-map { margin: 0; }
.attach-map svg { width: 100%; max-width: 220px; height: auto; display: block; }
.attach-map figcaption { font-size: .74rem; color: var(--ink-soft); }
.attach-q { fill: var(--bg); stroke: var(--line); }
.attach-axis { stroke: var(--line); stroke-dasharray: 3 3; }
.attach-q-label { fill: var(--ink-soft); font-size: 10px; font-family: var(--font); }
.attach-axis-label { fill: var(--ink-soft); font-size: 10px; font-weight: 600; font-family: var(--font); }
.attach-blob { fill: var(--accent); opacity: .22; }
.attach-dot { fill: var(--accent); }
.attach-style { margin-top: 0; }

/* Who suits you: the verdict as a banner, then two matched lists. */
.partner-summary {
  display: flex; gap: .9rem; align-items: center; margin: .2rem 0 1rem; padding: 1rem 1.2rem;
  border-radius: 16px; background: linear-gradient(120deg, var(--accent-soft), color-mix(in srgb, var(--accent-soft) 30%, var(--bg)));
}
.partner-summary-icon {
  flex: none; display: grid; place-items: center; width: 2.6rem; height: 2.6rem; border-radius: 999px;
  background: var(--bg-alt); font-size: 1.3rem; box-shadow: 0 2px 8px rgba(60, 20, 90, .12);
}
.partner-summary p { margin: 0; font-size: 1.02rem; font-weight: 600; font-style: italic; line-height: 1.45; color: var(--ink); }
.partner-grid { display: grid; gap: .9rem; }
@media (min-width: 680px) { .partner-grid { grid-template-columns: 1fr 1fr; } }
.partner-col { padding: .9rem 1rem; border-radius: 14px; border: 1px solid var(--line); background: var(--bg-alt); border-top: 4px solid var(--good); }
.partner-careful { border-top-color: var(--warn); }
.partner-col-head { display: flex; align-items: center; gap: .55rem; margin: 0 0 .6rem; font-size: 1rem; }
.partner-badge {
  display: grid; place-items: center; width: 1.6rem; height: 1.6rem; border-radius: 999px;
  background: var(--good-soft); color: var(--good); font-weight: 800; font-size: .9rem;
}
.partner-careful .partner-badge { background: var(--warn-soft); color: var(--warn); }
/* No rule between entries: the numbers and the spacing already separate them. */
.partner-items { list-style: none; margin: 0; padding: 0; counter-reset: partner; display: grid; gap: .85rem; }
.partner-items li { counter-increment: partner; position: relative; padding: 0 0 0 2rem; margin: 0; }
.partner-items li::before {
  content: counter(partner); position: absolute; left: 0; top: .05rem; width: 1.35rem; height: 1.35rem; border-radius: 999px;
  display: grid; place-items: center; font-size: .75rem; font-weight: 800; color: var(--good); background: var(--good-soft);
}
.partner-careful .partner-items li::before { color: var(--warn); background: var(--warn-soft); }
.partner-items strong { display: block; font-size: .95rem; }
.partner-items span { display: block; font-size: .9rem; color: var(--ink-soft); line-height: 1.45; margin-top: .15rem; }
/* The same pair, used for what you bring and for your strengths at work. */
.paired-lists { margin: 1.1rem 0 .4rem; }

/* The plan: one row per step, this week to this year, each action tickable.
   The step's name on the left and its actions beside it, stacked on a phone. */
.timeline { display: grid; gap: .8rem; margin: .5rem 0 1rem; }
.timeline-col { padding: .8rem; border-radius: 12px; background: var(--bg); border: 1px solid var(--line); }
@media (min-width: 640px) {
  .timeline-col { display: grid; grid-template-columns: 9.5rem 1fr; gap: .4rem 1rem; align-items: start; }
  .timeline-col .timeline-head { margin: .55rem 0 0; }
  .timeline-col .plan-step:last-child { margin-bottom: 0; }
}
.timeline-head { display: flex; align-items: center; gap: .5rem; margin: 0 0 .6rem; font-weight: 700; font-size: .88rem; }
.timeline-dot { width: .7rem; height: .7rem; border-radius: 999px; background: var(--accent); }
.timeline-col + .timeline-col .timeline-dot { opacity: .65; }
.timeline-col:last-child .timeline-dot { opacity: .35; }
.plan-step {
  display: flex; gap: .55rem; align-items: flex-start; padding: .55rem .6rem; margin-bottom: .5rem;
  border-radius: 10px; background: var(--bg-alt); border: 1px solid var(--line); cursor: pointer;
}
.plan-step input { margin-top: .25rem; accent-color: var(--accent); width: 1rem; height: 1rem; flex: none; }
.plan-step b { display: block; font-weight: 600; font-size: .9rem; }
.plan-step small { display: block; color: var(--ink-soft); font-size: .8rem; }
/* Where an action came from — "How you work", "Wellbeing", a develop area —
   as a tag, so it reads as a source and not as more of the explanation. */
.plan-from {
  display: inline-flex; align-items: center; gap: .3rem; margin-top: .35rem;
  font-size: .72rem; font-weight: 700; letter-spacing: .02em; color: var(--accent);
  background: var(--accent-soft); border-radius: 999px; padding: .08rem .55rem;
}
.plan-from::before { content: '↳'; font-weight: 800; }
.plan-step:has(input:checked) b { text-decoration: line-through; color: var(--ink-soft); }
.timeline-empty { font-size: .85rem; margin: 0; }
.dev-grid { display: grid; gap: .8rem; }
@media (min-width: 760px) { .dev-grid { grid-template-columns: repeat(2, 1fr); } }

/* How you work: the edge as the hero of the section. */
.edge-hero h3 { font-size: 1.35rem; margin: .2rem 0 .4rem; }

/* Wellbeing: six tiles coloured by band — no scores, the band is the read. */
.wellness-tiles { display: grid; gap: .7rem; }
/* Two across, three down. */
@media (min-width: 640px) { .wellness-tiles { grid-template-columns: repeat(2, 1fr); } }
.wellness-tile { padding: .8rem .9rem; border-radius: 12px; border: 1px solid var(--line); border-top: 4px solid var(--line); background: var(--bg); }
.wellness-tile-steady { border-top-color: var(--good); }
.wellness-tile-mixed { border-top-color: var(--warn); }
.wellness-tile-under-strain { border-top-color: var(--danger); }
.wellness-tile-not-enough-evidence { border-top-style: dashed; }
.wellness-tile .trait-reading { margin: .4rem 0; font-size: .92rem; }

/* Under pressure: from the strength at its best to its overused form. */
.gauge { margin: .5rem 0 .6rem; }
.gauge-track {
  position: relative; height: 8px; border-radius: 999px;
  background: linear-gradient(90deg, var(--good), var(--warn)); opacity: .85;
}
.gauge-marker {
  position: absolute; top: 50%; width: 14px; height: 14px; margin: -7px 0 0 -7px; border-radius: 999px;
  background: var(--bg-alt); border: 3px solid var(--ink);
}
.gauge-ends { display: flex; justify-content: space-between; gap: 1rem; margin-top: .3rem; font-size: .78rem; color: var(--ink-soft); }
.gauge-ends span:last-child { text-align: right; font-weight: 600; color: var(--ink); }

/* "Read from": what was counted in full. */

/* The roast as its own room: grey while it is covered, then the page's own
   colours once "Read it anyway" opens it, so it reads as easily as the rest.
   It fades in when revealed. */
.layout-structured .bonus-card:not(.is-revealed) {
  background: color-mix(in srgb, var(--ink-soft) 13%, var(--bg-alt));
  border-color: color-mix(in srgb, var(--ink-soft) 22%, var(--bg-alt));
}
.layout-structured .bonus-card:not(.is-revealed) .bonus-cover {
  background: repeating-linear-gradient(135deg,
    color-mix(in srgb, var(--ink-soft) 8%, var(--bg-alt)) 0 10px,
    color-mix(in srgb, var(--ink-soft) 15%, var(--bg-alt)) 10px 20px);
  border-color: color-mix(in srgb, var(--ink-soft) 30%, var(--bg-alt));
}
.layout-structured .bonus-card:not(.is-revealed) .bonus-cover h3 { color: var(--ink); }
.layout-structured .bonus-card:not(.is-revealed) .bonus-reveal {
  color: var(--ink); border-color: color-mix(in srgb, var(--ink-soft) 55%, var(--bg-alt)); background: var(--bg-alt);
}
.layout-structured .bonus-body:not([hidden]) { animation: roast-in .5s ease both; }
@keyframes roast-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }

/* Charts grow when a section opens. Held at zero while shut — the
   !important outranks the inline width the data attribute writes — and
   animated to their value on opening. */
.layout-structured .bar-fill { transition: width .7s cubic-bezier(.2, .7, .2, 1); }
.layout-structured .bipolar-marker, .layout-structured .slider-marker, .layout-structured .gauge-marker {
  transition: left .7s cubic-bezier(.2, .7, .2, 1);
}
.layout-structured .section-card.is-collapsed .bar-fill { width: 0 !important; }
.layout-structured .section-card.is-collapsed .bipolar-marker,
.layout-structured .section-card.is-collapsed .slider-marker,
.layout-structured .section-card.is-collapsed .gauge-marker { left: 50% !important; }
@media (prefers-reduced-motion: reduce) {
  .layout-structured .bar-fill, .layout-structured .bipolar-marker,
  .layout-structured .slider-marker, .layout-structured .gauge-marker { transition: none; }
  .layout-structured .bonus-body:not([hidden]) { animation: none; }
}


/* ---------- the structured layout's card: a 1080 x 1920 story ----------
 *
 * One portrait canvas, fitted to the screen and exported as it is. Every rule
 * starts .psyche-card or .pc- so cardStyles() carries it into the image. */
.psyche-card.pc-story {
  width: 1080px; height: 1920px; padding: 56px 60px 44px; gap: 0; border-radius: 44px;
  text-align: left; overflow: hidden;
  background: linear-gradient(180deg, var(--bg-alt) 0%, var(--bg) 100%);
}
.pc-story-in { display: flex; flex-direction: column; gap: 18px; height: 100%; }
.pc-stop { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; font-size: 30px; }
.pc-stop .pc-brand { font-size: 30px; gap: 12px; }
.pc-stop .pc-brand-mark { width: 44px; height: 44px; }
.pc-sowner { font-weight: 700; font-size: 32px; text-align: center; color: var(--ink); }
.pc-sconf { justify-self: end; }
.pc-ring { width: 96px; height: 96px; display: block; }
.pc-ring-track { fill: none; stroke: var(--accent-soft); stroke-width: 7; }
.pc-ring-fill { fill: none; stroke: var(--accent); stroke-width: 7; stroke-linecap: round; }
.pc-ring-text { fill: var(--ink); font-size: 20px; font-weight: 800; font-family: inherit; }

.pc-shero {
  border-radius: 36px; padding: 38px 42px 40px; color: #fff;
  background: linear-gradient(135deg, var(--accent) 0%, var(--accent-2) 100%);
}
.pc-skicker { font-size: 24px; letter-spacing: .2em; text-transform: uppercase; opacity: .85; margin: 0 0 14px; font-weight: 600; }
.pc-sname { display: flex; align-items: center; gap: 26px; }
.pc-smedal {
  flex: none; width: 112px; height: 112px; border-radius: 999px; display: flex; align-items: center; justify-content: center;
  font-size: 62px; background: rgba(255, 255, 255, .95); box-shadow: 0 6px 24px rgba(0, 0, 0, .18);
}
.pc-sname h2 { margin: 0; font-size: 70px; line-height: 1; font-weight: 800; letter-spacing: -.03em; color: #fff; }
.pc-sheadline { margin: 22px 0 0; font-size: 34px; line-height: 1.25; font-style: italic; font-weight: 650; }
.pc-sblurb { margin: 12px 0 0; font-size: 26px; line-height: 1.42; opacity: .95; }

.pc-spanel {
  background: var(--bg-alt); border: 1px solid var(--line); border-radius: 28px; padding: 22px 28px; min-width: 0;
}
.pc-slab {
  display: flex; align-items: center; gap: 12px; margin: 0 0 12px;
  font-size: 22px; letter-spacing: .16em; text-transform: uppercase; color: var(--ink-soft); font-weight: 700;
}
.pc-sgrid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }

.pc-spatterns { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px; }
.pc-spatterns li { display: flex; align-items: center; gap: 18px; font-size: 32px; font-weight: 700; line-height: 1.2; }
.pc-snum {
  flex: none; width: 50px; height: 50px; border-radius: 999px; display: flex; align-items: center; justify-content: center;
  background: var(--pat, var(--accent)); color: #fff; font-size: 26px; font-weight: 800;
}
.pc-pat-p1 { --pat: var(--pat-p1); }
.pc-pat-p2 { --pat: var(--pat-p2); }
.pc-pat-p3 { --pat: var(--pat-p3); }

.pc-sletters { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.pc-sletters li { display: grid; grid-template-columns: 36px 1fr auto; align-items: center; gap: 12px; font-size: 26px; }
.pc-sletters b { font-size: 30px; font-weight: 800; color: var(--accent); }
/* How firmly each MBTI letter was picked, in words, firmer reading darker. */
.pc-sstrength {
  font-size: 19px; font-weight: 700; letter-spacing: .02em; padding: 3px 12px; border-radius: 999px;
  color: var(--accent); background: var(--accent-soft); white-space: nowrap;
}
.pc-sstrength-clear { color: #fff; background: var(--accent); }
.pc-sstrength-slight { color: var(--ink-soft); background: color-mix(in srgb, var(--accent-soft) 55%, var(--bg-alt)); }
/* The card guide lights the part it is on, and steps the rest back. */
.psyche-card .pc-spanel, .psyche-card .pc-shero, .psyche-card .pc-sconf { transition: opacity .3s ease, box-shadow .3s ease, transform .3s ease; }
.psyche-card.pc-guiding .pc-spanel:not(.pc-glow), .psyche-card.pc-guiding .pc-shero:not(.pc-glow),
.psyche-card.pc-guiding .pc-sconf:not(.pc-glow) { opacity: .32; }
.psyche-card .pc-glow { box-shadow: 0 0 0 6px var(--accent), 0 16px 40px rgba(123, 63, 160, .35); transform: scale(1.02); position: relative; z-index: 1; }
.psyche-card .pc-sconf.pc-glow { border-radius: 999px; }

.pc-straits { list-style: none; margin: 0; padding: 0; display: grid; gap: 13px; }
.pc-straits li { display: grid; grid-template-columns: 170px 1fr 48px; align-items: center; gap: 14px; font-size: 25px; }
.pc-straits b { text-align: right; font-variant-numeric: tabular-nums; font-size: 27px; }
.pc-sbar { width: 100%; height: 16px; display: block; }
.pc-sbar-track { fill: var(--accent-soft); }
.pc-sbar-fill { fill: var(--accent); }

.pc-schips { display: flex; flex-wrap: wrap; gap: 10px; }
.pc-schips span {
  background: var(--accent-soft); color: var(--accent); border-radius: 14px; padding: 10px 18px;
  font-size: 26px; font-weight: 600; line-height: 1.2;
}
.pc-slove { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; font-size: 27px; }
.pc-slove li { display: flex; align-items: center; gap: 12px; }
.pc-sfoot {
  margin: auto 0 0; text-align: center; font-size: 20px; letter-spacing: .14em; text-transform: uppercase; color: var(--ink-soft);
}


/* The story card, round two: the emblem in its medallion, the full
   write-up, a line under each pattern, and fuller panels. */
.pc-smedal { color: var(--accent); }
.pc-emblem { width: 66px; height: 66px; display: block; }
.pc-sfranchise { margin: 6px 0 0; font-size: 24px; letter-spacing: .14em; text-transform: uppercase; opacity: .8; font-weight: 600; }
.pc-shero {
  background:
    radial-gradient(circle at 92% 8%, rgba(255, 255, 255, .18) 0 120px, transparent 121px),
    radial-gradient(circle at 104% 46%, rgba(255, 255, 255, .10) 0 90px, transparent 91px),
    linear-gradient(135deg, var(--accent) 0%, var(--accent-2) 100%);
}
.pc-sblurb { font-size: 27px; line-height: 1.45; }
.pc-spatterns li { align-items: flex-start; }
.pc-spatterns li > div { display: flex; flex-direction: column; gap: 4px; }
.pc-spatterns b { font-size: 31px; line-height: 1.2; }
.pc-sline { font-size: 23px; line-height: 1.35; font-weight: 400; color: var(--ink-soft); }
.pc-snum { margin-top: 2px; }
.pc-slab span:first-child {
  display: inline-flex; align-items: center; justify-content: center; width: 40px; height: 40px;
  border-radius: 999px; background: var(--accent-soft); font-size: 22px;
}
.pc-sletters li { grid-template-columns: 44px 1fr auto; font-size: 28px; }
.pc-sletters b { font-size: 40px; line-height: 1; }
.pc-slove-panel { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
.pc-sgrid > .pc-spanel { display: flex; flex-direction: column; }
/* The report's essence medallion, with an emblem in place of an emoji. */
.essence-icon.has-emblem { color: var(--accent); }
.essence-emblem { width: 60%; height: 60%; }

/* ---------- four parts, each one box ----------
   The part's numbered heading is the only toggle, and the only gradient: the
   box's own top edge. The sections inside are set apart by one faint rule
   each — the same rule under the part's heading — rather than drawn as cards
   in a card, each with a gradient bar repeating the box's. */
.part-card { padding-top: 1.1rem; }
.part-card-head { display: block; cursor: pointer; margin-bottom: 0; }
.part-card .report-part { margin: 0; padding: 0; border: 0; }
.part-card .part-title { margin: 0; }
.part-card .part-title .card-toggle { font-size: inherit; }
.part-card:not(.is-collapsed) > .part-card-head { padding-bottom: .9rem; border-bottom: 1px solid var(--line); }
.part-body > .card {
  background: none; box-shadow: none; border: 0; border-radius: 0; margin: 0;
  padding: 1.5rem 0 .4rem; border-top: 1px solid var(--line);
}
.part-body > .card::before { display: none; }
.part-body > .card:first-child { border-top: 0; }
.part-body > .card > .card-head { margin-bottom: .8rem; align-items: center; }
.part-body > .card > .card-head .card-icon { width: 1.9rem; height: 1.9rem; font-size: .95rem; }
.part-body > .card > .card-head h2 { font-size: 1.2rem; }

/* MBTI and the Big Five: the scale on the left, the reading on the right. */
.reading-row { display: grid; grid-template-columns: minmax(0, 1fr); gap: .5rem 1.4rem; align-items: start; }
@media (min-width: 760px) { .reading-row { grid-template-columns: minmax(220px, 300px) minmax(0, 1fr); } }
.reading-row + .reading-row { padding-top: 1rem; border-top: 1px dashed var(--line); }
.trait-text .trait-reading:first-child { margin-top: 0; }
.slider-ends .is-faint { opacity: .4; }
.slider-strength { margin: .4rem 0 0; }
.sub-head { margin: 1rem 0 .4rem; font-size: .95rem; }
.relationships-card .split { margin-top: 1.4rem; }

/* Evidence runs to a sentence or more here, so a pill would clip it. */
.layout-structured .ev { border-radius: 8px; line-height: 1.4; padding: .3rem .6rem; }

/* The story card, round three: pattern names beside what motivates them,
   and one text size in every panel, so no box shouts over another. */
.psyche-card.pc-story { --pc-text: 27px; }
.pc-spatterns li, .pc-smotives li { display: flex; align-items: center; gap: 16px; font-size: var(--pc-text); line-height: 1.25; }
.pc-spatterns b, .pc-smotives b { font-size: var(--pc-text); font-weight: 700; line-height: 1.25; }
.pc-smotives { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px; }
.pc-smotives .pc-snum { background: var(--accent-soft); color: var(--accent); }
.pc-spatterns .pc-snum, .pc-smotives .pc-snum { width: 42px; height: 42px; font-size: 22px; margin-top: 0; }
.pc-sletters li { font-size: var(--pc-text); }
.pc-sletters b { font-size: var(--pc-text); line-height: 1.25; }
.pc-straits li, .pc-straits b { font-size: var(--pc-text); }
.pc-schips span { font-size: var(--pc-text); }
.pc-slove { font-size: var(--pc-text); }
/* Round four: the card's small print a step up — the panel labels, the
   strength pills, the kicker and franchise, the numerals and the footer read
   at a glance on a phone, without the card turning heavy. */
.pc-slab { font-size: 24px; }
.pc-slab span:first-child { width: 42px; height: 42px; font-size: 23px; }
.pc-sstrength { font-size: 21px; padding: 3px 13px; }
.pc-skicker { font-size: 25.5px; }
.pc-sfranchise { font-size: 25.5px; }
.pc-spatterns .pc-snum, .pc-smotives .pc-snum { width: 44px; height: 44px; font-size: 23.5px; }
.pc-sfoot { font-size: 21.5px; }
.pc-ring-text { font-size: 21.5px; }

/* ---------- what insights will I get? ----------
   Two tiers, and a note: the free summary card — what is on it, beside a
   real card drawn from the sample — then the full premium report by its four
   numbered parts, then compatibility, free. */
.insight-card-head {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .6rem 1rem;
}
.insight-card-head h2 { margin: 0; flex: 1 1 auto; }
.insight-card-head .btn { flex: none; padding: .45rem .9rem; font-size: .9rem; }

.insight-tier { margin-top: 1.2rem; border-radius: var(--radius); }
.tier-badges { margin: 0; }
.tier-badge {
  display: inline-block; padding: .18rem .65rem; border-radius: 999px;
  font-size: .72rem; font-weight: 800; letter-spacing: .1em; text-transform: uppercase;
}
.tier-badge-free { background: var(--good-soft); color: var(--good); }
.tier-title { margin: .45rem 0 .25rem; font-size: 1.3rem; line-height: 1.25; }
.tier-blurb { margin: 0 0 .9rem; color: var(--ink-soft); font-size: .95rem; }

/* The free tier: what is on the card, and the card. */
.insight-free {
  display: grid; gap: 1.2rem; align-items: center;
  padding: 1.2rem clamp(1rem, 3vw, 1.4rem);
  background: linear-gradient(140deg, color-mix(in srgb, var(--accent-soft) 70%, var(--bg)), var(--bg));
  border: 1px solid var(--line);
}
@media (min-width: 760px) { .insight-free { grid-template-columns: 1fr 250px; } }
/* What is on the card: one white box, two columns by four rows at every
   width, each entry an icon and two lines, ruled apart rather than boxed. */
.insight-card .card-features, .insight-card .insight-parts, .insight-card .insight-extras { padding-left: 0; }
.card-features {
  list-style: none; margin: 0; padding: .35rem;
  display: grid; grid-template-columns: 1fr 1fr; gap: 0;
  background: var(--bg-alt); border: 1px solid var(--line); border-radius: 18px;
  box-shadow: 0 10px 28px -20px rgba(60, 20, 90, .45);
}
.card-features li {
  display: flex; gap: .65rem; align-items: center; margin: 0;
  padding: .75rem .7rem; border-bottom: 1px solid var(--line);
}
.card-features li:nth-child(odd) { border-right: 1px solid var(--line); }
.card-features li:nth-last-child(-n + 2) { border-bottom: 0; }
.card-feature-icon {
  flex: none; display: grid; place-items: center; width: 2.3rem; height: 2.3rem;
  border-radius: 12px; font-size: 1.1rem;
  background: linear-gradient(135deg, var(--accent-soft), color-mix(in srgb, var(--accent-soft) 40%, var(--bg-alt)));
}
.card-features strong { display: block; font-size: .9rem; line-height: 1.3; }
@media (max-width: 479px) {
  .card-features li { align-items: center; gap: .5rem; padding: .6rem .55rem; }
  .card-feature-icon { width: 2rem; height: 2rem; font-size: .95rem; border-radius: 10px; }
  .card-features strong { font-size: .84rem; }
}

/* The card itself: tilted, lifted, and a tap away from full screen. */
.insight-preview {
  position: relative; margin: 0 auto; padding: 0; border: 0; background: none; cursor: zoom-in;
  display: block; width: fit-content; font: inherit; color: inherit; border-radius: 18px;
}
.insight-preview-frame {
  display: block; border-radius: 18px; overflow: hidden; box-shadow: 0 18px 40px -18px rgba(60, 20, 90, .45);
  transform: rotate(-2deg); transition: transform .3s ease;
}
.insight-preview:hover .insight-preview-frame, .insight-preview:focus-visible .insight-preview-frame { transform: rotate(0deg) translateY(-3px); }
.insight-preview-frame .psyche-card { display: block; transform-origin: top left; }
.insight-preview-expand {
  position: absolute; right: -6px; bottom: -6px; display: grid; place-items: center;
  width: 2.2rem; height: 2.2rem; border-radius: 999px; background: var(--accent); color: var(--bg);
  box-shadow: 0 6px 16px -6px rgba(60, 20, 90, .6);
}
.insight-preview-expand svg { width: 1.05rem; height: 1.05rem; fill: none; stroke: currentColor; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }
@media (prefers-reduced-motion: reduce) { .insight-preview-frame { transition: none; } }

/* The premium tier: four numbered parts, like the report itself. */
.insight-premium.premium-tier { padding: 1.2rem clamp(1rem, 3vw, 1.4rem); }
.insight-premium .premium-tier-head h3 { font-size: 1.3rem; margin: 0; }
.insight-premium .tier-blurb { margin-top: .4rem; }
.insight-parts {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: 1fr; gap: .7rem;
}
@media (min-width: 640px) { .insight-parts { grid-template-columns: 1fr 1fr; } }
.insight-part {
  display: flex; gap: .8rem; align-items: flex-start; padding: .9rem 1rem;
  background: var(--bg-alt); border: 1px solid var(--line); border-radius: 14px;
}
.insight-part-num {
  flex: none; font-size: 1.9rem; font-weight: 800; line-height: 1; letter-spacing: -.03em;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.insight-part h4 { margin: 0 0 .25rem; font-size: 1rem; }
.insight-part p { margin: 0; font-size: .86rem; color: var(--ink-soft); line-height: 1.45; }
.insight-part-chips { display: flex; flex-wrap: wrap; gap: .3rem; margin-top: .55rem !important; }
.insight-part-chips span {
  font-size: .74rem; font-weight: 600; color: var(--accent); background: var(--accent-soft);
  border-radius: 999px; padding: .15rem .55rem;
}
.insight-premium-foot {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: .8rem 1rem; margin-top: 1rem;
}
.insight-extras {
  list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: .5rem 1.2rem;
  font-size: .86rem; font-weight: 600; color: var(--ink);
}
.insight-premium-foot .btn { margin-left: auto; padding: .5rem 1rem; font-size: .9rem; flex: none; }
/* On a laptop the button shares the last line with what comes with the report. */
@media (min-width: 760px) {
  .insight-premium-foot { flex-wrap: nowrap; align-items: flex-end; }
  .insight-extras { flex: 1 1 auto; min-width: 0; }
}
.insight-extras li { display: flex; align-items: center; gap: .4rem; }

/* Compatibility, free: one line under the two tiers. */
.insight-compat {
  margin-top: 1rem; display: flex; gap: .8rem; align-items: center;
  padding: .85rem 1rem; border-radius: 14px; border: 1px dashed var(--line);
}
.insight-compat-icon { font-size: 1.5rem; }
.insight-compat h3 { margin: 0; font-size: .98rem; }
.insight-compat p { margin: .15rem 0 0; font-size: .86rem; color: var(--ink-soft); }

/* The part nav, back under the summary card: each part by its heading. */
.part-nav {
  position: sticky; top: 3.4rem; z-index: 10;
  display: flex; flex-wrap: wrap; gap: .3rem;
  margin: 0 0 .8rem; padding: .4rem; border-radius: 18px;
  background: color-mix(in srgb, var(--bg-alt) 92%, transparent); backdrop-filter: blur(8px);
  border: 1px solid var(--line); box-shadow: var(--shadow);
}
.part-nav::-webkit-scrollbar { display: none; }
/* Flush with the top of the sample's scrolling body, over its padding. */
#sample-sections .part-nav { top: -1rem; }
.part-nav-item {
  flex: none; border: 0; border-radius: 999px; padding: .32rem .8rem; cursor: pointer; white-space: nowrap;
  background: transparent; color: var(--ink-soft); font: inherit; font-size: .82rem; font-weight: 600;
}
.part-nav-num { margin-right: .35rem; font-variant-numeric: tabular-nums; opacity: .65; }
.part-nav-item:hover { color: var(--accent); }
.part-nav-item.is-current { background: var(--accent); color: var(--bg-alt); }
.part-nav-item.is-current .part-nav-num { opacity: .85; }
[data-part="method"], [data-part="roast"] { scroll-margin-top: 5.5rem; }

/* Every name in full: the nav wraps rather than scrolling one off the end.
   On a phone it stays where it is rather than following the reader down, so
   two rows of it never sit over the report. Where the screen leaves room
   beside the 820px report, it stands down the left as a list instead. */
.part-nav-lead { display: none; }
/* On a phone: one thin row of numerals stuck under the site's header. */
@media (max-width: 639px) {
  #profile-body .part-nav {
    top: var(--header-h, 3rem); z-index: 15; flex-wrap: nowrap; align-items: center; gap: .1rem;
    margin: 0 0 .8rem; padding: .2rem .35rem; border-radius: 12px;
  }
  .part-nav .part-nav-label { display: none; }
  /* The part being read by name, as much of it as fits, then the numerals. */
  .part-nav .part-nav-lead {
    display: block; flex: 1 1 auto; min-width: 0; padding: 0 .3rem 0 .4rem; font-size: .74rem; font-weight: 700;
    color: var(--accent); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .part-nav .part-nav-item { flex: none; width: 1.7rem; padding: .22rem 0; font-size: .7rem; text-align: center; }
  .part-nav .part-nav-num { margin: 0; opacity: 1; }
}
/* On any laptop the nav stands down the left. Where the screen is too narrow
   to centre the 820px report with the nav beside it, the report moves right
   to make room rather than the nav giving up its column. */
@media (min-width: 1000px) {
  body:has(#view-profile:not([hidden]) #profile-body .part-nav) main.container {
    margin-left: max(248px, calc((100vw - 820px) / 2)); margin-right: auto;
    max-width: min(820px, calc(100vw - 248px));
  }
  #profile-body .part-nav {
    position: fixed; top: 6.5rem; left: max(1rem, calc((100vw - 820px) / 2 - 248px)); width: 232px;
    flex-direction: column; flex-wrap: nowrap; align-items: stretch; gap: .15rem; margin: 0; border-radius: 16px;
  }
  /* Number and name side by side, so a long name wraps under itself rather
     than under its number; the unnumbered entries line up with the names. */
  #profile-body .part-nav-item { display: flex; align-items: baseline; text-align: left; white-space: normal; border-radius: 10px; padding: .45rem .7rem; }
  #profile-body .part-nav-num { flex: none; width: 1.75rem; margin-right: 0; }
  #profile-body .part-nav-item:not(:has(.part-nav-num)) { padding-left: 2.45rem; }
}

/* The full report on a wide, tall screen: the nav down the left column,
   scrolling on its own if a short screen cannot hold all of it, and the
   page's three actions under it. */
@media (min-width: 1000px) and (min-height: 520px) {
  #view-profile.profile-paid #profile-body .part-nav { max-height: calc(100vh - 6.5rem - 5.2rem); overflow-y: auto; }
  /* The page's three actions, under the nav: quiet icons in the nav's own
     box style, each named by a tooltip. Its top is the nav's measured foot
     (--side-nav-bottom, set by layoutPsycheCard). */
  #view-profile.profile-paid .cta-row {
    position: fixed; z-index: 10; top: var(--side-nav-bottom, 760px); left: max(1rem, calc((100vw - 820px) / 2 - 248px));
    width: 232px; margin: 0; padding: .35rem; gap: .25rem; justify-content: space-around; flex-wrap: nowrap;
    border-radius: 16px; border: 1px solid var(--line); box-shadow: var(--shadow);
    background: color-mix(in srgb, var(--bg-alt) 92%, transparent); backdrop-filter: blur(8px);
  }
  #view-profile.profile-paid .cta-row .btn {
    position: relative; display: grid; place-items: center; width: 44px; height: 38px; padding: 0; min-height: 0;
    border: 0; border-radius: 10px; background: transparent; color: var(--ink-soft); box-shadow: none;
  }
  #view-profile.profile-paid .cta-row .btn:hover, #view-profile.profile-paid .cta-row .btn:focus-visible {
    background: var(--accent-soft); color: var(--accent); outline: none;
  }
  #view-profile.profile-paid .cta-row .btn.danger:hover, #view-profile.profile-paid .cta-row .btn.danger:focus-visible {
    background: color-mix(in srgb, #c0392b 12%, var(--bg-alt)); color: #b3261e;
  }
  #view-profile.profile-paid .cta-row .cta-icon { display: block; }
  #view-profile.profile-paid .cta-row .cta-label {
    position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
  }
  #view-profile.profile-paid .cta-row [data-tip]:hover::after, #view-profile.profile-paid .cta-row [data-tip]:focus-visible::after {
    content: attr(data-tip); position: absolute; top: calc(100% + 8px); left: 50%; transform: translateX(-50%);
    padding: .3rem .6rem; border-radius: 8px; background: var(--ink); color: var(--bg-alt);
    font-size: .74rem; font-weight: 600; white-space: nowrap; pointer-events: none; z-index: 2;
  }
}

/* Both reports open on the Psyche Card, three fifths of the column, and
   beside it what each part of it means. Stacked on a phone. */
.profile-top { margin-bottom: 1rem; }
#view-profile.profile-structured .profile-top { display: grid; gap: 1rem; align-items: start; }
#view-profile.profile-structured #profile-side { padding: 1rem; overflow: visible; }
@media (min-width: 720px) {
  /* The card three fifths of the width, what it means the rest, the two
     the same height. */
  #view-profile.profile-structured .profile-top { grid-template-columns: 3fr 2fr; align-items: stretch; }
}
#view-profile.profile-structured #psyche-card-section { width: auto; margin: 0; }
/* The card fills its box, edge to edge, on a free report and in the left
   column of a full one: no padding round it, and no shadow of its own. */
#view-profile.profile-structured #psyche-card-section { padding: 0; overflow: hidden; }
#view-profile.profile-structured .psyche-card-slot { margin: 0; }
#view-profile.profile-structured #psyche-card { box-shadow: none; }
/* Enlarge is the way to full screen, so the card says nothing about tapping it. */
#view-profile.profile-structured .psyche-card-hint { display: none; }
#view-profile.profile-structured .psyche-card-slot { cursor: default; }
.cx-status { margin: .5rem 0 0; font-size: .8rem; color: #b3261e; }
/* Delete everything: a warning sheet, the red action on the right. */
.delete-dialog { max-width: 26rem; text-align: center; }
/* The data popout, once a fresh export is loaded over an existing card: a
   short heads-up that the card may change. */
.card-change-note {
  display: flex; gap: .45rem; align-items: flex-start; margin: .7rem 0 0; padding: .5rem .7rem; border-radius: 12px;
  background: var(--accent-soft); font-size: .82rem; line-height: 1.4; color: var(--ink); text-align: left;
}
.card-change-note[hidden] { display: none; }
/* The unlock's Deeper read, in the data popout: a quiet line above the
   buttons, off until ticked — offered, not pressed on anyone. */
.deeper-read {
  display: flex; gap: .45rem; align-items: center; margin: .7rem 0 0; cursor: pointer; text-align: left;
  font-size: .8rem; line-height: 1.4; color: var(--ink-soft);
}
.deeper-read[hidden] { display: none; }
.deeper-read input { flex: none; width: .95rem; height: .95rem; margin: 0; accent-color: var(--accent); }
.delete-dialog-icon {
  display: grid; place-items: center; width: 3.2rem; height: 3.2rem; margin: 0 auto .7rem; border-radius: 999px;
  color: #b3261e; background: color-mix(in srgb, #c0392b 12%, var(--bg-alt));
}
.delete-dialog-icon svg { width: 1.6rem; height: 1.6rem; }
.delete-dialog h2 { margin: 0 0 .5rem; font-size: 1.3rem; }
.delete-dialog p { margin: 0 0 .7rem; color: var(--ink-soft); line-height: 1.5; }
.delete-dialog-actions { display: flex; gap: .6rem; justify-content: center; margin-top: 1rem; flex-wrap: wrap; }
.delete-confirm { background: #b3261e; border-color: #b3261e; color: #fff; }
.delete-confirm:hover, .delete-confirm:focus-visible { background: #8f1d17; border-color: #8f1d17; }
.free-method-card { margin-top: 1rem; }
/* Evidence and method's data: one quiet row per source, what was read from
   it beneath, the row itself the way to change or add it. */
.sources-read { margin-top: 1.2rem; }
.sources-read-title {
  margin: 0 0 .5rem; font-size: .74rem; font-weight: 700; letter-spacing: .07em; text-transform: uppercase; color: var(--ink-soft);
}
.sources-read-lost { margin: 0 0 .6rem; font-size: .86rem; color: var(--ink-soft); }
.card .sources-read-list { list-style: none; margin: 0; padding: 0; border: 1px solid var(--line); border-radius: 14px; overflow: hidden; }
.card .sources-read-list li { margin: 0; padding: 0; }
.sources-read-list li + li { border-top: 1px solid var(--line); }
/* Icon on the left; name and Change/Add on one line; what was read beneath,
   across the full width, so a phone does not squeeze it into a column. */
.source-read {
  display: grid; grid-template-columns: 2.1rem 1fr auto; grid-template-areas: "icon name action" "icon what what";
  column-gap: .8rem; row-gap: .1rem; align-items: center; width: 100%; margin: 0;
  padding: .75rem .9rem; border: 0; border-radius: 0; background: none; color: inherit; font: inherit; text-align: left; cursor: pointer;
  transition: background .15s ease;
}
.source-read-body { display: contents; }
.source-read:hover { background: color-mix(in srgb, var(--accent-soft) 55%, transparent); }
.source-read:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.source-read-icon {
  grid-area: icon; align-self: start; display: grid; place-items: center; width: 2.1rem; height: 2.1rem; border-radius: 10px;
  background: var(--accent-soft); font-size: 1rem; line-height: 1;
}
.source-read.is-missing .source-read-icon { filter: grayscale(1); opacity: .6; }
.source-read-name { grid-area: name; min-width: 0; font-weight: 650; font-size: .95rem; }
.source-read-what { grid-area: what; min-width: 0; font-size: .8rem; line-height: 1.45; color: var(--ink-soft); }
.source-read-action { grid-area: action; justify-self: end; font-size: .8rem; font-weight: 650; color: var(--accent); white-space: nowrap; }
.source-read.is-loaded .source-read-action::after { content: ' ›'; }
.source-read.is-missing .source-read-action {
  padding: .25rem .65rem; border-radius: 999px; border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
}
.sources-read-note { margin: .6rem 0 0; font-size: .78rem; line-height: 1.45; color: var(--ink-soft); }
/* Beside a free card: its three actions along the top, and below them a
   stage that stays empty until the reader points at a part of the card —
   then that part's meaning pops out level with it, an arrow pointing back. */
.cx { position: relative; height: 100%; display: flex; flex-direction: column; gap: .8rem; }
.cx-home { transition: opacity .25s ease; }
.cx.is-explaining .cx-home { opacity: .12; }
.cx-home-title { margin: 0 0 .7rem; font-size: 1.2rem; line-height: 1.25; }
.cx-home-intro { margin: .85rem 0 .6rem; font-size: .9rem; line-height: 1.55; color: var(--ink-soft); }
.cx-home-hint {
  display: flex; gap: .55rem; align-items: flex-start; margin: 0; padding: .65rem .75rem; border-radius: 12px;
  font-size: .85rem; line-height: 1.45; font-weight: 600; color: var(--accent); background: var(--accent-soft);
}
.cx-home-hint .cx-hint-tap, .cx-home-hint .cx-hint-phone { display: none; }
@media (hover: none) { .cx-home-hint .cx-hint-tap { display: inline; } .cx-home-hint .cx-hint-hover { display: none; } }
/* On a phone the card opens full screen first, and is explained there. */
@media (max-width: 719px) {
  .cx-home-hint .cx-hint-phone { display: inline; }
  .cx-home-hint .cx-hint-hover, .cx-home-hint .cx-hint-tap { display: none; }
}
.cx-tools { display: grid; grid-template-columns: repeat(3, 1fr); gap: .45rem; }
.cx-tool {
  display: inline-flex; flex-direction: column; align-items: center; gap: .25rem; min-width: 0;
  padding: .5rem .55rem .4rem; border-radius: 12px; border: 1px solid var(--line); background: var(--bg-alt);
  font: inherit; font-size: .7rem; font-weight: 700; color: var(--ink-soft); cursor: pointer;
  transition: border-color .2s, color .2s, transform .2s, box-shadow .2s;
}
.cx-tool svg { width: 20px; height: 20px; color: var(--accent); }
.cx-tool:hover, .cx-tool:focus-visible {
  border-color: var(--accent); color: var(--accent); transform: translateY(-2px);
  box-shadow: 0 8px 18px rgba(123, 63, 160, .14); outline: none;
}
/* A free report's compatibility test, under the card's three tools. */
.cx-compat {
  display: flex; align-items: center; justify-content: center; gap: .5rem; width: 100%; margin-top: .45rem;
  padding: .6rem .8rem; border-radius: 12px; border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
  background: var(--accent-soft); font: inherit; font-size: .86rem; font-weight: 700; color: var(--accent); cursor: pointer;
  transition: border-color .2s, transform .2s, box-shadow .2s;
}
.cx-compat svg { width: 18px; height: 18px; }
/* On a paid report, a thin line of a button: the report below is the page's business. */
.cx-compat.is-thin { padding: .32rem .7rem; margin-top: .4rem; border-radius: 10px; font-size: .78rem; gap: .4rem; }
.cx-compat.is-thin svg { width: 15px; height: 15px; }
.cx-compat:hover, .cx-compat:focus-visible {
  border-color: var(--accent); transform: translateY(-2px); box-shadow: 0 8px 18px rgba(123, 63, 160, .14); outline: none;
}
.cx-stage { flex: 1; min-height: 14rem; }
/* The pop is placed against the whole panel, so it can come level with the
   ring at the very top of the card, over the actions if need be. */
.cx-pop {
  position: absolute; left: 0; right: 0; top: 0; z-index: 3;
  padding: .95rem 1rem 1rem; border-radius: 16px; border: 1px solid var(--accent);
  background: var(--bg-alt); box-shadow: 0 16px 38px rgba(123, 63, 160, .18);
  transition: transform .25s ease; animation: cx-in .2s ease both;
}
@keyframes cx-in { from { opacity: 0; } to { opacity: 1; } }
.cx-arrow {
  position: absolute; left: -7px; top: 50%; width: 12px; height: 12px; margin-top: -6px; transform: rotate(45deg);
  background: var(--bg-alt); border-left: 1px solid var(--accent); border-bottom: 1px solid var(--accent);
}
.cx-close {
  position: absolute; top: .45rem; right: .45rem; width: 1.7rem; height: 1.7rem; border-radius: 999px; border: 0;
  background: transparent; color: var(--ink-soft); cursor: pointer; font-size: .8rem; display: none;
}
.cx-pop-head { display: flex; align-items: center; gap: .55rem; margin-bottom: .55rem; }
.cx-pop-icon {
  flex: none; display: grid; place-items: center; width: 2.1rem; height: 2.1rem; border-radius: 10px; font-size: 1.05rem;
  background: linear-gradient(135deg, var(--accent-soft), color-mix(in srgb, var(--accent-2) 18%, var(--bg-alt)));
}
.cx-pop-title { font-size: 1rem; }
.cx-about { margin: 0 0 .7rem; font-size: .84rem; line-height: 1.55; color: var(--ink-soft); }
.cx-letters { list-style: none; margin: 0 0 .7rem; padding: 0; display: grid; gap: .4rem; }
.cx-letters li { display: grid; grid-template-columns: auto 1fr; gap: .55rem; align-items: start; font-size: .8rem; line-height: 1.45; color: var(--ink-soft); }
.cx-letters strong { color: var(--ink); }
.cx-terms li { grid-template-columns: 1fr; }
.cx-pair { display: inline-flex; gap: .15rem; }
.cx-pair b {
  display: grid; place-items: center; width: 1.35rem; height: 1.35rem; border-radius: 6px; font-size: .74rem;
  color: var(--ink-soft); background: color-mix(in srgb, var(--line) 60%, var(--bg-alt));
}
.cx-pair b.is-yours { color: #fff; background: var(--accent); }
.cx-yours { font-size: .84rem; line-height: 1.5; padding: .6rem .7rem; border-radius: 12px; }
.cx-yours { margin-bottom: 0; color: var(--ink); font-weight: 600; background: var(--accent-soft); }
.cx-label {
  display: block; margin-bottom: .2rem; font-size: .66rem; font-weight: 800; letter-spacing: .1em; text-transform: uppercase;
  color: var(--accent);
}
#view-profile.profile-structured #psyche-card [data-cx] { cursor: help; }
/* Where to start: the ring pulses until the reader points at anything. */
.psyche-card.pc-hint .pc-sconf { border-radius: 999px; animation: pc-pulse 1.8s ease-in-out infinite; }
@keyframes pc-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(123, 63, 160, .45); }
  50% { box-shadow: 0 0 0 22px rgba(123, 63, 160, 0); }
}
@media (prefers-reduced-motion: reduce) { .psyche-card.pc-hint .pc-sconf { animation: none; } .cx-pop { transition: none; animation: none; } }
/* On a phone the card and its panel become one white box, read top to
   bottom: the title, the card, how to learn more, then the three actions.
   The panel's own boxes dissolve (display: contents) so their pieces can be
   ordered around the card; the one-line introduction and "Tap to open full
   screen" go — Enlarge is the way to full screen here. */
@media (max-width: 719px) {
  #view-profile.profile-structured .profile-top {
    display: flex; flex-direction: column; align-items: stretch; gap: .8rem; position: relative; overflow: hidden;
    background: var(--bg-alt); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow);
    padding: 1rem;
  }
  #view-profile.profile-structured .profile-top::before {
    content: ""; position: absolute; inset: 0 0 auto 0; height: 3px; opacity: .8;
    background: linear-gradient(90deg, var(--accent), var(--accent-2));
  }
  #view-profile.profile-structured #profile-side, #view-profile.profile-structured .cx,
  #view-profile.profile-structured .cx-home { display: contents; }
  #view-profile.profile-structured #profile-side::before { display: none; }
  #view-profile.profile-structured #psyche-card-section {
    order: 2; border: 0; box-shadow: none; background: transparent; border-radius: 14px; margin: 0;
  }
  #view-profile.profile-structured #psyche-card-section::before { display: none; }
  #view-profile.profile-structured .psyche-card-hint { display: none; }
  #view-profile.profile-structured .psyche-card-slot { cursor: default; }
  .cx-home-title { order: 1; margin: 0; text-align: center; }
  .cx-home-intro { display: none; }
  .cx-home-hint { order: 3; justify-content: center; text-align: left; }
  .cx-tools { order: 4; }
  .cx-compat { order: 4; }
  .cx-stage { order: 5; }
}
/* On a phone the explanation rises from the foot of the screen instead. */
@media (max-width: 719px) {
  .cx-stage { min-height: 0; flex: none; }
  .cx-pop { position: fixed; left: 12px; right: 12px; top: auto; bottom: 12px; transform: none !important; z-index: 30;
    max-height: 72vh; overflow-y: auto; padding-right: 2.4rem; }
  .cx-arrow { display: none; }
  .cx-close { display: block; }
  #view-profile.profile-structured #psyche-card [data-cx] { cursor: pointer; }
}
/* The card full screen on a phone: no download or share, a line saying to
   tap instead, and each tapped part explained in a sheet — at the foot of
   the screen, or at the top when the part sits in the lower half. */
.card-dialog-tip { display: none; }
/* The card as large as the phone allows: a slim margin, clear of the notch
   and the home bar, and the line under it kept short. */
.card-dialog.is-guided {
  box-sizing: border-box;
  padding: max(10px, env(safe-area-inset-top)) max(10px, env(safe-area-inset-right)) 0 max(10px, env(safe-area-inset-left));
}
.card-dialog.is-guided .card-dialog-bar { gap: 0; padding: 6px 0 max(8px, env(safe-area-inset-bottom)); }
.card-dialog.is-guided .card-dialog-close { top: 6px; margin-top: 0; right: 4px; }
.card-dialog.is-guided .pc-story-in { justify-content: space-between; }
.card-dialog.is-guided .pc-sfoot { margin-top: 0; }
.card-dialog.is-guided .card-dialog-actions { display: none; }
.card-dialog.is-guided .card-dialog-tip {
  display: flex; align-items: center; justify-content: center; min-height: 44px; margin: 0; padding: 0 52px;
  white-space: nowrap; color: #fff; font-size: clamp(.8rem, 3.8vw, .95rem); font-weight: 600; letter-spacing: .01em; text-align: center;
}
.card-dialog.is-guided .card-dialog-tip::before { content: "✨"; margin-right: .45rem; }
.card-dialog.is-guided #psyche-card-full [data-cx] { cursor: pointer; }
.card-dialog-pop {
  position: fixed; left: 12px; right: 12px; top: auto; bottom: 12px; z-index: 5;
  max-height: 46vh; overflow-y: auto; padding-right: 2.4rem;
  box-shadow: 0 18px 50px rgba(0, 0, 0, .45);
}
.card-dialog-pop.at-top { top: max(12px, env(safe-area-inset-top)); bottom: auto; }
.card-dialog-pop .cx-close { display: block; }
.card-dialog-pop .cx-arrow { display: none; }
/* On a phone, over the card, a note is one lavender surface, unlike the
   card's white, inset from the card's edges and outlined in purple: no band,
   no boxes, no rules inside it. Its parts are told apart by space and a small
   heading each, nothing else. Beside the card on a laptop (.at-side) it keeps
   the report panel's own white note. */
.card-dialog-pop:not(.at-side) {
  --pop-tint: color-mix(in srgb, var(--accent-soft) 85%, var(--bg-alt));
  padding: 1.1rem 1.15rem 1.15rem; border: 2px solid var(--accent); border-radius: 18px; background: var(--pop-tint);
  box-shadow: 0 22px 60px rgba(0, 0, 0, .45);
}
.card-dialog-pop:not(.at-side) .cx-pop-head { margin: 0 0 .7rem; padding-right: 2rem; }
.card-dialog-pop:not(.at-side) .cx-pop-icon { width: auto; height: auto; background: none; font-size: 1.35rem; }
.card-dialog-pop:not(.at-side) .cx-pop-title { font-size: 1.1rem; color: var(--ink); }
.card-dialog-pop:not(.at-side) .cx-close { top: .75rem; right: .7rem; background: none; color: var(--ink-soft); }
.card-dialog-pop:not(.at-side) .cx-about { margin: 0 0 1rem; color: var(--ink); }
.card-dialog-pop:not(.at-side) .cx-letters { margin-bottom: 1rem; }
/* The reader's letter of each pair in colour, the other faint — no chips. */
.card-dialog-pop:not(.at-side) .cx-pair b { width: auto; height: auto; min-width: .9rem; background: none; color: var(--ink-soft); opacity: .55; font-size: .85rem; }
.card-dialog-pop:not(.at-side) .cx-pair b.is-yours { background: none; color: var(--accent); opacity: 1; }
.card-dialog-pop:not(.at-side) .cx-yours {
  margin: 0; padding: 0; border: 0; border-radius: 0; background: none; box-shadow: none; color: var(--ink);
}
.card-dialog-pop:not(.at-side) .cx-label { color: var(--accent); margin-bottom: .25rem; }
.card-dialog-pop:not(.at-side) .cx-arrow { background: var(--pop-tint); border: 0; }
/* Beside the card on a laptop, level with the part and pointing at it. */
.card-dialog-pop.at-side { transition: top .2s ease; }
.card-dialog-pop.at-side .cx-arrow { display: block; }
.card-dialog-pop.at-side .cx-close { display: none; }
@media (hover: none) { .card-dialog-pop.at-side .cx-close { display: block; } }
/* The sample's card full screen: how to learn more, under the card. */
.sample-card-tip { display: none; }
.card-dialog.is-explained .sample-card-tip {
  display: flex; align-items: center; justify-content: center; margin: 0; padding: 4px 16px max(14px, env(safe-area-inset-bottom));
  color: #fff; font-size: clamp(.8rem, 3.8vw, .95rem); font-weight: 600; letter-spacing: .01em; text-align: center;
}
.card-dialog.is-explained .sample-card-tip::before { content: "✨"; margin-right: .45rem; }
.card-dialog.is-explained #sample-psyche-card-full [data-cx] { cursor: pointer; }
/* On a phone the card reaches the top corner, where the cross would vanish
   over its white: the cross moves down beside the line, as on the reader's own. */
@media (max-width: 719px) {
  .card-dialog.is-explained .sample-card-tip { min-height: 44px; padding: 4px 56px max(10px, env(safe-area-inset-bottom)); }
  .card-dialog.is-explained .sample-card-close {
    top: auto; bottom: max(10px, env(safe-area-inset-bottom)); width: 44px; height: 44px;
    border-color: rgba(255, 255, 255, .3); background: rgba(255, 255, 255, .12); color: #fff;
  }
}
/* Part 05: a heading over two boxes of their own, not a disclosure. */
.appendix-part { margin-top: 1.4rem; }
.appendix-head { margin: 0 0 .9rem; padding: 0 .3rem; }
.appendix-head .part-title { margin: 0; }
.appendix-part > .section-card { margin-bottom: 1rem; }
/* Who wrote the report and when: a quiet pill per line, closing the page. */
.provenance { display: flex; justify-content: center; margin: 1.4rem 0 .4rem; }
.provenance-box {
  display: grid; gap: .35rem; padding: .6rem 1rem; border-radius: 14px;
  border: 1px solid var(--line); background: var(--bg-alt);
}
.provenance-item { display: flex; align-items: center; gap: .5rem; font-size: .78rem; color: var(--ink-soft); }
.provenance-item b { color: var(--ink); font-weight: 600; white-space: nowrap; }
/* A free report's foot is one line: Delete everything on the left, who wrote
   the card on the right, the two boxes the same height. Wraps to two lines
   only when the screen is too narrow for both. */
#view-profile.profile-free .profile-foot {
  display: flex; flex-wrap: wrap; align-items: stretch; justify-content: space-between; gap: .7rem; margin-top: 1.4rem;
}
#view-profile.profile-free .profile-foot .cta-row { margin: 0; align-items: stretch; }
#view-profile.profile-free .profile-foot .cta-row .btn { min-height: 0; padding-block: .45rem; }
#view-profile.profile-free .profile-foot .provenance { margin: 0; }
#view-profile.profile-free .profile-foot .provenance-box { align-content: center; }
/* On a phone there is no room for both at full width, so the delete button
   becomes its red bin alone — its name still read out, and still asked about
   before anything goes — and the stamp takes the rest of the line. */
@media (max-width: 559px) {
  #view-profile.profile-free .profile-foot { flex-wrap: nowrap; }
  #view-profile.profile-free .profile-foot .cta-row { flex: none; }
  #view-profile.profile-free .profile-foot .cta-row .btn { padding-inline: .8rem; }
  #view-profile.profile-free .profile-foot .cta-label {
    position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap;
  }
  #view-profile.profile-free .profile-foot .provenance { flex: 1 1 auto; min-width: 0; }
  #view-profile.profile-free .profile-foot .provenance-box { width: 100%; }
}
.provenance-icon {
  display: grid; place-items: center; flex: none; width: 1.25rem; height: 1.25rem; border-radius: 999px; font-size: .7rem; color: #fff;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
}
.profile-top > .section-card { margin-bottom: 0; }
.free-method .confidence-meter { margin: .2rem 0 .7rem; }
.free-method .free-score { margin: 0 0 .5rem; font-size: .95rem; }
.free-method .free-why { margin: 0 0 .8rem; font-size: .9rem; color: var(--ink-soft); line-height: 1.5; }
.free-method .trait-evidence { margin: .3rem 0 0; }
/* The review's de-identification note: what is taken out before anything is sent. */
.deid-note {
  display: flex; gap: .5rem; align-items: flex-start; margin: 0 0 .6rem; padding: .45rem .65rem;
  border-radius: 12px; border: 1px solid color-mix(in srgb, var(--good) 45%, transparent);
  background: color-mix(in srgb, var(--good-soft) 75%, var(--bg-alt));
}
.deid-icon { font-size: .9rem; line-height: 1.35; flex: none; }
.deid-note p { margin: 0; font-size: .76rem; line-height: 1.4; color: var(--ink); }
.deid-note strong { color: var(--good); }
/* Beside the plan's part in the unlock offer: a section it keeps secret, as
   a panel of its own. The two share a row where there is room for it. */
@media (min-width: 560px) {
  .unlock-parts { grid-template-columns: 1fr 1fr; }
  .unlock-parts > .unlock-part:not(.unlock-part-half) { grid-column: 1 / -1; }
  .unlock-part-half .premium-tier-list { grid-template-columns: 1fr; }
}
.unlock-secret {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: .35rem;
  padding: 1rem 1.1rem; border-radius: 14px; text-align: center;
  border: 1.5px dashed color-mix(in srgb, var(--accent) 55%, transparent);
  background: linear-gradient(135deg, color-mix(in srgb, var(--accent-soft) 75%, var(--bg-alt)), color-mix(in srgb, var(--accent-2) 12%, var(--bg-alt)));
}
.unlock-secret-icon { font-size: 1.9rem; line-height: 1; }
.unlock-secret strong { font-size: 1rem; color: var(--accent); }
.unlock-secret span:last-child { font-size: .84rem; line-height: 1.45; color: var(--ink-soft); max-width: 22rem; }
