/* Onboarding + paywall — "paywall flow 1" (app/onboarding.js, architecture.md §35).
   The one place in the app allowed to feel SOFT: typed questions, Apple-style glass
   inputs, rounded chips, a warm gradient wash. Everything else keeps the near-sharp
   system — this flow is a first impression, not a scene. */

.ob2 {
  position: fixed; inset: 0; z-index: 10;
  display: flex; flex-direction: column;
  /* The ground is a DRAWING (app/OB/mainBg.png): cream paper with pastel blobs in the
     corners, a ribbon heart at the top and flowers at the foot — the same hand as the
     illustrations, which four radial gradients in the partners' colours never were. The
     flat colour under it is the paper the drawing is painted on, so a slow decode shows
     the right tone rather than white. */
  /* …and a WASH of that same paper over its top 31%, fading out by 45%: the drawing's pink
     ribbon heart and corner blob run straight through where every plain screen's heading
     sits, and black type over a pink stroke read as a smudge. #FCFAF3 is the drawing's own
     paper (measured flat, sd < 1.1), so the wash has no edge — the decoration just isn't
     there under the words. The flowers and side blobs below are untouched. */
  background:
    linear-gradient(180deg, #fcfaf3 0%, #fcfaf3 31%, rgba(252, 250, 243, 0) 45%),
    #fcfaf3 url('/app/assets/ob/mainbg.webp') center / cover no-repeat;
  overflow: hidden;
}
/* The card every answer, field, tile and perk sits on. OPAQUE, and that is load-bearing:
   it was 72% white over the ground, and on a screen with enough answers to scroll, the
   question slid UNDER the cards and read straight through them — two sentences on top of
   each other. Nothing here is translucent any more. No backdrop-filter either: a live blur
   behind a dozen cards is what a slow GPU drops frames on. */
.ob2-chip, .ob2-input-row, .ob2-game, .ob2-perk {
  background: #fff;
  border: 1px solid rgba(23, 24, 28, 0.07);
  box-shadow: 0 8px 24px rgba(90, 60, 70, 0.08);
}

/* ---- chrome: back + progress ----
   Floats OVER the screen rather than sitting in a band above it, so an illustrated screen
   can run its picture to the very top edge. Both the arrow and the bar already carry their
   own white backing, so they read over a drawing. Screens without art pad past it. */
.ob2-head {
  position: absolute; top: 0; left: 0; right: 0; z-index: 4;
  display: flex; align-items: center; gap: 12px;
  padding: calc(12px + env(safe-area-inset-top)) 16px 8px;
}
.ob2-back {
  flex: none;
  width: 44px; height: 44px;
  display: inline-flex; align-items: center; justify-content: center;
  padding: 0; border-radius: 50%;
  background: rgba(255, 255, 255, 0.72);
  border: 1px solid rgba(255, 255, 255, 0.95);
  box-shadow: 0 4px 14px rgba(90, 60, 70, 0.08);
  color: var(--ink);
}
.ob2-back:active { transform: scale(0.94); }
.ob2-bar {
  flex: 1; height: 6px; border-radius: 3px;
  background: rgba(255, 255, 255, 0.75);
  box-shadow: inset 0 0 0 1px rgba(23, 24, 28, 0.04);
  overflow: hidden;
}
.ob2-bar-fill {
  height: 100%; width: 0;
  border-radius: 3px;
  background: linear-gradient(90deg, var(--pink), var(--blue));
  transition: width 420ms cubic-bezier(.2, 1, .4, 1);
}

/* ---- screens + transitions ---- */
.ob2-stage { flex: 1; position: relative; min-height: 0; }
.ob2-screen {
  position: absolute; inset: 0;
  display: flex; flex-direction: column;
  padding: calc(62px + env(safe-area-inset-top)) 22px calc(22px + env(safe-area-inset-bottom));
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}
.ob2-in { animation: ob2-in 380ms cubic-bezier(.2, 1, .4, 1) both; }
.ob2-out { animation: ob2-out 180ms ease both; pointer-events: none; }
.ob2-in-still { opacity: 1; }
@keyframes ob2-in { from { opacity: 0; transform: translateY(16px); } }
@keyframes ob2-out { to { opacity: 0; transform: translateY(-10px); } }

.ob2-qscreen { justify-content: flex-start; gap: 22px; }
.ob2-qscreen.ob2-center { justify-content: center; text-align: center; align-items: center; }
/* pushes a screen's button to the bottom; the content stays up under the question */
.ob2-grow { flex: 1 1 auto; min-height: 4px; }
.ob2-grow.is-low { flex-grow: 1.6; }
.ob2-qscreen.is-input .ob2-q { margin-top: 0; min-height: 0; }

/* the typed question */
.ob2-q {
  margin: 4vh 0 0;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(26px, 7vw, 32px);
  letter-spacing: -0.025em;
  line-height: 1.2;
  min-height: 2.4em; /* no reflow while it types */
  max-width: 22ch;
}
/* a name the user typed, in the brand's two colours (deep enough to hold 3:1 as large type) */
.ob2-name {
  background: linear-gradient(100deg, #d63f78, #3e7be0);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
}
.ob2-center .ob2-q { margin-top: 0; }
.ob2-caret {
  display: inline-block; width: 2px; height: 0.95em;
  margin-left: 2px; vertical-align: -0.12em;
  background: var(--pink);
  animation: ob2-blink 0.9s steps(1) infinite;
}
@keyframes ob2-blink { 50% { opacity: 0; } }

.ob2-hint { margin: -12px 0 0; color: var(--ink-soft); font-size: 15px; line-height: 1.45; }

/* staged reveals */
.ob2-hide { opacity: 0; pointer-events: none; }
.ob2-reveal { animation: ob2-rise 460ms cubic-bezier(.2, 1, .4, 1) both; pointer-events: auto; }
@keyframes ob2-rise { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }

/* text input row */
.ob2-input-row {
  display: flex; align-items: center; gap: 8px;
  min-height: 62px;
  border-radius: 18px;
  padding: 7px 7px 7px 18px;
  transition: border-color 200ms ease, box-shadow 200ms ease;
}
.ob2-input-row:focus-within { border-color: rgba(214, 63, 120, 0.45); box-shadow: 0 10px 28px rgba(90, 60, 70, 0.12), 0 0 0 4px rgba(255, 123, 163, 0.14); }
.ob2-input {
  flex: 1; min-width: 0;
  border: 0; background: none; outline: none;
  font: inherit; font-size: 17px; color: var(--ink);
  padding: 12px 0;
}
.ob2-input::placeholder { color: rgba(91, 94, 104, 0.55); }
.ob2-send {
  flex: none;
  width: 46px; height: 46px;
  display: flex; align-items: center; justify-content: center;
  border: 0; border-radius: 14px;
  background: var(--ink); color: #fff;
  transition: opacity 180ms ease, transform 180ms ease;
}
.ob2-send:disabled { opacity: 0.18; }
.ob2-send:not(:disabled):active { transform: scale(0.92); }

/* answer cards: the 3D emoji (app/assets/emoji, Fluent Emoji 3D), the label and a mark on
   the right (a radio for pick-one, a checkbox for pick-many). A pick is ONE solid pink —
   the edge, a blush fill and the tick — the pink-to-blue gradient went muddy purple at
   22px. */
/* 56px rows, 8px apart: a list that runs past the fold must PEEK its next row, or the
   answer down there is invisible — at 62/10 "I'm single" sat wholly below the fold on the
   status screen once its picture grew. 56 is still a generous tap target. */
.ob2-chips { display: flex; flex-direction: column; gap: 8px; }
.ob2-reveal .ob2-chip, .ob2-chips.ob2-reveal .ob2-chip { animation: ob2-rise 420ms cubic-bezier(.2, 1, .4, 1) both; animation-delay: var(--d, 0ms); }
/* No emoji on an answer (2026-10, the user's call — they had been put back once). What
   carries the row instead is a short colour bar down its left edge, one tone per position
   out of the flow's own palette, and a bigger label: the list keeps its rhythm and its
   colour without a picture per line, and a long answer no longer wraps around a 34px slot.
   The bar grows and the label steps across when the row is picked. */
.ob2-chip {
  position: relative;
  display: flex; align-items: center; gap: 14px;
  width: 100%; min-height: 56px;
  padding: 11px 18px 11px 22px;
  border-radius: 18px;
  font-family: var(--font-body); font-weight: 600; font-size: 17px;
  color: var(--ink);
  text-align: left;
  overflow: hidden;
  transition: transform 160ms ease, background-color 200ms ease, border-color 200ms ease, box-shadow 200ms ease;
}
.ob2-chip::before {
  content: ''; position: absolute; left: 0; top: 50%;
  width: 5px; height: 26px; transform: translateY(-50%);
  border-radius: 0 5px 5px 0;
  background: var(--chip-c, #ec5f93);
  transition: height 240ms cubic-bezier(.2, 1, .4, 1), width 240ms ease;
}
.ob2-chip.is-picked::before { height: 100%; width: 6px; }
.ob2-chip:active { transform: scale(0.98); }
.ob2-chip-t { flex: 1; min-width: 0; transition: transform 240ms cubic-bezier(.2, 1, .4, 1); }
.ob2-chip.is-picked .ob2-chip-t { transform: translateX(3px); }
.ob2-chip-mark {
  flex: none;
  width: 22px; height: 22px;
  border-radius: 50%;
  border: 2px solid rgba(23, 24, 28, 0.15);
  transition: background-color 200ms ease, border-color 200ms ease;
}
.ob2-chips.is-multi .ob2-chip-mark { border-radius: 7px; }
.ob2-chip.is-picked {
  background: #fff6f9;
  border-color: #ec5f93;
  box-shadow: 0 10px 28px rgba(90, 60, 70, 0.12), 0 0 0 3px rgba(236, 95, 147, 0.14);
  animation: ob2-pop 300ms cubic-bezier(.2, 1.4, .4, 1);
}
.ob2-chip.is-picked .ob2-chip-mark {
  border-color: #ec5f93;
  background: #ec5f93 url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"><path d="M6 12.5l4 4L18 8" stroke="white" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg>') center / 13px no-repeat;
}
@keyframes ob2-pop { 40% { transform: scale(1.02); } }
.ob2-other-row { padding-left: 18px; }
.ob2-input--other { font-size: 16px; padding: 13px 0; }

/* the one button at the foot of a screen */
.ob2-cta {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  width: 100%; min-height: 56px;
  padding: 0 20px;
  border: 0; border-radius: 16px;
  background: var(--ink); color: #fff;
  font-family: var(--font-body); font-weight: 700; font-size: 16.5px;
  letter-spacing: -0.01em;
  box-shadow: 0 10px 26px rgba(23, 24, 28, 0.16);
  transition: transform 160ms ease, opacity 200ms ease, background-color 220ms ease, color 220ms ease;
}
.ob2-cta:active { transform: scale(0.98); }
/* Disabled is its own pair of OPAQUE colours, never opacity: at 28% opacity the button let
   the screen show through it and its label dropped to nothing — "I can't see Continue".
   It still has to read as not-yet, so it is a pale grey with a mid-grey label — 4.6:1,
   measured (the first pick, #6f6d78, came out at 3.9). */
.ob2-cta:disabled { background: #e2e1e6; color: #64626c; box-shadow: none; }
.ob2-cta--float { flex: none; }
/* THE CTA BAR. A screen's floating button sits in a bar (ctaBar() in onboarding.js), and the
   BAR is what sticks to the foot of the screen and paints the backing under it — edge to
   edge, down past the home indicator — so a long list scrolls away beneath it and nothing
   shows through in halves. The backing used to be the button's own `::after` at z-index
   -1, and a sticky element always makes its own stacking context, so that "behind" was
   inside the button: it painted OVER the button's dark fill and every Continue went
   white-on-paper. Keep the backing on this separate element.
   Its negative margins cancel the screen's (or sheet's) side and bottom padding so it spans
   the full width; its own padding puts the spacing back. */
.ob2-cta-bar {
  position: sticky; bottom: 0; z-index: 2;
  flex: none;
  display: flex; justify-content: center;
  margin: 10px -22px calc(-22px - env(safe-area-inset-bottom));
  padding: 14px 22px calc(14px + env(safe-area-inset-bottom));
}
/* On a plain screen it fades up out of the drawn ground rather than slicing it. The
   negative `bottom` is not a typo: a sticky box sticks inside its scroll container's
   PADDING, and a plain screen has 22px (+ the home indicator) of it, so `bottom: 0` left
   the bar floating 22px above the foot with the screen showing under it. On an
   illustrated screen the scroll container has no padding and 0 is right. */
.ob2-qscreen > .ob2-cta-bar {
  bottom: calc(-22px - env(safe-area-inset-bottom));
  background: linear-gradient(180deg, rgba(249, 246, 241, 0) 0%, #f9f6f1 18px);
}
/* while the button is still hidden (it reveals after the list), so is its backing */
.ob2-cta-bar:has(> .ob2-hide) { background: none; }

/* ============ welcome ============
   The sign-up-sheet pattern: the window loop full-bleed on top with the brand over its
   foot, and a white sheet rising over it with the two actions. The screen covers the
   WHOLE viewport: the hidden step header still takes its 68px, and the loop used to start
   under it on a pink band. It enters on a plain fade, because the shared slide-up would
   pull the full-bleed picture off the top edge for a moment. */
.ob2-welcome {
  position: fixed; inset: 0; z-index: 2;
  padding: 0; overflow: hidden;
  background: #b59e94; /* the still's own tone, under it while it decodes */
}
.ob2-welcome.ob2-in { animation-name: ob2-fade-in; }
.ob2-welcome.ob2-out { animation-name: ob2-fade-out; }
@keyframes ob2-fade-in { from { opacity: 0; } }
@keyframes ob2-fade-out { to { opacity: 0; } }
.ob2-hero {
  position: relative;
  flex: 1 1 auto; min-height: 240px;
  display: flex; flex-direction: column; justify-content: flex-end;
  padding: 0 26px 54px; /* 28px of it sits under the sheet's curve */
  overflow: hidden;
}
.ob2-bg {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover; object-position: 50% 42%;
}
/* The loop waits, invisible, over its own first frame until a frame is really on screen
   (onboarding.js), so no native play button or start button is ever seen. */
.ob2-bg-live { opacity: 0; transition: opacity 800ms ease; }
.ob2-bg-live.is-on { opacity: 1; }
.ob2-bg-live::-webkit-media-controls,
.ob2-bg-live::-webkit-media-controls-start-playback-button,
.ob2-bg-live::-webkit-media-controls-overlay-play-button { display: none !important; -webkit-appearance: none; }
.ob2-welcome-body {
  position: relative;
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  text-align: center;
  animation: ob2-rise 700ms 200ms cubic-bezier(.2, 1, .4, 1) both;
}
.ob2-word, .splash-word {
  font-family: "Didot", "Bodoni 72", "Playfair Display", Georgia, serif;
  font-weight: 500;
  font-size: clamp(56px, 17vw, 84px);
  letter-spacing: -0.02em;
  line-height: 1.05;
  color: var(--ink);
}
/* The welcome is the FILM and nothing else (app/assets/ob/welcome.mp4): the wordmark,
   "More ways to be closer" and the line under it are painted into it, so the screen adds no
   type of its own — it used to, over the drawn ones. Anchored to the TOP, because that is
   where the brand is painted; the foot of the frame is what slides under the sheet. No
   scrim and no veil: nothing of ours sits over the picture but the language pill. */
.ob2-hero.is-film { padding: 0; }
/* A whole-page drawing (the film, the sign-in) is 9:20 and the phone is about 9:18.5, so
   `cover` spills ~9% of the HEIGHT. Anchored to the TOP, all of that falls off the bottom —
   which the sheet covers anyway — so nothing anyone can see is cropped, and the wordmark
   painted across the top is never touched. */
/* The 2026-10 redraws are 9:16 — WIDER than a phone — with no painted wordmark and an
   empty band of paper across the top. So they are CONTAINED and set flush with the bottom:
   full width, nothing cropped on any side, and the paper the phone has left over above
   them (128px on a 448x921 screen) joins the drawing's own empty top as one band, which is
   where the logo goes. The screen's ground is the drawing's paper (#FAFBF6, measured), so
   the join doesn't show. `cover` here trimmed 7% off each side, through the couple. */
.ob2-welcome.has-film { background: #fafbf6; }
.ob2-welcome.has-film .ob2-bg { object-fit: contain; object-position: 50% 100%; }
/* …except the WELCOME FILM, which the user wants FULL — edge to edge, the logo over the
   film's own empty top rather than in a band above it. A 9:16 film on a ~9:18.5 phone
   loses ~7% off each SIDE under `cover` and nothing off the top or bottom, so the empty
   paper the film is drawn with across its top is still where the lockup sits. The
   sign-in followed (the user's next ask: "the same as the video") — same 7% off the
   sides, nothing off the top or bottom. */
.ob2-welcome.has-film:not(.ob2-signin) .ob2-bg,
.ob2-welcome.ob2-signin .ob2-bg { object-fit: cover; object-position: 50% 0%; }

/* Angie's lockup, top centre, in that band — the splash's dots and wordmark. */
.ob2-logo {
  /* centred by spanning the width, NOT by translateX(-50%): the entrance animation animates
     `transform`, and its end state wiped the -50% — the lockup sat with its left edge on the
     centre line. */
  position: absolute; z-index: 2;
  top: calc(54px + env(safe-area-inset-top)); left: 0; right: 0;
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  pointer-events: none;
  animation: ob2-rise 700ms 150ms cubic-bezier(.2, 1, .4, 1) both;
}
.ob2-logo-dots { display: flex; gap: 7px; }
.ob2-logo-dots i { display: block; width: 11px; height: 11px; border-radius: 50%; }
.ob2-logo-dots i.p { background: #FF7BA3; }
.ob2-logo-dots i.b { background: #5FA0FF; }
.ob2-logo .ob2-logo-word { font-size: 46px; line-height: 1; }
.ob2-logo-line {
  margin-top: 4px;
  font-family: var(--font-body); font-size: 14.5px; font-weight: 600;
  color: var(--ink-soft); white-space: nowrap;
}
@media (prefers-reduced-motion: reduce) { .ob2-logo { animation: none; } }
/* A whole-page drawing — the welcome film and the sign-in's illustration — gets the WHOLE
   screen, with the sheet floating over its foot. Any box that isn't the phone's own aspect
   crops it, and these are composed at 9:16: wider than a modern phone, so what cover trims
   is a sliver off each side and NOTHING off the top, where both of them paint the wordmark.
   NOTE: never give .has-film a `position` of its own — .ob2-welcome is `fixed; inset: 0`,
   and relative-positioning it drops the inset, collapsing the screen to the sheet's height
   (the film then showed as a 198px band). */
.ob2-welcome.has-film > .ob2-hero { position: absolute; inset: 0; min-height: 0; }
.ob2-welcome.has-film > .ob2-sheet { position: relative; margin-top: auto; }

.ob2-tag { margin: 0; color: rgba(255, 255, 255, 0.94); font-size: 16px; font-weight: 500; text-shadow: 0 1px 12px rgba(28, 20, 24, 0.35); }
/* ---- the illustrations (app/assets/ob, tools/onboarding-art.py) ----------------------
   An illustrated screen is the app-store shape: the drawing FULL-BLEED across the top, the
   question and its answers on a white sheet whose rounded top laps over the picture. (It
   was a small contained picture box UNDER the answers, which read as a form with a
   thumbnail stuck on the end.) The hero takes whatever the sheet leaves and is
   bottom-anchored, because tools/onboarding-art.py crops every drawing with the couple at
   the foot of the frame — so what a short screen cuts is the empty sky, never a face. */
/* THE WHOLE SCREEN SCROLLS — picture, question and answers in one flow. It used to be a
   fixed picture over a sheet that scrolled inside ITSELF, and on a list long enough to
   scroll, the question slid up behind the sheet's own top edge and came out sliced through
   the middle of its letters (reported twice from the device). Nothing here is clipped any
   more: if it doesn't fit, you scroll past it. */
.ob2-screen.has-art {
  padding: 0;
  gap: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  justify-content: flex-start;
}
/* The box is the drawing's OWN shape (tools/onboarding-art.py normalises every one to 4:5),
   so the picture fills it exactly and NOTHING is cropped — the user asked twice, because a
   `cover` box was eating the top of the frame (a thought bubble, the top of a head). On a
   screen too short for the full box, the screen scrolls — the box does NOT shrink, because
   a shorter box is a narrower drawing between two bars. */
.ob2-art {
  flex: 0 0 auto;        /* never squeezed — a squeezed box is what put bars beside the art */
  width: 100%;
  aspect-ratio: var(--art-ar, 4 / 5);   /* the card's own shape: 4:5, or ART_AR's taller one */
  position: relative;
  container-type: size;  /* so anything drawn ON the picture can size itself in cqh/cqw */
  background: linear-gradient(180deg, #fdf3f6, #eef3fb);
}
.ob2-art img {
  display: block;
  width: 100%; height: 100%;
  object-fit: contain;
  object-position: 50% 100%;
}
.ob2-qsheet {
  position: relative;
  flex: 1 0 auto;        /* fills what's left when the content is short, grows past it when
                            it isn't — the SCREEN scrolls, never this box on its own */
  margin-top: -26px;
  padding: 24px 22px calc(22px + env(safe-area-inset-bottom));
  border-radius: 26px 26px 0 0;
  background: var(--paper);
  box-shadow: 0 -10px 30px rgba(90, 60, 70, 0.1);
  display: flex; flex-direction: column; gap: 16px;
  animation: ob2-sheet-up 520ms 120ms cubic-bezier(.2, 1, .4, 1) both;
}
@keyframes ob2-sheet-up { from { opacity: 0; transform: translateY(22px); } }
@media (prefers-reduced-motion: reduce) { .ob2-qsheet { animation: none; } }
/* inside the sheet the question is a heading, not a hero line */
.ob2-qsheet .ob2-q { margin: 0; min-height: 0; }
/* In a sheet the bar is the sheet's own paper, so it reads as the sheet's foot — and it
   IS the foot: `margin-top: auto` sends it to the bottom when the sheet has room to spare
   (the stat screen, once its copy was cut to three lines, had its button floating
   mid-sheet over an empty band). When the list overflows, auto resolves to 0 and the bar's
   own padding keeps it off the last answer. */
.ob2-qsheet > .ob2-cta-bar { background: var(--paper); margin-top: auto; }
/* .ob2-center centres a plain screen's column, which also shrank the hero to its image's
   width — an illustrated screen stays full-bleed and centres only the sheet's text. */
.ob2-screen.has-art.ob2-center { align-items: stretch; }
.ob2-center.has-art .ob2-qsheet { text-align: center; align-items: stretch; }
.ob2-center.has-art .ob2-qsheet .ob2-q { text-wrap: balance; }
/* a centred sheet with room to spare splits it above and below its copy, so a short message
   sits in the middle of the paper instead of hanging off the top over an empty band */
.ob2-center.has-art .ob2-qsheet > .ob2-q:first-child { margin-top: auto; }

/* ---- the gender step ------------------------------------------------------------------
   The one question whose ANSWERS are the drawings, so it keeps its own shape rather than
   the hero-and-sheet the others use (a hero above would be the same picture twice): two
   halves of one illustration (tools/onboarding-art.py cuts app/OB/Gender.png either side
   of its diagonal into boxes of the same size), each pointing at themselves. They fly in
   from their own edge, and the one tapped lifts while the other steps back. */
.ob2-gender-screen { justify-content: center; }
.ob2-genders { display: flex; gap: 12px; justify-content: center; align-items: stretch; margin-top: 18px; }
.ob2-gender {
  flex: 1 1 0; min-width: 0; max-width: 200px;
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  padding: 0 0 12px;
  border: 2px solid rgba(255, 255, 255, 0.95);
  border-radius: 22px;
  background: rgba(255, 255, 255, 0.72);
  box-shadow: 0 8px 24px rgba(90, 60, 70, 0.1);
  color: var(--ink);
  font: inherit;
  cursor: pointer;
  overflow: hidden;
  -webkit-tap-highlight-color: transparent;
  transition: transform 300ms cubic-bezier(.2, 1, .4, 1), box-shadow 300ms ease,
              border-color 220ms ease, opacity 300ms ease;
  animation: ob2-gender-in 600ms cubic-bezier(.2, 1, .4, 1) both;
}
.ob2-gender.is-male { animation-name: ob2-gender-in-l; }
.ob2-gender.is-female { animation-name: ob2-gender-in-r; animation-delay: 90ms; }
.ob2-gender img { display: block; width: 100%; height: auto; }
.ob2-gender-cap { font-size: 15px; font-weight: 800; letter-spacing: -0.01em; }
.ob2-gender:active { transform: scale(0.97); }
.ob2-genders.is-done .ob2-gender { opacity: 0.35; transform: scale(0.94); }
.ob2-genders.is-done .ob2-gender.is-picked {
  opacity: 1; transform: scale(1.04) translateY(-6px);
  border-color: var(--pink);
  box-shadow: 0 16px 36px rgba(214, 63, 120, 0.26);
}
@keyframes ob2-gender-in-l { from { opacity: 0; transform: translateX(-26px) rotate(-3deg); } }
@keyframes ob2-gender-in-r { from { opacity: 0; transform: translateX(26px) rotate(3deg); } }
@media (prefers-reduced-motion: reduce) {
  .ob2-gender { animation: none; transition: none; }
}

/* the language picker, top right of the welcome picture: the current language's round
   flag in a small glass pill (client/core/langPicker.js styles the pill and its drop-down
   list); this only places it */
.ob2-lang {
  position: absolute; z-index: 2;
  top: calc(env(safe-area-inset-top) + 12px); right: 14px;
  animation: ob2-rise 700ms 300ms cubic-bezier(.2, 1, .4, 1) both;
}
/* the sheet */
.ob2-sheet {
  position: relative;
  margin-top: -28px;
  display: flex; flex-direction: column; gap: 10px;
  padding: 18px 22px calc(12px + env(safe-area-inset-bottom));
  background: #fff;
  border-radius: 28px 28px 0 0;
  box-shadow: 0 -10px 30px rgba(23, 24, 28, 0.1);
  animation: ob2-sheet-in 600ms 100ms cubic-bezier(.2, 1, .4, 1) both;
}
@keyframes ob2-sheet-in { from { opacity: 0; transform: translateY(36px); } }
.ob2-sheet .ob2-cta,
.ob2-alt {
  min-height: 54px; padding: 0 18px;
  border-radius: 14px;
  font-family: var(--font-body); font-size: 16px; font-weight: 600; letter-spacing: -0.005em;
}
.ob2-sheet .ob2-cta { background: var(--ink); color: #fff; }
.ob2-alt {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  width: 100%;
  border: 1px solid #dfe1e6; background: #fff; color: var(--ink);
  transition: transform 160ms ease, background-color 160ms ease;
}
.ob2-alt .icon { display: inline-flex; color: var(--ink-soft); }
.ob2-alt:active { transform: scale(0.98); background-color: #f5f6f8; }
.ob2-legal { margin: 0; text-align: center; color: var(--ink-soft); font-size: 12.5px; line-height: 1.5; }
.ob2-legal a { color: var(--ink-soft); font-weight: 600; }
.ob2-sheet .ob2-legal { margin: 4px 8px 0; font-size: 12px; }
/* The welcome's second row: the partner's code and Sign in, side by side — the sheet stays
   two rows whatever is added to it. Sign in is for someone who already has an account. */
.ob2-alt-row { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.ob2-alt-row .ob2-alt { min-width: 0; padding: 0 10px; gap: 8px; font-size: 15px; }
.ob2-alt-row .ob2-alt span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ob2-sheet .ob2-legal a { color: var(--ink); text-decoration: underline; text-underline-offset: 2px; }

/* ============ stat ============ */
/* THE NUMBER AND WHAT IT MEANS SIT IN A SPEECH BUBBLE OVER THE DRAWING. stat.webp drew a
   bubble of its own, and the number first went inside THAT — but it was ~166x56px on a
   phone, too small for "3.5x" and a line of words at a size anyone reads comfortably (the
   user asked for both bigger). So onboarding-art.py now takes the drawn bubble out (strokes,
   white, tail — filled with the paper measured right round it, 1.2 levels off at most), and
   this one is ours: white, rounded, a tail toward the boy, sized for the copy. Type is in
   cqh of .ob2-art (`container-type: size`) so it scales with the picture. The caption may
   wrap to two balanced lines; Indonesian does. */
.ob2-stat-bubble {
  /* x .51-.97, y .305-.47 of the card (206x92px on a 448px phone): right of the boy's hair
     (its edge is at .49), under the drawn sparks (they end at .31), above the pink arc. */
  position: absolute;
  left: 51%; top: 30.5%;
  width: 46%; height: 16.5%;
  box-sizing: border-box;
  padding: 0 4%;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 0.5cqh;
  background: #fff;
  border-radius: 22px;
  box-shadow: 0 10px 26px rgba(90, 60, 70, 0.12), 0 1px 2px rgba(90, 60, 70, 0.06);
  pointer-events: none;
}
/* the tail, toward the boy */
.ob2-stat-bubble::after {
  content: ''; position: absolute;
  left: 16%; bottom: -11px;
  width: 22px; height: 14px;
  background: #fff;
  clip-path: polygon(0 0, 100% 0, 18% 100%);
}
.ob2-stat-big {
  font-family: var(--font-display); font-weight: 800;
  letter-spacing: -0.04em; line-height: 1;
  background: linear-gradient(100deg, #d63f78, #3e7be0);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
}
.ob2-stat-bubble .ob2-stat-big { font-size: 8.4cqh; }
.ob2-stat-cap {
  font-size: 2.6cqh; font-weight: 600; line-height: 1.2;
  letter-spacing: -0.005em;
  color: #4b4757;
  text-align: center; text-wrap: balance;
}
.ob2-pop-in { animation: ob2-stat-pop 520ms cubic-bezier(.2, 1.5, .4, 1) both; }
@keyframes ob2-stat-pop { from { opacity: 0; transform: scale(0.72); } }
@media (prefers-reduced-motion: reduce) { .ob2-pop-in { animation: none; } }

/* The sheet under the stat says three short things in ONE family (Pretendard — the source
   line was the only Space Mono on the screen): the claim as the heading, what it has to do
   with you, where it comes from. Each block is centred on the column — a max-width block
   with no auto margins sat flush LEFT under a centred heading — and `text-wrap: balance`
   keeps a wrapped line from stranding one word. */
.ob2-stat-you {
  align-self: center; max-width: 30ch;
  margin: -2px 0 0;
  font-size: 17.5px; font-weight: 500; line-height: 1.45; color: #3a3744;
  text-align: center; text-wrap: balance;
}
.ob2-stat-src {
  align-self: center;
  margin: -4px 0 0;
  font-size: 13px; font-weight: 500; line-height: 1.4; color: #7a7684;
  text-align: center;
}
.ob2-center .ob2-cta--float { max-width: 380px; }

/* ============ activities ============ */
.ob2-games {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px;
}
.ob2-games.ob2-reveal { animation: none; }
.ob2-games .ob2-game { animation: ob2-game-in 480ms cubic-bezier(.2, 1.3, .4, 1) both; animation-delay: var(--d, 0ms); }
.ob2-games.ob2-hide .ob2-game { animation: none; opacity: 0; }
@keyframes ob2-game-in { from { opacity: 0; transform: scale(0.6); } }
.ob2-game {
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  padding: 12px 6px 11px;
  border-radius: 18px;
  font-size: 12px; font-weight: 600; line-height: 1.25; color: var(--ink);
  text-align: center;
}
.ob2-game-art {
  display: block;
  width: 100%; height: 58px;
  object-fit: contain;
  filter: drop-shadow(0 4px 6px rgba(90, 60, 70, 0.14));
}

/* ============ benefits ============ */
.ob2-perks { display: flex; flex-direction: column; gap: 8px; }
.ob2-perks.ob2-reveal { animation: none; }
.ob2-perks .ob2-perk { animation: ob2-rise 460ms cubic-bezier(.2, 1, .4, 1) both; animation-delay: var(--d, 0ms); }
.ob2-perks.ob2-hide .ob2-perk { animation: none; opacity: 0; }
.ob2-perk {
  display: flex; align-items: center; gap: 12px;
  padding: 7px 12px 7px 7px;
  border-radius: 18px;
}
.ob2-perk-copy { min-width: 0; flex: 1; }
.ob2-perk-copy h3 {
  margin: 0; font-family: var(--font-display); font-weight: 700; font-size: 15px;
  display: flex; align-items: center; gap: 7px;
}
.ob2-perk-copy p { margin: 2px 0 0; font-size: 12.5px; color: var(--ink-soft); line-height: 1.35; }
.ob2-perk-tag {
  font-family: var(--font-mono); font-size: 9px; font-weight: 700;
  letter-spacing: 0.1em; text-transform: uppercase;
  padding: 2px 6px; border-radius: 5px;
  color: #fff; background: linear-gradient(100deg, var(--pink), var(--blue));
}
/* Each perk's Higgsfield sticker sits on its own pastel tile (app/assets/perks/); the rows
   are tight enough that the list reads as one screen, the tick is a pink badge rather than
   a green form check, and the yearly-only partner perk wears the pink→blue edge. */
.ob2-perk-art {
  flex: none; width: 50px; height: 50px; border-radius: 15px;
  display: grid; place-items: center; background: var(--tile, #FFEFF4);
}
.ob2-perk-art img { width: 44px; height: 44px; object-fit: contain; filter: drop-shadow(0 3px 5px rgba(90, 60, 70, 0.14)); }
.ob2-perk--booth { --tile: #FFE9F0; }
.ob2-perk--games { --tile: #EFEAFF; }
.ob2-perk--offline { --tile: #E6F1FF; }
.ob2-perk--ai { --tile: #E3F7EF; }
.ob2-perk--date { --tile: #F7E9FF; }
.ob2-perk--hub { --tile: #E8F6E9; }
.ob2-perk--fresh { --tile: #FFF5DA; }
.ob2-perk--partner {
  --tile: #FFE9F0;
  border: 1.5px solid transparent;
  background: linear-gradient(#fff, #fff) padding-box, linear-gradient(100deg, var(--pink), var(--blue)) border-box;
}
.ob2-perk .ob2-check {
  width: 24px; height: 24px; padding: 5px; border-radius: 50%;
  color: #fff; background: var(--pink); box-shadow: 0 3px 8px rgba(255, 123, 163, 0.35);
}
.ob2-check { flex: none; width: 22px; height: 22px; color: #0a9c5f; }
.ob2-check svg { width: 100%; height: 100%; }
.ob2-check-path {
  stroke-dasharray: 24; stroke-dashoffset: 24;
  animation: ob2-draw 500ms 260ms ease forwards;
}
@keyframes ob2-draw { to { stroke-dashoffset: 0; } }

/* ============ paywall ============ */
.ob2-pay { padding: 0; }
.ob2-pay-scroll {
  flex: 1;
  display: flex; flex-direction: column;
  gap: 16px;
  padding: calc(54px + env(safe-area-inset-top)) 24px calc(22px + env(safe-area-inset-bottom));
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}
.ob2-x {
  position: absolute;
  top: calc(14px + env(safe-area-inset-top)); right: 16px;
  z-index: 2;
  width: 34px; height: 34px;
  display: flex; align-items: center; justify-content: center;
  border: 0; border-radius: 50%;
  background: rgba(23, 24, 28, 0.05);
  color: rgba(23, 24, 28, 0.30); /* deliberately pale */
  animation: ob2-rise 400ms 1.2s ease both;
}
.ob2-pay-title {
  margin: 4px 0 2px;
  font-family: var(--font-display); font-weight: 800;
  font-size: clamp(27px, 7.6vw, 34px);
  letter-spacing: -0.03em; line-height: 1.16;
  text-align: center;
  animation: ob2-rise 500ms cubic-bezier(.2,1,.4,1) both;
}
.ob2-pay-free {
  background: linear-gradient(100deg, var(--pink), var(--blue));
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
}

/* timeline */
.ob2-tl {
  display: flex; flex-direction: column; position: relative;
  padding: 16px 16px 16px 14px;
  border-radius: 22px;
  background: rgba(255, 255, 255, 0.7);
  border: 1px solid rgba(255, 255, 255, 0.95);
  box-shadow: 0 10px 28px rgba(90, 60, 70, 0.08);
}
.ob2-tl-item {
  position: relative;
  display: flex; gap: 16px;
  padding: 0 0 22px 0;
  animation: ob2-rise 460ms cubic-bezier(.2,1,.4,1) both;
}
.ob2-tl-item.p1 { animation-delay: 120ms; }
.ob2-tl-item.p2 { animation-delay: 260ms; }
.ob2-tl-item.p3 { animation-delay: 400ms; padding-bottom: 0; }
.ob2-tl-ic {
  flex: none;
  width: 44px; height: 44px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%;
  font-size: 19px;
  background: #fff;
  border: 1px solid rgba(23, 24, 28, 0.06);
  box-shadow: 0 4px 12px rgba(90, 60, 70, 0.1);
  z-index: 1;
}
.ob2-tl-emo { width: 28px; height: 28px; object-fit: contain; }
/* the rail connecting the icons */
.ob2-tl-item:not(.p3)::before {
  content: '';
  position: absolute;
  left: 21px; top: 44px; bottom: -2px;
  width: 3px; border-radius: 2px;
  background: linear-gradient(180deg, var(--pink), var(--blue));
  opacity: 0.45;
}
.ob2-tl-copy h3 { margin: 2px 0 3px; font-family: var(--font-display); font-weight: 800; font-size: 16.5px; letter-spacing: -0.01em; }
.ob2-tl-copy p { margin: 0; font-size: 13.5px; line-height: 1.45; color: var(--ink-soft); max-width: 34ch; }

/* plan cards */
.ob2-plans { display: grid; grid-template-columns: 1fr 1.15fr; gap: 10px; align-items: stretch; animation: ob2-rise 460ms 500ms cubic-bezier(.2,1,.4,1) both; }
.ob2-plan {
  position: relative;
  display: flex; flex-direction: column; align-items: flex-start; gap: 3px;
  padding: 16px 14px 13px;
  border-radius: 20px;
  border: 2px solid var(--line);
  background: var(--paper-raised);
  text-align: left;
  font-family: var(--font-display);
  color: var(--ink);
  transition: border-color 200ms ease, box-shadow 200ms ease, transform 160ms ease;
}
.ob2-plan:active { transform: scale(0.98); }
.ob2-plan.is-on {
  border-color: #ec5f93;
  background: #fff6f9;
  box-shadow: 0 12px 30px rgba(90, 60, 70, 0.12);
}
.ob2-plan-badge {
  position: absolute; top: -11px; right: 10px;
  padding: 4px 10px;
  border-radius: 999px;
  background: #ec5f93; color: #fff;
  font-family: var(--font-body); font-size: 11px; font-weight: 700; letter-spacing: 0;
}
.ob2-plan-name { font-weight: 700; font-size: 15px; }
.ob2-plan-price { font-size: 13px; color: var(--ink-soft); }
.ob2-plan-price b { font-size: 21px; letter-spacing: -0.02em; color: var(--ink); font-weight: 800; }
.ob2-plan-sub { font-size: 11.5px; color: var(--ink-soft); }
.ob2-plan-sub b { color: var(--ink); }
.ob2-plan-partner {
  margin-top: 3px;
  font-size: 11px; font-weight: 700;
  color: var(--pink);
}
/* "Buy 1, get 1 free" (Fase 5, architecture.md §16h — the banner styles itself in
   client/core/paywallUi.js): the first thing in the column, the trial's title the second line
   under it, and the line on a card whose plan carries the partner's seat. */
.ob2-b1g1:empty { display: none; }
.ob2-pay.has-b1g1 .ob2-pay-title { margin-top: 0; font-size: clamp(22px, 6.2vw, 26px); }
.ob2-plan-partner.is-b1g1 { max-width: calc(100% - 28px); font-weight: 800; color: #c2416f; }
.ob2-plan-check {
  position: absolute; right: 12px; bottom: 12px;
  width: 22px; height: 22px;
  border-radius: 50%;
  border: 2px solid var(--line);
  transition: all 200ms ease;
}
.ob2-plan.is-on .ob2-plan-check {
  border-color: #ec5f93;
  background: #ec5f93 url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"><path d="M6 12.5l4 4L18 8" stroke="white" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg>') center/13px no-repeat;
}

.ob2-nodue {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  font-weight: 700; font-size: 15px;
  animation: ob2-rise 460ms 600ms cubic-bezier(.2,1,.4,1) both;
}
.ob2-nodue .ob2-check { color: var(--ink); width: 19px; height: 19px; }
.ob2-cta--pay { border-radius: 999px; padding: 18px; animation: ob2-rise 460ms 660ms cubic-bezier(.2,1,.4,1) both; }
.ob2-cta--pay.is-busy { opacity: 0.6; pointer-events: none; }
.ob2-pay-small { margin: -6px 0 0; text-align: center; font-size: 12.5px; color: var(--ink-soft); animation: ob2-rise 460ms 720ms ease both; }
.ob2-pay-note { margin: 0; min-height: 18px; text-align: center; font-size: 13px; color: #b4443c; }
.ob2-pay-link {
  border: 0; background: none;
  color: var(--ink-soft); font-size: 13.5px; font-weight: 600;
  text-decoration: underline; text-underline-offset: 3px;
  padding: 4px;
}
/* Required auto-renewable subscription disclosure (App Store 3.1.2) — deliberately fine
   print, subordinate to the price, but fully legible: five short lines on a quiet card
   (.ob2-terms) rather than one nine-line block of grey. */
.ob2-terms {
  margin: 2px 0 0;
  padding: 14px 16px 12px;
  border-radius: 18px;
  background: rgba(255, 255, 255, 0.55);
  border: 1px solid rgba(255, 255, 255, 0.9);
}
.ob2-terms-h { margin: 0 0 6px; font-size: 12px; font-weight: 700; color: var(--ink); }
.ob2-terms-list { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 5px; }
.ob2-terms-list li {
  position: relative;
  padding-left: 12px;
  font-size: 11.5px; line-height: 1.45; color: var(--ink-soft);
}
.ob2-terms-list li::before {
  content: ""; position: absolute; left: 0; top: 0.62em;
  width: 4px; height: 4px; border-radius: 50%; background: rgba(23, 24, 28, 0.3);
}
.ob2-pay-foot { margin-top: -4px; }
.ob2-pay-foot a { color: var(--ink-soft); font-weight: 600; }

/* ============ sign-in ============
   The welcome's shape on the sign-in's own drawing (app/OB/LoginandRegister.png — the two of
   them holding their strips, the wordmark painted across the top): the picture fills the
   screen and the sheet floats over its foot with Apple and Google (app/app.css
   .ob-btn--sso) and "Not now". The title sits ON THE SHEET, not over the picture — white
   type with a shadow over a busy illustration is the thing this flow keeps not doing, and
   the drawing already carries the brand. No scrim either, for the same reason. */
.ob2-signin-head { display: flex; flex-direction: column; gap: 4px; margin-bottom: 2px; }
.ob2-hero-title {
  margin: 0;
  font-family: var(--font-display); font-weight: 800;
  font-size: clamp(24px, 6.4vw, 29px);
  letter-spacing: -0.025em; line-height: 1.15;
  color: var(--ink);
}
.ob2-signin .ob2-tag {
  margin: 0; max-width: 32ch;
  font-size: 14.5px; line-height: 1.45; font-weight: 500;
  color: var(--ink-soft); text-shadow: none;
}
.ob2-signin .ob-auth { max-width: none; }
/* Tight on purpose: every pixel this sheet gives back is a pixel of the couple above it.
   The sign-in status line (.ob-note, app/app.js) is an EMPTY 18px box until a sign-in
   reports something; with its gap it was the 28px hole above "Not now". It only takes room
   when it has words. */
.ob2-signin .ob2-sheet { gap: 8px; padding-top: 18px; }
.ob2-signin .ob2-signin-head { gap: 2px; margin-bottom: 2px; }
.ob2-signin .ob2-tag { font-size: 13.5px; }
.ob2-signin .ob-btn--sso { padding-top: 13px; padding-bottom: 13px; }
.ob2-signin .ob-note:empty { display: none; }
.ob2-signin .ob2-skip { min-height: 36px; }
/* The drawing is lifted so the couple and their strips clear the sheet (--bg-lift, measured
   by liftSigninArt in onboarding.js — 0 until it has measured). What it uncovers at the
   bottom is the screen's own paper, and that is under the sheet. */
.ob2-welcome.ob2-signin .ob2-bg { transform: translateY(calc(-1 * var(--bg-lift, 0px))); }
.ob2-skip {
  align-self: center;
  min-height: 44px; padding: 0 18px;
  border: 0; background: none;
  color: var(--ink-soft);
  font-family: var(--font-body); font-size: 15px; font-weight: 600;
}

/* small phones: tuck the question up a bit */
@media (max-height: 700px) {
  .ob2-q { margin-top: 1vh; }
  .ob2-qscreen { gap: 16px; }
  .ob2-games .ob2-game { padding: 9px 4px 8px; }
  .ob2-game-art { height: 46px; }
}

@media (prefers-reduced-motion: reduce) {
  .ob2 *, .ob2 *::before, .ob2 *::after { animation: none !important; transition: none !important; }
  .ob2-hide { opacity: 1; pointer-events: auto; }
}


/* ---- mobile polish: tap targets, step count, sticky paywall CTA ---- */
.ob2-x { min-width: 44px; min-height: 44px; }
.ob2-step { font: 600 12.5px/1 var(--font-body); color: var(--ink-soft); min-width: 44px; text-align: right; }
.ob2 { padding-left: env(safe-area-inset-left); padding-right: env(safe-area-inset-right); }
.ob2-pay { display: flex; flex-direction: column; }
.ob2-pay .ob2-pay-scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding-bottom: 12px; }
.ob2-pay-dock {
  flex: 0 0 auto; display: flex; flex-direction: column; gap: 8px;
  padding: 12px 22px calc(14px + env(safe-area-inset-bottom));
  background: linear-gradient(to top, rgba(251, 248, 246, 0.96) 65%, rgba(251, 248, 246, 0));
}

/* ============ a tablet (2026-10-22, the user on an iPad: "fix the onboarding in landscape") ======
   Everything above is a phone's: full-bleed drawings and sheets as wide as the screen. On an iPad
   that cropped the whole-page drawings to a band (the sign-in showed the top of a head), stretched
   every button across 1,500px, and on a question screen in portrait the 4:5 drawing alone filled
   the screen, the answers under the fold. Same screens, same order — only the layout changes. */

/* LANDSCAPE (second round, the same day — the user, showing another app's iPad sign-in: "the
   picture looks separate, like a box; make it like this"): the drawing runs FULL-BLEED down the
   left edge — the screen's whole height, at its own width for that height, so nothing is cropped
   and the stat's bubble still sits where the drawing put it — and fades into the paper of the
   column beside it. No card, no corners, no shadow. What you read and tap is that column, its
   block centred in the height. */
@media (orientation: landscape) and (min-width: 900px) and (min-height: 600px) {
  /* the head: the back arrow over the drawing's corner, the progress bar short at the top right
     instead of a line across the picture */
  .ob2-head { padding-right: 28px; }
  .ob2-head > .ob2-bar { flex: 0 1 360px; margin-left: auto; }

  /* the welcome film and the sign-in drawing (both 9:16): the screen's height on the left, the
     lockup and the sheet's actions centred in the column on the right. The hero steps aside
     (display: contents) so its picture, the lockup and the sheet are one grid — the language
     pill stays in the top right corner. */
  .ob2-welcome.has-film {
    --film-w: calc(100vh * 9 / 16);
    display: grid;
    grid-template-columns: var(--film-w) minmax(0, 1fr);
    grid-template-rows: minmax(0, 1fr) auto auto minmax(0, 1fr);
    row-gap: 28px;
    padding: 0;
    /* the email sign-in opens inside the sheet, and the keyboard takes half the height: the
       column scrolls rather than cutting the field off */
    overflow-y: auto;
  }
  .ob2-welcome.has-film > .ob2-hero { display: contents; }
  .ob2-welcome.has-film .ob2-bg {
    position: relative; inset: auto;
    grid-column: 1; grid-row: 1 / -1; align-self: start;
    width: var(--film-w); height: 100vh;
    object-fit: cover;
    transform: none; /* the sign-in's lift (liftSigninArt) is the phone's, for the overlap */
  }
  /* its right edge fades into the column's paper (the drawing's own, #FAFBF6) */
  .ob2-welcome.has-film::after {
    content: ''; position: absolute; z-index: 1; top: 0; left: 0;
    width: var(--film-w); height: 100vh;
    background: linear-gradient(90deg, rgba(250, 251, 246, 0) 84%, #fafbf6);
    pointer-events: none;
  }
  .ob2-welcome.has-film .ob2-logo {
    position: static; grid-column: 2; grid-row: 2;
  }
  .ob2-welcome.has-film .ob2-logo .ob2-logo-word { font-size: 64px; }
  .ob2-welcome.has-film .ob2-logo-line { font-size: 16px; }
  /* the actions stand on the paper itself */
  .ob2-welcome.has-film > .ob2-sheet {
    grid-column: 2; grid-row: 3; justify-self: center;
    width: calc(100% - 96px); max-width: 420px;
    margin: 0; padding: 0;
    background: none; border-radius: 0; box-shadow: none;
  }

  /* a question with its drawing: the drawing down the left edge, the question and its answers in
     the column beside it */
  .ob2-screen.has-art,
  .ob2-screen.has-art.ob2-center {
    flex-direction: row; align-items: stretch; justify-content: flex-start;
    gap: 0; padding: 0;
    overflow: hidden;
    background: var(--paper);
  }
  .ob2-screen.has-art > .ob2-art {
    flex: 0 0 auto;
    width: auto; height: 100%;
    max-width: calc(100% - 420px);
  }
  .ob2-screen.has-art > .ob2-art::after {
    content: ''; position: absolute; top: 0; right: 0; bottom: 0; width: 18%;
    background: linear-gradient(90deg, rgba(248, 249, 251, 0), var(--paper));
    pointer-events: none;
  }
  .ob2-screen.has-art .ob2-stat-bubble { z-index: 1; } /* over the fade */
  .ob2-screen.has-art > .ob2-qsheet {
    flex: 1 1 0; min-width: 0;
    max-height: none;
    margin: 0;
    padding: calc(84px + env(safe-area-inset-top)) 40px calc(28px + env(safe-area-inset-bottom));
    align-items: center;
    background: var(--paper);
    border-radius: 0; box-shadow: none;
    overflow-y: auto;
  }
  .ob2-screen.has-art > .ob2-qsheet > * { width: 100%; max-width: 460px; }
  /* the block — question, answers, button — centred in the height: an auto margin above it and
     one below (the ::after). Unlike justify-content: center these give way when the block is
     taller than the screen, so nothing is ever cut off its top. */
  .ob2-screen.has-art > .ob2-qsheet > :first-child { margin-top: auto; }
  .ob2-screen.has-art > .ob2-qsheet::after { content: ''; flex: 0 0 0; margin-top: auto; }
  /* the button follows the answers; on a list too long for the screen it sticks at the foot, its
     shadow (the paper) covering what scrolls through the padding under it */
  .ob2-screen.has-art > .ob2-qsheet > .ob2-cta-bar {
    bottom: 0;
    margin: 4px 0 0; padding: 14px 0 0;
    background: var(--paper);
    box-shadow: 0 calc(28px + env(safe-area-inset-bottom)) 0 var(--paper);
  }

  /* the screens without a drawing (which one are you, the games, what Premium gives): one
     centred column, not a line of answers the width of the screen */
  .ob2-qscreen:not(.has-art) {
    justify-content: center;
    padding-left: max(44px, calc((100% - 760px) / 2));
    padding-right: max(44px, calc((100% - 760px) / 2));
  }
  .ob2-qscreen:not(.has-art) > .ob2-grow { flex: 0 0 4px; }
  .ob2-qscreen:not(.has-art) > .ob2-cta-bar { background: none; margin-left: 0; margin-right: 0; padding-left: 0; padding-right: 0; }
  .ob2-qscreen:not(.has-art) .ob2-cta--float { max-width: 420px; }
  /* the two halves are 0.43 wide for 1 tall: sized off the screen's height so they never run
     into its foot (at 250px they reached 9px off it) */
  .ob2-gender { max-width: min(250px, calc((100vh - 290px) * 0.43)); }
  .ob2-games { grid-template-columns: repeat(7, minmax(0, 1fr)); }
  .ob2-perks { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }

  /* the paywall in two columns: what happens when (the timeline) beside the plans and their
     terms; the button stays at the foot, centred */
  .ob2-pay-scroll {
    display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    column-gap: 28px; row-gap: 16px; align-content: start;
    padding-left: max(44px, calc((100% - 960px) / 2));
    padding-right: max(44px, calc((100% - 960px) / 2));
  }
  .ob2-pay-scroll > .ob2-b1g1 { grid-column: 1 / -1; grid-row: 1; }
  .ob2-pay-scroll > .ob2-pay-title { grid-column: 1 / -1; grid-row: 2; }
  .ob2-pay-scroll > .ob2-tl { grid-column: 1; grid-row: 3 / span 2; align-self: start; }
  .ob2-pay-scroll > .ob2-plans { grid-column: 2; grid-row: 3; }
  .ob2-pay-scroll > .ob2-terms { grid-column: 2; grid-row: 4; margin: 0; }
  .ob2-pay-scroll > .ob2-pay-link { grid-column: 1 / -1; grid-row: 5; justify-self: center; }
  .ob2-pay-scroll > .ob2-pay-foot { grid-column: 1 / -1; grid-row: 6; }
  /* no timeline (the web, once the $1 days are used): one centred column */
  .ob2-pay-scroll:has(> .ob2-tl[hidden]) { grid-template-columns: minmax(0, 560px); justify-content: center; }
  .ob2-pay-scroll:has(> .ob2-tl[hidden]) > * { grid-column: 1 !important; grid-row: auto !important; }
  .ob2-pay-dock {
    padding-left: max(44px, calc((100% - 480px) / 2));
    padding-right: max(44px, calc((100% - 480px) / 2));
  }
}

/* PORTRAIT: the phone's shape, in a column that stops growing — the drawing a card under the bar
   instead of the full width (which put the answers under the fold), the sheets floating cards. */
@media (orientation: portrait) and (min-width: 700px) {
  .ob2-welcome.has-film > .ob2-sheet {
    width: min(560px, calc(100% - 48px));
    align-self: center;
    margin-bottom: calc(24px + env(safe-area-inset-bottom));
    padding: 22px 26px 18px;
    border-radius: 28px;
  }
  .ob2-welcome.has-film .ob2-logo .ob2-logo-word { font-size: 60px; }
  .ob2-screen.has-art,
  .ob2-screen.has-art.ob2-center { align-items: center; }
  .ob2-screen.has-art > .ob2-art {
    /* …and short enough to leave five answers on screen under it (an iPad mini's 1133px) */
    width: min(100%, 540px, calc((100vh - 520px) * 0.8));
    margin-top: calc(72px + env(safe-area-inset-top));
    border-radius: 28px; overflow: hidden;
    box-shadow: 0 18px 44px rgba(90, 60, 70, 0.12);
  }
  .ob2-screen.has-art > .ob2-qsheet { width: min(100%, 540px); padding-left: 26px; padding-right: 26px; }
  .ob2-screen.has-art > .ob2-qsheet > .ob2-cta-bar { margin-left: -26px; margin-right: -26px; padding-left: 26px; padding-right: 26px; }
  .ob2-qscreen:not(.has-art) {
    padding-left: max(32px, calc((100% - 620px) / 2));
    padding-right: max(32px, calc((100% - 620px) / 2));
  }
  .ob2-qscreen:not(.has-art) > .ob2-cta-bar { background: none; margin-left: 0; margin-right: 0; padding-left: 0; padding-right: 0; }
  .ob2-qscreen:not(.has-art) .ob2-cta--float { max-width: 480px; }
  .ob2-gender { max-width: 240px; }
  .ob2-games { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .ob2-pay-scroll, .ob2-pay-dock {
    padding-left: max(32px, calc((100% - 560px) / 2));
    padding-right: max(32px, calc((100% - 560px) / 2));
  }
}

