/* Professor Pip web onboarding core: tokens, base text, app frame, shared components.
   Port of lib/onboarding/theme.dart + widgets.dart. All sizes are CSS px (= Flutter logical px).

   Text rules (spec 00 §1.4): any text without its own style is Lora Medium 16px, ink-soft,
   letter-spacing .25px, line-height 1.43 (inherited as a factor). Flutter rounds every line box to
   whole px, so give text explicit px line-heights: round(size × height), e.g. 15→21, 16→23, 17→24, 22→31.
   Lora 600/700/800 asked of inherited-family text renders as Lora 500 (+ .fb faux bold at ≥700).
   Brutal boxes (2.5px ink border + hard shadow) use `.brutal` (below), not CSS borders: Chrome renders
   `border: 2.5px` as 2px, and an inset ring beside an outer box-shadow leaves a pale hairline on curves.
   Measured against the Flutter frames, Chrome draws small Lora 500 text (12–17px; not 16) 0.5–0.8px
   lower than CanvasKit and the button's Inter label 1px higher; those components carry a 1px nudge
   (Chrome snaps text to whole px, so half-pixel nudges do not work). */

:root {
  --cream: #F2ECDF;
  --cream-soft: #EDE6D6;
  --ink: #1F1F1F;
  --ink-soft: #3A3A3A;
  --burgundy: #7A1F2B;        /* AppColors.teal == burgundy: primary */
  --muted: #8A8275;
  --success: #4CAF7C;
  --forest: #2E7D4A;          /* AppColors.forestGreen */
  --gift-red: #F6454E;
  --gift-red-deep: #C42F38;

  --ease-out-back: cubic-bezier(.175, .885, .32, 1.275);
}

/* ---------------------------------------------------------------- base */
*, *::before, *::after { box-sizing: border-box; }
html, body {
  margin: 0; padding: 0; height: 100%; overflow: hidden;
  background: var(--cream); overscroll-behavior: none; touch-action: manipulation;
}
html { font-synthesis: none; -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
body {
  font-family: Lora, Georgia, serif; font-weight: 500; font-size: 16px;
  line-height: 1.43; letter-spacing: .25px; color: var(--ink-soft);
  -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
  font-kerning: normal; font-variant-ligatures: normal; overflow-wrap: break-word;
  /* Flutter text is not selectable and has no tap highlight or callout. */
  -webkit-user-select: none; user-select: none; -webkit-touch-callout: none;
  -webkit-tap-highlight-color: transparent; cursor: default;
}
h1, h2, h3, h4, p, ul, ol, figure { margin: 0; padding: 0; font-size: inherit; font-weight: inherit; }
button {
  font: inherit; letter-spacing: inherit; line-height: inherit; color: inherit; text-align: inherit;
  background: none; border: 0; border-radius: 0; padding: 0; margin: 0;
  -webkit-appearance: none; appearance: none; cursor: default; touch-action: manipulation;
}
input { -webkit-user-select: text; user-select: text; }
:focus { outline: none; }
:focus-visible { outline: 2px solid var(--burgundy); outline-offset: 3px; }
input:focus-visible { outline: none; }
/* Flutter shows no scrollbars on phones. */
.pf-phone * { scrollbar-width: none; }
.pf-phone *::-webkit-scrollbar { display: none; }

/* Faux bold: Skia's synthetic bold of Lora 500 (Flutter w700/w800 on inherited-family text).
   Exact width = Pip.fauxBold(size): 12→.49 14→.56 15→.59 16→.62 17→.66 19→.72 20→.75 22→.81 56→1.75 */
.fb { -webkit-text-stroke: var(--fb, .04em) currentColor; }
.inter { font-family: Inter, system-ui, sans-serif; }

/* Brutal box, painted like Flutter: shadow (::before) under the whole box, then the face (::after:
   fill + 2.5px ring inside the box), then the content. Padding = Flutter padding + 2.5 (border).
   Like Flutter, the fill stops 1.25px inside the edge so it cannot bleed through the ring's edge.
   Vars: --b-fill (#FFF), --b-dx/--b-dy (4px/5px), --b-ring (ink), --b-t (fill transition, 0ms).
   Set border-radius on the element. The element itself must have no background. Unlike a box-shadow,
   the shadow counts as scrollable overflow: give scroll containers `overflow-x: hidden`. */
.brutal { position: relative; isolation: isolate; }
.brutal::before, .brutal::after {
  content: ""; position: absolute; top: 0; right: 0; bottom: 0; left: 0; z-index: -1; border-radius: inherit;
}
.brutal::before { background: var(--ink); transform: translate(var(--b-dx, 4px), var(--b-dy, 5px)); }
.brutal::after {
  padding: 1.25px; background: var(--b-fill, #FFFFFF) content-box; box-shadow: inset 0 0 0 2.5px var(--b-ring, var(--ink));
  transition: background-color var(--b-t, 0ms) linear;
}

/* ----------------------------------------------------------- app frame */
#app { position: fixed; top: 0; right: 0; bottom: 0; left: 0; overflow: hidden; background: var(--cream); }
html.fonts-wait #app { visibility: hidden; }
#frame { position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow: hidden; background: var(--cream); }
/* MaterialApp.builder on web: wider than 600 → a centred 430px column. (Chrome matches
   min-width: 600.01px at exactly 600, so the threshold is written as 601.) */
@media (min-width: 601px) { #frame { margin: 0 auto; max-width: 430px; } }

/* A layer is a full-frame route (root flow, done screen, pushed quiz, dialog); a page is one flow step. */
.layer, .page {
  position: absolute; top: 0; right: 0; bottom: 0; left: 0;
  display: flex; flex-direction: column; overflow: hidden;
}
.layer > *, .page > * { flex: 1 1 0; min-height: 0; }
.layer.root, .page { background: var(--cream); }
/* Cupertino edge shadow (Pip.pushScreen / done screen): rides with the sliding page, left of it. */
.cup-edge { position: absolute; top: 0; bottom: 0; left: 0; width: 100%; pointer-events: none; }
.cup-edge::before {
  content: ""; position: absolute; top: 0; bottom: 0; right: 100%; width: 5%;
  background: linear-gradient(to left, rgba(0, 0, 0, .0157), rgba(0, 0, 0, 0));
}
.no-input { pointer-events: none; }
.layer.push-in { pointer-events: auto; }   /* a route sliding in already takes input (Pip.pushScreen) */

/* ------------------------------------------------- OnboardingScaffold */
.ob-scaffold { display: flex; flex-direction: column; background: var(--cream); }
.ob-top { flex: none; display: flex; align-items: center; padding: 8px 16px 4px 8px; }
.ob-back {
  flex: none; position: relative; width: 48px; height: 48px;
  display: flex; align-items: center; justify-content: center; cursor: pointer;
}
.pf-desktop .ob-back { width: 40px; height: 40px; }
.ob-back[disabled] { cursor: default; }
/* IconButton overlay: 40px circle, onSurfaceVariant at 8% hovered; pressed = the 10% highlight plus
   the 10% InkRipple that fills the circle within ~150ms (≈19% together, ≈26% over the hover). */
.ob-back::before {
  content: ""; position: absolute; left: 50%; top: 50%; width: 40px; height: 40px; margin: -20px 0 0 -20px;
  border-radius: 50%; background: rgba(31, 31, 31, .1); opacity: 0;
  transition: opacity 375ms linear, background-color 375ms linear;
}
.ob-back.down:not([disabled])::before { opacity: 1; background-color: rgba(31, 31, 31, .19); transition-duration: 150ms; }
@media (hover: hover) {
  .pf-desktop .ob-back:not([disabled]):not(.down):hover::before { opacity: .8; transition-duration: 75ms; }
  .pf-desktop .ob-back.down:not([disabled]):hover::before { background-color: rgba(31, 31, 31, .255); }
}
.ob-back .ic { position: relative; }
.ob-progress { flex: 1 1 0; min-width: 0; height: 4px; border-radius: 99px; overflow: hidden; background: var(--cream-soft); }
.ob-progress > i { display: block; height: 100%; background: var(--ink); }
.ob-skip-slot { flex: none; width: 64px; }
.ob-skip {
  display: block; width: 100%; padding: 8px 0; text-align: right; white-space: nowrap;
  font-size: 15px; line-height: 21px; color: var(--ink); position: relative; top: -1px;
}
.ob-body { flex: 1 1 0; min-height: 0; position: relative; display: flex; flex-direction: column; }
.ob-body > * { flex: 1 1 0; min-height: 0; }

/* ------------------------------------------------------ PrimaryButton */
/* Flutter paints shadow → fill → border, the shadow under the whole box, so a 45% fill shows it
   through (the dark disabled face). The shadow never moves; pressing slides the face onto it. */
.pbtn { position: relative; flex: none; height: 64px; --fill: var(--burgundy); }
.pbtn-sh {
  position: absolute; top: 0; right: 0; bottom: 0; left: 0;
  border-radius: 36px; background: var(--ink); transform: translate(4px, 6px);
}
.pbtn-face {
  position: absolute; top: 0; left: 0; width: 100%; height: 100%; border-radius: 36px; padding: 1.25px;
  background: var(--fill) content-box; box-shadow: inset 0 0 0 2.5px var(--ink); overflow: visible;
  transition: transform 80ms linear, background-color 80ms linear;
}
/* Pip.button({href}): the face is a real link (the Text Pip sms: button). */
a.pbtn-face { display: block; text-decoration: none; cursor: pointer; -webkit-user-drag: none; }
.pbtn.down .pbtn-face { transform: translate(4px, 6px); }
.pbtn-label {
  position: absolute; left: 50%; top: calc(50% + 1px); transform: translate(-50%, -50%) scale(var(--s, 1));
  font-family: Inter, system-ui, sans-serif; font-weight: 800; font-size: 22px; line-height: 31px;
  letter-spacing: .25px; color: #FFFFFF; white-space: nowrap;
}
.pbtn-spin { position: absolute; left: 50%; top: 50%; margin: -13px 0 0 -13px; overflow: visible; }
/* PrimaryButton(pulse: true): ScaleTransition of the whole button (shadow too), 1 → 1.035, easeInOut, 1100 ms each way. */
.pbtn-pulse { animation: pbtn-pulse 1100ms cubic-bezier(0.42, 0, 0.58, 1) infinite alternate; }
@keyframes pbtn-pulse { from { transform: scale(1); } to { transform: scale(1.035); } }
@media (prefers-reduced-motion: reduce) { .pbtn-pulse { animation: none; } }

/* ------------------------------------------------------------- avatar */
/* Shadow disc under the circle; ring = inset shadow under the art, which is scaled about its
   centre and clipped to the outer circle. The art's box is the one inside the ring, but its 2.5px
   inset rides in the transform: laid out at 2.5px, Chrome draws the art half a pixel off. This is
   more specific than the groups' earlier img workarounds for that, which it supersedes. */
.avatar { position: relative; flex: none; }
.avatar::before {
  content: ""; position: absolute; top: 0; right: 0; bottom: 0; left: 0; border-radius: 50%;
  background: var(--ink); transform: translate(var(--av-dx), var(--av-dy));
}
.avatar-c {
  position: absolute; top: 0; right: 0; bottom: 0; left: 0; border-radius: 50%; overflow: hidden;
  isolation: isolate; padding: 1.25px; background: #FFFFFF content-box; box-shadow: inset 0 0 0 2.5px var(--ink);
}
.avatar > .avatar-c > img.av-art {
  position: absolute; left: 0; top: 0; width: calc(100% - 5px); height: calc(100% - 5px);
  object-fit: cover; transform: translate(2.5px, 2.5px) scale(var(--av-scale, 1.25)); pointer-events: none;
}

/* ------------------------------------------------------- iMessage shot */
/* Pip.imessageShot(): a flex slot (basis set from JS) holding a brutal card the size JS fits, the
   screenshot clipped inside the 2.5px ring. Black fill, so no pale seam shows where the image's
   curve meets the ring's. Screens give the slot its margins and, in a flex: none column, its flex. */
.shot-slot { position: relative; flex: 0 1 auto; min-height: 0; width: 100%; }
.shot-slot > .shot-card {
  position: absolute; left: 0; top: 0; padding: 2.5px; border-radius: 22px;
  --b-fill: #000000; --b-dx: 4px; --b-dy: 6px; transition: opacity 240ms linear;
}
.shot-slot > .shot-wait { opacity: 0; }
/* Too small to read, the card is hidden (JS) and its slot gives the screen's margins back, so they
   don't push Continue off a short screen. !important: screens set the margins with longer selectors. */
.shot-slot.shot-off { margin: 0 !important; }
.shot-img {
  display: block; width: 100%; height: 100%; border-radius: 19.5px;
  object-fit: cover; object-position: 50% 0%; pointer-events: none;
}

/* --------------------------------------------------- typed Pip speech */
/* `anywhere`: a word longer than the line (a long name) breaks inside, like Flutter's RichText, and does
   not widen a shrink-to-fit bubble past the screen (it no longer counts whole toward min-content). */
.tw {
  position: relative; font-family: Inter, system-ui, sans-serif; font-weight: 600; color: var(--ink);
  white-space: pre-line; overflow-wrap: anywhere;
}
.tw-sizer { visibility: hidden; letter-spacing: .25px; }
.tw-reveal { position: absolute; left: 0; right: 0; letter-spacing: 0; }
.tw-nw { white-space: nowrap; }
/* Flutter types '▍' in muted, from the Noto Sans SC fallback: advance 1em, ink x 0–.375em and from
   .88em above to .12em below the baseline. No bundled font has it, so it is a box whose right margin
   makes up the rest of the advance (the partial word + caret wraps where Flutter's does). */
.tw-caret {
  display: inline-block; width: .375em; height: 1em; vertical-align: -.12em; margin-right: .625em;
  background: var(--muted);
}

/* _PipIntroBubble */
.pb { padding: 20.5px 22.5px; border-radius: 22px; --b-dx: 4px; --b-dy: 5px; }
.pb-name {
  font-size: 12px; line-height: 17px; letter-spacing: .5px; color: var(--burgundy);
  -webkit-text-stroke: .49px currentColor; position: relative; top: -1px;
}
.pb .tw { margin-top: 8px; }

/* -------------------------------------------------------- TitleHeader */
.title-h { display: flex; flex-direction: column; align-items: center; text-align: center; }
.title {
  font-weight: 700; font-size: 34px; line-height: 39px; letter-spacing: 0;
  color: var(--ink); white-space: pre-line;
}
.subtitle { margin-top: 12px; font-size: 16px; line-height: 23px; color: var(--ink-soft); }

/* ------------------------------------------------------ _DifficultyPill */
.pill {
  align-self: center; padding: 9.5px 18.5px 11.5px; border-radius: 99px;
  --b-fill: var(--burgundy); --b-dx: 2px; --b-dy: 3px;
  color: var(--cream); font-size: 14px; line-height: 20px; letter-spacing: .3px;
  -webkit-text-stroke: .556px currentColor;
}

/* ------------------------------------------------- OptionTile + _Radio */
.opt {
  display: flex; align-items: center; padding: 22.5px 24.5px; border-radius: 36px;
  color: var(--ink); font-size: 17px; line-height: 24px; text-align: left;
  --b-dx: 3px; --b-dy: 4px; --b-t: 150ms;      /* only the fill fades; label and radio switch instantly */
}
.opt.sel { --b-fill: var(--burgundy); color: #FFFFFF; }
.opt-label { flex: 1 1 0; min-width: 0; position: relative; top: -1px; }
.opt-radio {
  flex: none; width: 26px; height: 26px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  box-shadow: inset 0 0 0 2.5px var(--ink);
}
.opt-radio .ic { visibility: hidden; }
.opt.sel .opt-radio { background: #FFFFFF; box-shadow: inset 0 0 0 2.5px #FFFFFF; }
.opt.sel .opt-radio .ic { visibility: visible; }

/* ---------------------------------------------------------- TopicChip */
.chip {
  flex: none; max-width: 100%; padding: 14.5px 22.5px 16.5px; border-radius: 99px;
  color: var(--ink); font-size: 17px; line-height: 24px; -webkit-text-stroke: .656px currentColor;
  --b-dx: 2px; --b-dy: 3px; --b-t: 150ms;
}
.chip.sel { --b-fill: var(--ink); color: #FFFFFF; }

/* --------------------------------------------------------- text field */
/* Height = line + 2×22 (phone) or 2×18 (desktop: compact density); text starts 26px in.
   The .brutal wrapper paints shadow, fill and ring (inputs have no pseudo-elements). InputDecorator
   paints its white fill to the outer edge, under the ring (no 1.25px inset like BoxDecoration boxes). */
.tf { flex: none; border-radius: 36px; --b-dx: 4px; --b-dy: 5px; }
.tf::after { padding: 0; }
.tf-input {
  display: block; width: 100%; height: calc(var(--tf-lh) + 44px); margin: 0; padding: 0 26px;
  -webkit-appearance: none; appearance: none; border: 0; border-radius: 36px; background: transparent;
  font-family: Lora, Georgia, serif; font-weight: 500; line-height: var(--tf-lh);
  letter-spacing: .5px; color: var(--ink); caret-color: var(--burgundy); cursor: text;
}
.pf-desktop .tf-input { height: calc(var(--tf-lh) + 36px); }
.pf-desktop .tf:not(.off):hover { --b-fill: #F5F5F5; }   /* M3 filled hover (mouse only) */
.tf-input::placeholder { color: var(--muted); opacity: 1; letter-spacing: var(--tf-hint-ls); }
.tf-input::selection { background: rgba(122, 31, 43, .4); }
.tf-input:disabled { opacity: 1; color: var(--ink); -webkit-text-fill-color: var(--ink); cursor: default; }

/* ---------------------------------- _SingleChoiceScreen / _MultiChoiceScreen */
.choice { display: flex; flex-direction: column; padding: 28px 24px 24px; }   /* 16 + 12 spacer */
.choice > .pill { margin-bottom: 14px; }
.choice > .title-h { flex: none; margin-bottom: 32px; }
.choice.multi > .title-h { margin-bottom: 24px; }
.choice-list { flex: 1 1 0; min-height: 0; overflow: hidden auto; overscroll-behavior: contain; margin-bottom: 12px; }
.choice-inner { display: flex; flex-direction: column; gap: 12px; padding: 2px 6px 8px 4px; }

/* ---------------------------------------------------- dialogs (overlay) */
.ov-scrim, .ov-content { position: absolute; top: 0; right: 0; bottom: 0; left: 0; }
.ov-content { display: flex; flex-direction: column; }   /* content fills the frame, like a page */
.ov-content > * { flex: 1 1 0; min-height: 0; }

/* 05-data: LevelBar (level_bar.dart) and the Pronounce pill (pronounce_button.dart).
   Text here is Flutter's inherited Lora Medium; w800 is Skia's synthetic bold,
   reproduced with -webkit-text-stroke (spec 04 §1.1).
   Fractional borders (2.5 / 1.5 px) are inset box-shadow rings plus that much
   padding: Chrome floors border-width to whole CSS px (spec 02 §1.5). Boxes with
   a hard shadow keep their fill 1px inside the ring (padding 1px + background-clip
   content-box) so no light anti-aliasing seam shows between ring and shadow. */

/* ---- LevelBar: 56 tall, fills its container's width ---- */
.lb {
  position: relative;
  height: 56px;
  min-width: 0;
  font-family: Lora, Georgia, serif;
  font-weight: 500;
  letter-spacing: 0.25px;
  color: #1F1F1F;
  -webkit-user-select: none;
  user-select: none;
}
.lb-grow { flex: 1 1 0; }

/* Track + bubbles; this box pulses (scale about its centre). */
.lb-inner {
  position: absolute;
  left: 0;
  right: 0;
  top: 12px;
  height: 32px;
}
.lb-track {
  position: absolute;
  left: 22px;
  right: 22px;
  top: 0;
  height: 32px;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1px;
  background: #fff content-box;
  border-radius: 99px;
  box-shadow: inset 0 0 0 2.5px #1F1F1F, 2px 3px 0 #1F1F1F;
  overflow: hidden; /* clips the fill to the outer stadium, as Flutter's Clip.antiAlias */
}
/* Green fill inside the ring; width = progress × (track − 5px), set from JS.
   Placed with a transform: a 2.5px top/left would be pixel-snapped and open a
   hairline under the ring. Flutter's candy stripes: stripe where
   (x + y) mod 22 < 9, anchored at the fill's top-left so they never slide. */
.lb-fill {
  position: absolute;
  left: 0;
  top: 0;
  height: 27px;
  transform: translate(2.5px, 2.5px);
  background:
    repeating-linear-gradient(135deg, rgba(255, 255, 255, 0.18) 0 6.364px, transparent 6.364px 15.556px),
    linear-gradient(#4CAF7C, #2E7D4A);
}
/* "n / span XP" badge with odometer digit slots. */
.lb-xp {
  position: relative;
  display: flex;
  padding: 2.5px 9.5px;
  background: #F2ECDF;
  border-radius: 8px;
  box-shadow: inset 0 0 0 1.5px #1F1F1F;
  font-size: 13px;
  line-height: 17px;
  white-space: pre;
  -webkit-text-stroke: 0.52px currentColor;
}
/* Each text box is Flutter's 16 px (15.6 ceiled) and clips the sliding digit there. The 17 px line
   inside overflows it by 1 px so the baseline sits 13 px down in Chrome and Safari alike (ascent 13
   + descent 4 after rounding, no leading to split), where Flutter draws it (~12.9; a 16 px line
   put it at 12, about 0.9 px high). */
.lb-slot { display: inline-grid; justify-items: center; }
.lb-glyph { grid-area: 1 / 1; overflow: hidden; height: 16px; }
.lb-glyph > span { display: block; height: 16px; }
.lb-suffix { height: 16px; }

.lb-bub {
  position: absolute;
  top: -6px;
  width: 44px;
  height: 44px;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1px;
  background-clip: content-box;
  border-radius: 50%;
  box-shadow: inset 0 0 0 2.5px #1F1F1F, 2px 3px 0 #1F1F1F;
  font-size: 18px;
  line-height: 26px;
  -webkit-text-stroke: 0.6875px currentColor;
}
.lb-cur { left: 0; background-color: #4CAF7C; color: #fff; }
.lb-next { right: 0; background-color: #fff; color: #8A8275; }

/* Star burst canvas (120×120, 14 dots) and the rising "+N XP" chip; neither pulses. */
.lb-burst {
  position: absolute;
  left: 22px;
  top: -32px;
  width: 120px;
  height: 120px;
  pointer-events: none;
}
.lb-chip {
  position: absolute;
  left: 50%;
  top: -6px;
  padding: 5px 12px;
  background: #E9D8A6;
  border: 2px solid #1F1F1F;
  border-radius: 99px;
  box-shadow: 2px 2px 0 #1F1F1F;
  font-size: 14px;
  line-height: 20px;
  white-space: nowrap;
  -webkit-text-stroke: 0.556px currentColor;
  pointer-events: none;
}

/* ---- Pronounce pill (and the empty 44 px slot for words without a clip) ---- */
.pron-gap { height: 44px; }
.pron {
  display: inline-flex;
  height: 44px;
  margin: 0;
  padding: 1px;
  box-sizing: border-box;
  flex: none;
  background: #fff content-box;
  border: 0;
  border-radius: 99px;
  box-shadow: inset 0 0 0 2.5px #1F1F1F, 2px 3px 0 #1F1F1F;
  color: #1F1F1F;
  font: 500 15px/21px Lora, Georgia, serif;
  letter-spacing: 0.25px;
  -webkit-text-stroke: 0.59px currentColor;
  -webkit-tap-highlight-color: transparent;
  -webkit-user-select: none;
  user-select: none;
  touch-action: manipulation;
  transition: transform 80ms linear, box-shadow 80ms linear, background-color 80ms linear;
}
/* Pressed: the pill sinks onto its shadow. */
.pron.is-down { transform: translate(2px, 3px); box-shadow: inset 0 0 0 2.5px #1F1F1F, 0 0 0 #1F1F1F; }
.pron.is-playing { background-color: #E9D8A6; }
.pron-in { display: flex; align-items: center; gap: 8px; padding: 0 19.5px; }
.pron svg { flex: none; }
.pron .pron-on, .pron.is-playing .pron-off { display: none; }
.pron.is-playing .pron-on { display: block; }

/* Intro steps: 01-Welcome (.wel-), 04-Name (.nm-), 02-SignIn (.sgn-), Text Pip (.tp-). Spec 01. */

/* ------------------------------------------------------------ 01 Welcome */
.wel { display: flex; flex-direction: column; align-items: center; padding: 24px; }
.wel > * { flex: none; }
.wel > .wel-sp1 { flex: 1 1 0; min-height: 0; }
.wel > .wel-sp2 { flex: 2 1 0; min-height: 0; }
.wel-row { display: flex; align-items: flex-start; align-self: stretch; }
.wel-face { margin: 4px 12px 0 0; }
.wel-bubble { flex: 1 1 0; min-width: 0; }
.wel > .shot-slot { flex: 0 1 auto; align-self: stretch; margin: 22px 0 24px; }
.wel > .pbtn { align-self: stretch; }

/* Pip's typed speech: Flutter's proportional leading puts the glyphs ~1.16px (22px) / 0.89px (19px)
   below CSS's even split. Chrome floors core's fractional `top` to whole px; a transform moves the
   text by device px. Values measured against the Flutter frames. */
.wel-bubble .tw-reveal { top: 0 !important; transform: translateY(1.5px); }
.sgn-bubble .tw-reveal { top: 0 !important; transform: translateY(.5px); }   /* 19px and compact 16px */

/* ------------------------------------------------------------- 02 SignIn */
.sgn { display: flex; flex-direction: column; padding: 8px 24px 24px; }
.sgn-mid { flex: 1 1 0; min-height: 0; display: flex; flex-direction: column; }
/* Clips (and scrolls) only when the block does not fit, like SingleChildScrollView. */
.sgn-mid.scroll { overflow: hidden auto; overscroll-behavior: contain; scrollbar-width: none; }
.sgn-mid.scroll::-webkit-scrollbar { display: none; }
.sgn-content { flex: none; margin: auto 0; position: relative; }
.sgn-row { display: flex; align-items: flex-start; }
.sgn-face { margin: 4px 12px 0 0; }
.sgn-bubble { flex: 1 1 0; min-width: 0; }
.sgn-field { margin-top: 22px; }
/* Chrome centres the 20px line 1px lower than Flutter in the 64/72px field. */
.sgn-field .tf-input { padding-bottom: 2px; }
/* InputDecorator paints its white fill to the outer edge, under the ring: the fill's antialiased
   edge leaves a pale hairline between ring and shadow (unlike BoxDecoration boxes). */
.sgn-field::after, .nm-field::after { padding: 0; }
.sgn-error {
  margin-top: 14px; text-align: center; color: var(--burgundy);
  font-size: 15px; line-height: 21px; letter-spacing: .25px; -webkit-text-stroke: .59px currentColor;
}
.sgn-error[hidden] { display: none; }
.sgn-cta { flex: none; margin-top: 12px; opacity: 0; pointer-events: none; transition: opacity 450ms linear; }
.sgn-cta.on { opacity: 1; pointer-events: auto; }
.sgn-note {
  flex: none; margin-top: 14px; text-align: center; color: var(--muted);
  font-size: 15px; line-height: 21px; letter-spacing: .25px; -webkit-text-stroke: .59px currentColor;
}
/* The Text Pip state: Pip's number (Pip.textPipNumber) under the sms: button. */
.sgn-after { flex: none; margin-top: 14px; display: flex; flex-direction: column; align-items: center; }
.sgn-after + .sgn-note { margin-top: 6px; }

/* Text Pip (also on the done screen): "Pip's number: (646) 290-4347", the number an sms: link. */
.tp-number {
  text-align: center; color: var(--muted);
  font-size: 15px; line-height: 21px; letter-spacing: .25px; -webkit-text-stroke: .59px currentColor;
}
.tp-num {
  color: var(--ink); white-space: nowrap; cursor: pointer;
  text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 1.5px;
}

/* Short viewports (MediaQuery height < 640): smaller Pip and type, tighter gap. */
@media (max-height: 639.98px) {
  .sgn-face { width: 60px !important; height: 60px !important; }
  .sgn-bubble .tw { font-size: 16px !important; line-height: 22px !important; }
  .sgn-field { margin-top: 16px; }
}

/* ---------------------------------------------------------------- 04 Name */
.nm { display: flex; flex-direction: column; padding: 28px 24px 24px; }   /* 16 + SizedBox 12 */
.nm > .title-h { flex: none; }
.nm-field { margin: 32px 0 24px; }

/* Questionnaire: Onboarding-11-Categories (chip cloud). Steps 12–19 are core's choiceScreen.
   Body: padding 16 + 12 spacer on top, TitleHeader, 24 gap, the scroll area, 12 gap, PrimaryButton. */
.cat { display: flex; flex-direction: column; padding: 28px 24px 24px; }
.cat > .title-h { flex: none; margin-bottom: 24px; }
.cat > .pbtn { margin-top: 12px; }

/* SingleChildScrollView: no padding, hard clip at its edges, no native scrollbar (Flutter's desktop
   scrollbar is drawn by .q-track and takes no width). */
.cat-view { flex: 1 1 0; min-height: 0; position: relative; }
.cat-scroll {
  position: absolute; top: 0; right: 0; bottom: 0; left: 0;
  overflow: hidden auto; overscroll-behavior: contain; scrollbar-width: none;
}
.cat-scroll::-webkit-scrollbar { display: none; }

/* Wrap(spacing 10, runSpacing 12). The scroll range must be the Wrap's height (Flutter clips the last
   row's shadow at max scroll), so the chip shadows are drawn as a filter, which (unlike .brutal's
   translated ::before) adds no scrollable overflow. Same rrect, same offset, painted under the chip. */
.cat-wrap { display: flex; flex-wrap: wrap; gap: 12px 10px; align-items: flex-start; }
.cat-wrap .chip::before { display: none; }
.cat-wrap .chip::after { filter: drop-shadow(var(--b-dx) var(--b-dy) 0 var(--ink)); }

/* Steps 12–19 on desktop: core's .choice-list inside a positioned view for the scrollbar overlay. */
.q-view { flex: 1 1 0; min-height: 0; position: relative; margin-bottom: 12px; }
.q-view > .q-list { position: absolute; top: 0; right: 0; bottom: 0; left: 0; margin: 0; scrollbar-width: none; }
.q-view > .q-list::-webkit-scrollbar { display: none; }

/* Material Scrollbar (desktop OSes only; MaterialScrollBehavior): 12px track strip, 8px thumb 2px
   from the edge. Fade in with fastOutSlowIn, out with its reverse (Flutter reverses the curve). */
.q-track {
  position: absolute; top: 0; right: 0; bottom: 0; width: 12px;
  opacity: 0; pointer-events: none;
  transition: opacity 300ms cubic-bezier(.8, 0, .6, 1);
}
.q-track.on { opacity: 1; pointer-events: auto; transition-timing-function: cubic-bezier(.4, 0, .2, 1); }
.q-thumb {
  position: absolute; top: 0; right: 2px; width: 8px; border-radius: 8px;
  background: rgba(31, 31, 31, .1); transition: background-color 200ms linear;
}
.q-thumb.hover { background: rgba(31, 31, 31, .5); }
.q-thumb.drag { background: rgba(31, 31, 31, .6); transition: none; }

/* 30-plan: 21 BuildingPlan (.bp-), 22 OneMinuteADay, 22b PlanReady, 11a PipIntro (.pr-). */

/* ------------------------------------------------------ 21 BuildingPlan */
.bp-root { display: flex; flex-direction: column; }
.bp-stage { flex: 1 1 0; min-height: 0; position: relative; }
/* The storm fills the stage edge to edge and fades out at the left/right 10 % (ShaderMask dstIn). */
.bp-storm {
  position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; letter-spacing: 0;
  -webkit-mask-image: linear-gradient(to right, transparent 0%, #000 10%, #000 90%, transparent 100%);
  mask-image: linear-gradient(to right, transparent 0%, #000 10%, #000 90%, transparent 100%);
}
.bp-over { position: absolute; top: 0; right: 0; bottom: 0; left: 0; display: flex; flex-direction: column; padding: 0 24px; }
.bp-sp2 { flex: 2 1 0; min-height: 0; }
.bp-sp3 { flex: 3 1 0; min-height: 0; }

/* _PipSpeechRow: 76px Pip, 4px gap, tailed bubble (text column 218px wide at 390). */
.bp-row { flex: none; display: flex; align-items: center; }
.bp-bubble { flex: 1 1 0; min-width: 0; margin-left: 4px; position: relative; padding: 14px 18px 16px 26px; }
.bp-bg { position: absolute; left: 0; top: 0; overflow: visible; }
.bp-bshadow { fill: var(--ink); }
.bp-bface { fill: #FFFFFF; stroke: var(--ink); stroke-width: 2.5px; stroke-linejoin: round; }
.bp-bubble > .pb-name, .bp-bubble > .tw { position: relative; }
.bp-bubble > .tw { margin-top: 6px; color: #000000; }

/* LinearProgressIndicator (value, minHeight 10) in a 99-radius clip, glowing (box-shadow set per frame). */
.bp-barwrap { flex: none; padding: 8px 24px 24px; }
.bp-bar { height: 10px; border-radius: 99px; overflow: hidden; background: var(--cream-soft); }
.bp-fill { height: 100%; background: var(--forest); transform: scaleX(0); transform-origin: left center; }

/* ------------------------------------------- 22 / 22b / 11a shared column */
.pr-col { display: flex; flex-direction: column; padding: 16px 24px 24px; }
.pr-center { align-items: center; }
.pr-center > * { flex: none; }
.pr-center > .pbtn, .pr-center > .pr-card { align-self: stretch; }
.pr-col > .pr-sp1 { flex: 1 1 0; min-height: 0; }
.pr-col > .pr-sp2 { flex: 2 1 0; min-height: 0; }
.pr-face { flex: none; display: flex; justify-content: center; margin-top: 22px; }

/* ------------------------------------------------- 22 OneMinuteADay */
/* The iMessage screenshot takes the portrait's place: 22px under the bubble, up to its natural height,
   shrinking (before the spacers, which only share what it leaves) so Continue stays on screen; 24px
   keeps its shadow clear of the button. */
.pr-center > .shot-slot { flex: 0 1 auto; align-self: stretch; margin: 22px 0 24px; }

/* ------------------------------------------------------- 22b PlanReady */
.pr-card { margin-top: 28px; padding: 10.5px 22.5px; border-radius: 28px; --b-dx: 4px; --b-dy: 6px; }
.pr-row { display: flex; align-items: flex-start; padding: 14px 0; }
.pr-ic { flex: none; fill: var(--forest); margin-left: -.5px; }
.pr-txt { flex: 1 1 0; min-width: 0; margin-left: 16.5px; }
/* Inherited-family text: Lora 500; w600 renders plain, w700 is Skia's faux bold. */
.pr-label { font-size: 16px; line-height: 23px; color: var(--muted); }
.pr-value { margin-top: 2px; font-size: 20px; line-height: 29px; color: var(--ink); -webkit-text-stroke: .75px currentColor; }

/* -------------------------------------------------------- 11a PipIntro */
.pr-intro { padding-top: 8px; }
/* Center > SingleChildScrollView clips to its viewport only when the column outgrows it (.over, set by
   JS from layout heights). It then clips at x 24→W−24, cutting the bubble's right shadow. The clip on
   .pr-inner also keeps the face's 6px shadow out of the scroll range, which Flutter never counts.
   Content that fits is not clipped, so nothing stops the face shadow painting into the 12px gap. */
.pr-scroll { flex: 1 1 0; min-height: 0; display: flex; flex-direction: column; overscroll-behavior: contain; }
.pr-scroll.over { overflow: hidden auto; }
.pr-inner { flex: none; margin: auto 0; }
/* hidden first: Safari < 16 drops overflow:clip; hidden clips the same here (.pr-inner is never scrolled). */
.pr-scroll.over > .pr-inner { overflow: hidden; overflow: clip; }
.pr-cta { flex: none; margin-top: 12px; opacity: 0; pointer-events: none; transition: opacity 450ms linear; }
.pr-cta.on { opacity: 1; pointer-events: auto; }

/* Pip's typed Inter 22/31 text. Chrome rounds Inter's ascent/descent to whole px (baseline 23px into
   the 31px line, not 23.5) and the line top to a whole px; Flutter's proportional leading puts the
   baseline 24.66px in. Offsetting the typed layer by 24.66 − 23 lands it within ±0.5px of Flutter
   (core's inline 1.16px leaves it 0.5–1px high, measured in 21-t06 / 22 / 11a). */
.bp-bubble .tw-reveal, .pr-col .tw-reveal { top: 1.66px !important; }
/* Flutter's '▍' is 22px tall (1em) and ends 2.5px under the baseline (21-t01, 21-t09, 11a-a). */
.bp-bubble .tw-caret, .pr-col .tw-caret { height: 1em; vertical-align: -.114em; }

/* 40-trial: Onboarding-11e-TrialHome (home_screen.dart, trial). Lora here is the inherited Medium
   face; Flutter's w700/w800 is Skia's synthetic bold (-webkit-text-stroke = Pip.fauxBold(size)).
   Line heights are Flutter's rounded line boxes. The 1.5px tag ring is an inset shadow (Chrome
   floors fractional border widths). */

.th-root {
  display: flex; flex-direction: column; align-items: center;
  padding: 12px 20px 16px; overflow: hidden; background: var(--cream); color: var(--ink);
}
.th-top { flex: none; display: flex; width: 100%; }
.th-sp3 { flex: 3 1 0; min-height: 0; }
.th-sp4 { flex: 4 1 0; min-height: 0; }

/* AnimatedSwitcher: every card in one centred cell; the cell is as tall as the tallest card. */
.th-switcher { flex: none; display: grid; grid-template-columns: minmax(0, 1fr); place-items: center; width: 100%; }
.th-card { grid-area: 1 / 1; width: 100%; display: flex; flex-direction: column; align-items: center; }

/* Topic tag: 36 tall (1.5 ring + 7 + 19 + 7 + 1.5). */
.th-tag {
  flex: none; display: inline-flex; align-items: center; gap: 6px; padding: 8.5px 15.5px;
  border-radius: 99px; box-shadow: inset 0 0 0 1.5px var(--ink-soft); color: var(--ink-soft);
  font-size: 13px; line-height: 19px; letter-spacing: .3px; white-space: nowrap;
}
.th-tag .ic { flex: none; display: block; }

/* Headword: one line, FittedBox scaleDown (JS sets the scale and the box height 59·s). */
.th-word-box { flex: none; position: relative; width: 100%; height: 59px; margin-top: 22px; }
.th-word, .th-ruler {
  flex: none; white-space: nowrap; font-family: Lora, Georgia, serif; font-weight: 500;
  font-size: 56px; line-height: 59px; letter-spacing: .25px; -webkit-text-stroke: 1.75px currentColor;
}
.th-word { position: absolute; left: 50%; top: 0; color: var(--ink); transform: translateX(-50%); transform-origin: 50% 0; }
.th-ruler { position: absolute; left: -10000px; top: 0; visibility: hidden; pointer-events: none; }

/* Pronounce pill / its empty 44px slot (05-data). */
.th-card > .pron, .th-card > .pron-gap { flex: none; margin-top: 16px; }

.th-def {
  flex: none; max-width: 100%; margin-top: 14px; text-align: center;
  font-size: 19px; line-height: 26px; letter-spacing: .25px; color: var(--ink);
}
.th-pos { color: var(--muted); }

/* Pip + tail + speech bubble (_PipSpeechBubble). */
.th-pip { flex: none; display: flex; align-items: center; width: 100%; margin-top: 22px; padding: 0 4px; }
.th-tail { flex: none; display: block; margin-left: 4px; overflow: visible; }
.th-bubble { flex: 1 1 0; min-width: 0; padding: 14.5px 16.5px; border-radius: 18px; --b-dx: 3px; --b-dy: 4px; }
.th-pip-label {
  font-size: 11px; line-height: 16px; letter-spacing: .4px; color: var(--burgundy);
  -webkit-text-stroke: .45px currentColor;
}
/* systemSans RichText: no inherited letter-spacing; proportional leading puts the baseline ~.7px
   lower than CSS's even leading. */
.th-example {
  position: relative; top: .75px; margin-top: 4px;
  font-family: Inter, system-ui, sans-serif; font-style: italic; font-weight: 500;
  font-size: 17px; line-height: 23px; letter-spacing: 0; color: var(--ink);
}
.th-nw { white-space: nowrap; }
/* Flutter's '▍' (fallback font, muted, upright): 6.4 × 17 box, advance ≈ 17. */
.th-cursor {
  display: inline-block; width: 6.375px; height: 17px; margin-right: 10.6px;
  vertical-align: -2.1px; background: var(--muted);
}

/* Empty state (no word for the followed topics). */
.th-empty { display: flex; flex-direction: column; align-items: center; padding: 0 12px; }
.th-empty-t {
  margin-top: 16px; text-align: center; font-size: 22px; line-height: 31px; color: var(--ink);
  -webkit-text-stroke: .81px currentColor; transform: translateY(1px); /* Flutter's glyphs sit 1px lower */
}
/* "Browse topics": padding 12/22 + the 2.5 ring, shadow 3/4, 16px w700 (faux bold). */
.th-browse {
  flex: none; margin-top: 16px; padding: 14.5px 24.5px; border-radius: 99px;
  --b-fill: var(--burgundy); --b-dx: 3px; --b-dy: 4px; color: #FFFFFF;
  font-size: 16px; line-height: 23px; letter-spacing: .25px; white-space: nowrap;
  -webkit-text-stroke: .62px currentColor;
}

/* Bottom bar (web: caption in flow, 6px under the row). */
.th-bottom { flex: none; display: flex; flex-direction: column; align-items: center; width: 100%; }
.th-row { display: flex; align-items: center; width: 100%; height: 60px; }
.th-side { flex: none; width: 60px; height: 60px; }
.th-mid { flex: 1 1 0; min-width: 0; display: flex; justify-content: center; }
.th-next {
  flex: none; display: inline-flex; align-items: center; gap: 8px; height: 60px; padding: 0 34.5px;
  border-radius: 99px; --b-fill: var(--burgundy); --b-dx: 4px; --b-dy: 5px; color: #FFFFFF;
  font-size: 19px; line-height: 27px; letter-spacing: .25px; white-space: nowrap;
}
.th-next.th-dim { --b-fill: rgba(122, 31, 43, .4); }
.th-next-label { -webkit-text-stroke: .718px currentColor; }
.th-next .ic { flex: none; display: block; }
.th-caption {
  margin-top: 6px; max-width: 100%; text-align: center; pointer-events: none;
  font-size: 13px; line-height: 19px; letter-spacing: .25px; color: var(--muted);
}

/* 50-quiz: trial quiz, stage-promotion card, results, level-up popover (spec 05).
   Text: Lora 500 everywhere (Flutter's inherited bodyMedium file); w700/w800 = .fb faux bold
   with --fb = Pip.fauxBold(size); line boxes are round(size × height) px.
   Brutal boxes use core's .brutal (shadow + fill + 2.5px ring as pseudo-elements). */

/* Emoji: Flutter web draws 🌱 🌿 🌟 ⚡ with Noto Color Emoji (COLRv1, fetched from gstatic) on every
   browser. QzNotoEmoji is that font (v32) subset to those four glyphs (2.5 KB); Chrome and Firefox
   use it. Safari has no COLRv1 (it skips the tech() source), so it gets QzNotoEmoji0: the same
   glyphs, metrics and colours as COLRv0 (the subset only paints solid layers; the two mirrored
   ones are baked into outlines). The advance is pinned to Noto's 1275/1024 em either way. */
@font-face {
  font-family: 'QzNotoEmoji'; font-display: block;
  src: url(data:font/woff2;base64,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) format('woff2') tech(color-COLRv1);
}
@font-face {
  font-family: 'QzNotoEmoji0'; font-display: block;
  src: url(data:font/woff2;base64,d09GMgABAAAAAAlQAA8AAAAAD9QAAAj7AAIMCAAAAAAAAAAAAAAAAAAAAAAAAAAAIoEWIz4cKgZgAIEECpNIjlkBNgIkA0QLRAAEIAWDGgcgFyQYRBsaDFGUcFYlxEdiHBO5c0MTm+/gAfC0f0lTaeycke3gdB3I7mTs7gNEh/KsnW/3GxyltAd6Od/0Owi6TqEVs/bAqrXU/Wpfbr0iVEEhE6XQuPt96CeeAKmPQ5RxpGSEigR2wDYvVIyNsC5GxdYaaTTyMOcIxIWqV89zAhIscwmEI5DoENVCNSrMGAF01KAaHRpoEQXZclIPooFoDKBJwRy0IGEN2gA7/OCPAAQGgxAIhgMhF4ciEoaWcPREYCCyBMGEcAFYIPrUgC/twf2+9PKI4odP+y5493Mfjb/6ct/8zyfd+b9P+5DPl/hUUdOfqf2+2INcbSCBJMTK6sZOLPPGli1gASD75HpJ+kc4hBrEM6IXaDJPJ3hxCk7EJ6Jg/QXlM3kw4SKfT3W7TwOG4u8qAtQuPjcho3/BUOXipqzkjMAOFQYtIS+WkBAgqTGqb71IuAYTwNXTEsCoDkgIv0QJCGYmWwmINVOuzOWDEA3XVADhIP4KqneQE3TIIQinlEm+NMlqiwx3BAemOmITU5PsNmugpbI2OMJkmuQHnNaCJKbcQLb6Im4rd1DM9YejuZ9oWDWwFQiYRM8jMU+5G9nz1EsUq0ZyyV0rGdVfdBx8huPpRfdLN4coTK173rzsT0nb+njZriAkJGBaal6iObXY7d4fgjSuYkv5YcASzhdytgRFMgpqHFoXc1ydNsiQFJXgjwGLcdP+Filvz0u5CroaGTozwO74jEWe0O7D09xo4pNZcPAOHGEiOCaEqM/rl1KnWEH+zMwGn/UoiCymUn7gfFbFlkamxCUh1sUcnUqetxC2zrATtYyxEwk2oiDL0kjzolSZTsNhADumN3dLKU+r3i15OMIYO+IXh5oXi6VVLX18/IzhH7BxDWr0wG7Ysp62AFdeHpdnR3lOcesEW+MxcYjE8mjb1jq+IlKbSQBo5xRbFjgAXRdAB8BY1zZHyKrFbkPNEzp4sYlzo4SzcgCxim0bnICli5wUcl2zRsbmtlUL+W5QWuq8uAPTHevjUQDtbOfeNi7H18fHxzt0KIJCsDXeRAbAWMDz/pAyub1mukXFvr8BZTKJPBJaHG7yFb1mmt/a+AaVVgJYG9VwSXmOVWE9iWcX2pxvtTuzije1LSXEMuh8ceT++GCTag/s8QaafxiwZVzK4dwwthTEJDVjXxgDMF1TkwYPoEsU02R9c8AkGomDF/BVFeV0TMrebbWzmR+035uqAWX9DPSMlP+rtMjcsPUC1LOVKQmUi9M67aXNaWrNdwNo9k2mGSDwB8pkbCpWFKRq+s/p112oEe8NnZ6f19Mq1b2ymCjIn5jxDUw8wI7mhair3SKecgcl84ww5n/PnhYkrkdYM/imd337jtpsr93rvw3NI2x8fWfNtsQQuyf5ASPzlHK5PO6440ZDjeGBEFKcSxZ+9dbG9VEfTzjDhGT5mS8eemuFp0hOfvMP44yfaJz5861k2VO04qvPH3pWThKdoRMfc1pp/KNYf8pWfhUj57XWIB1F2Y7DW5sCgh4pubCM12Mqz3w4xbmfWvZzxuq21g5aGsR9q0KPya8++hrxgdJb9Q3ewH6pQqoyz/kPThptxzG/UOD8dcjhLX/udPpKzSKM0omTi+7o2pLv9sRmN0ZVfIXSH3hk2UdXC60R82syN/7IY5ZbuWnaskVTHZLTqZTVJ1cq5MaceLTXo0SPX/rAyuqti5+eWV/SvWFNz09xaZE1RWNclVvlmCzfWjVz9PbJJ0ddj3VHrvcXhKBXjyjCpBnnxx13MP/buoHSPxInY4ZzBh96QUnWRufc/qoyWv66cWWMTY6CLYem5LTWpBHn/P25rX0jSlYL9Z4TzPOvS+gtGbjD3RbTYHwwF6qtfq3pnZdlDSYPB7XpZZ6RGa98ecEznKDk2EtqHne541fuYOFoVZRa1Xy1kvj01V32Memx2wzEOCfloX1/2RckBcy9qhrSrXqjxCa0Fl1QE8zPC3xGWy1VPzP2/F/HGXim6rdTI025nn5H7ff6EbHVDQMb+s51U8P5jo5VcFPMLbfkPXxdjmPYNKZuCeDuaf5tiq+ntW1UvhrO7rwz7cYedYvhp+q8t947rTHNr5lIrggds9Tqn3yz6kc4FjR6Bl99hyXQc/FFb75dkMyf0gczaxxk3TQ+a+arFVOb95jMn/y4ffurRv7//sa/54ZYAj0fGkpKQg32Fk60qiNvmAqskOvdjyUKTzbvuHBd9Kjf7FD9CDPdKOHjF47O0tv+v+Xlbj9uvju+jrU/1fpX2lP09utjcrsrSmYNbFywY8n6OVNdj9iOJ+hVYZcdc/B0GSy8UcptpKuBLFH9aUpYJmk38X5DY//A4EBzY4ZkVhfYP85uLhouHMxqeoEt79kripo9gxWDeY3fq1Q3VRd2VffWtXoeejuzurDVU1f6BfzYIHQLrcJmncU94b/mFn+Hq7toPLc/wd0b0SK43c967vAUN1c1VSmF2dlKUWN1Q9VfE5DXwQg9xdKLQayMzPsutSKjNkV5Nu5I7Yz9jXedVVlK1vSNIceXTjG35Y7yd1d8F33y9DWuNfc9lbN44ebXV+xYHBqiTF+960oKxKGtndv8vXZvdm37jvVeypVHT4nGB5LtnpDEbTtrXt+IKIErRJPuijD3kRFryW86lUTv+PiX3/8AfP7p7FQUn1W6UXUK0CAGmq+GovQPQNFwkoRQWNz3/dHub+kR62gQt6kaxAnxFIr4BNdyrfiSWCbcT4/whvi3ShLe5YA4wxO8LVwmFKgGVO8gANHUmjlaf1pa+lGRXdLDZaZRgThcURELvVEVMt6+UlTNeXY8TUO4EIeHhSxiNUuYzTSzWIZMDlmKOQdrAFy4pOcxdXEdC5gggzbW0MxClimpYj5xToL3yHhooZF2VpBFO1NMs5x5jLEkIAbjQjcWHpTDpZupg0snYwtZcBPM4KCLfIrZcmIN5RYkmkpnKjhXdMN0GSxg5jt1HhM6m2yrYlxcuAbHpnRCWGCMjZEbLF4KiflbZjwdnJJD4VjrZYIpJh8g2fa3igoZ0XcLTn6hAOKG/7aA9DEwKW1ABlaQPgY=) format('woff2');
}
/* The colour paints nothing (no layer uses the text colour). WebKit leaves the last COLR layer's
   colour in the graphics context after drawing these glyphs, and skips re-setting a fill colour equal
   to the one it believes is current: with an ink emoji, the ink text after it came out yellow. */
.qz-emoji {
  display: inline-block; width: 1.2451em; text-align: center; color: #1F1F1E;
  font-family: 'QzNotoEmoji', 'QzNotoEmoji0', 'Apple Color Emoji', 'Segoe UI Emoji', 'Noto Color Emoji', sans-serif;
  font-weight: 400; letter-spacing: 0; -webkit-text-stroke: 0;
}

.qz { position: relative; display: flex; flex-direction: column; background: var(--cream); color: var(--ink); }
.qz > * { flex: 1 1 0; min-height: 0; }

/* ------------------------------------------------------------ question */
.qz-q { display: flex; flex-direction: column; padding: 8px 24px 24px; }
.qz-q > * { flex: none; }
.qz-hdr { display: flex; align-items: center; }
.qz-close {
  flex: none; width: 44px; height: 44px; margin-right: 10px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center; --b-dx: 2px; --b-dy: 3px;
}
.qz-count {
  margin-top: 8px; text-align: center; color: var(--muted);
  font-size: 15px; line-height: 21px; --fb: .59px;
}
.qz-piprow { margin-top: 8px; height: 84px; display: flex; align-items: center; }
.qz-bob { flex: none; animation: qz-bob 1400ms cubic-bezier(.42, 0, .58, 1) infinite alternate; }
@keyframes qz-bob { from { transform: none; } to { transform: translateY(-5px); } }
.qz-chipwrap { flex: 1 1 0; min-width: 0; margin-left: 12px; display: flex; align-items: center; }
.qz-chip {
  flex: none; max-width: 100%; padding: 14.5px 20.5px; border-radius: 22px; --b-dx: 3px; --b-dy: 4px;
  transform: translateY(-.5px) scale(0); transform-origin: 0 50%; transition: transform 260ms var(--ease-out-back);
}
.qz-chip.on { transform: translateY(-.5px); }
.qz-chip-t { display: block; transform: translateY(.5px); min-height: 26px; white-space: pre-wrap; font-size: 18px; line-height: 26px; --fb: .6875px; }

.qz-fit { position: relative; }
.qz-fit > * { position: absolute; left: 0; top: 0; transform-origin: 0 0; white-space: pre; }
.qz-title-box { margin-top: 10px; }
.qz-title { font-size: 30px; line-height: 34px; --fb: 1.007px; }

/* The list (a scroll viewport) clips its tiles; the stage card may overshoot the area unclipped. */
.qz-optarea { flex: 1 1 0; min-height: 0; margin-top: 20px; position: relative; }
.qz-opts {
  position: absolute; top: 0; right: 0; bottom: 0; left: 0; overflow: hidden auto; overscroll-behavior: contain;
  padding: 2px 6px 8px 4px; display: flex; flex-direction: column; gap: 12px;
}
.qz-tile {
  flex: none; width: 100%; display: flex; align-items: center; padding: 18.5px 24.5px; border-radius: 28px;
  --b-dx: 3px; --b-dy: 4px; --b-t: 150ms; transition: opacity 150ms linear;
  font-size: 16px; line-height: 23px; color: var(--ink); text-align: left;
}
.qz-tile-l { flex: 1 1 0; min-width: 0; transform: translateY(-.5px); }
.qz-ic { display: none; flex: none; margin-left: 10px; width: 24px; height: 24px; transform: translate(-.5px, -.5px); }
.qz-ic .ic { display: block; }
.qz-tile.ok { --b-fill: var(--success); color: #FFFFFF; }
.qz-tile.bad { --b-fill: var(--gift-red); color: #FFFFFF; }
.qz-tile.ok .qz-ic-ok, .qz-tile.bad .qz-ic-bad { display: block; }
.qz-tile.dim { opacity: .45; }

/* ------------------------------------------------- stage-promotion card */
.qz-sc-layer {
  position: absolute; top: 0; right: 0; bottom: 0; left: 0;
  display: flex; align-items: center; justify-content: center; pointer-events: none;
}
.qz-sc {
  width: calc(100% - 24px); padding: 20.5px 22.5px 18.5px; border-radius: 24px; --b-dx: 4px; --b-dy: 5px;
  display: flex; flex-direction: column; align-items: center;
}
.qz-sc .qz-fit { flex: none; }
.qz-sc-word { font-size: 26px; line-height: 37px; --fb: .913px; }
.qz-sb {
  flex: none; align-self: stretch; position: relative; height: 40px; margin-top: 14px; border-radius: 20px;
  overflow: hidden; background: var(--cream-soft); box-shadow: inset 0 0 0 2.5px var(--ink);
}
/* Fill and sections sit inside the ring but paint over it where the stadium clip allows
   (Flutter paints the child after the border); placed by transform to avoid pixel snapping. */
.qz-sb-fill {
  position: absolute; left: 0; top: 0; width: 0; height: 35px; transform: translate(2.5px, 2.5px);
  background: linear-gradient(#4CAF7C, #2E7D4A);
}
.qz-sb-row {
  position: absolute; left: 0; top: 0; width: calc(100% - 5px); height: 35px; transform: translate(2.5px, 2.5px);
  display: flex;
}
.qz-sb-sec { flex: 1 1 0; min-width: 0; display: flex; align-items: center; justify-content: center; }
.qz-sb-sec:not(:last-child) { border-right: 2px solid var(--ink); }
.qz-sb-emo { display: block; font-size: 18px; line-height: 26px; }
.qz-sc-label { margin-top: 12px; color: var(--forest); font-size: 17px; line-height: 24px; --fb: .656px; opacity: 0; }

/* ------------------------------------------------------------- results */
.qz-res { display: flex; flex-direction: column; padding: 8px 28px 28px; }
.qz-res > * { flex: none; }
.qz-res > .qz-sp2 { flex: 2 1 0; min-height: 0; }
.qz-res > .qz-sp3 { flex: 3 1 0; min-height: 0; }
.qz-res-pip { display: flex; justify-content: center; }
.qz-res-title { margin-top: 24px; text-align: center; font-size: 34px; line-height: 49px; --fb: 1.089px; }
.qz-res-score { margin-top: 10px; transform: translateY(1px); text-align: center; color: var(--muted); font-size: 19px; line-height: 27px; --fb: .718px; }
.qz-rows { margin-top: 28px; display: flex; flex-direction: column; }
.qz-gap { height: 12px; }
.qz-row {
  display: flex; align-items: center; padding: 16.5px 20.5px; border-radius: 18px; --b-dx: 3px; --b-dy: 4px;
}
.qz-row-emo { flex: none; transform: translateY(.5px); font-size: 22px; line-height: 31px; margin-right: 12px; }
.qz-row-t { flex: 1 1 0; transform: translateX(.5px); min-width: 0; font-size: 17px; line-height: 24px; --fb: .656px; }
.qz-keep {
  height: 60px; border-radius: 99px; --b-fill: var(--burgundy); --b-dx: 4px; --b-dy: 5px;
  display: flex; align-items: center; justify-content: center;
  color: #FFFFFF; font-size: 19px; line-height: 27px; --fb: .718px;
}
.qz-keep > span { transform: translateY(.5px); }

/* ---------------------------------------------------- level-up popover */
.qz-lu { position: relative; }
.qz-confetti { position: absolute; left: 0; top: 0; width: 100%; height: 100%; pointer-events: none; }
.qz-lu-center {
  position: absolute; top: 0; right: 0; bottom: 0; left: 0; padding: 0 28px;
  display: flex; align-items: center; justify-content: center; pointer-events: none;
}
.qz-lu-card {
  width: 100%; pointer-events: auto; padding: 28.5px 26.5px 24.5px; border-radius: 28px;
  --b-fill: var(--cream); --b-dx: 5px; --b-dy: 6px;
  display: flex; flex-direction: column; align-items: center; color: var(--ink);
}
.qz-lu-level {
  flex: none; width: 88px; height: 88px; border-radius: 50%; --b-fill: linear-gradient(#4CAF7C, #2E7D4A);
  --b-dx: 3px; --b-dy: 4px; display: flex; align-items: center; justify-content: center;
  color: #FFFFFF; font-size: 40px; line-height: 57px; --fb: 1.25px;
}
.qz-lu-title { margin-top: 16px; text-align: center; font-size: 27px; line-height: 39px; --fb: .9375px; }
.qz-lu-row { align-self: stretch; margin-top: 16px; display: flex; align-items: center; }
.qz-lu-bubble {
  flex: 1 1 0; min-width: 0; margin-left: 10px; padding: 13.5px 16.5px; border-radius: 18px;
  --b-fill: #FFFFFF; --b-dx: 2px; --b-dy: 3px; font-size: 16px; line-height: 21px; --fb: .623px;
}
.qz-lu-mastered { margin-top: 14px; display: flex; align-items: center; justify-content: center; font-size: 16px; line-height: 23px; --fb: .623px; }
.qz-lu-emo { font-size: 20px; line-height: 29px; margin-right: 8px; }
.qz-lu-btn {
  align-self: stretch; height: 56px; margin-top: 20px; border-radius: 99px;
  --b-fill: var(--burgundy); --b-dx: 3px; --b-dy: 4px;
  display: flex; align-items: center; justify-content: center;
  color: #FFFFFF; font-size: 18px; line-height: 26px; --fb: .6875px;
}

/* Chrome paints boxes at whole px; Flutter keeps the half pixel the 28.5px padding puts the card's
   rows on (card centred at an integer y on even viewport heights). */
.qz-lu-card > * { transform: translateY(-.5px); }
.qz-lu-card > .qz-lu-row { transform: none; }
/* Pip's circle is laid out at (54.5, 437.5): Chrome draws a box there at whole px (55, 438), WebKit and
   Flutter at the half. Lay it out on the whole px and put the half back as a transform (not snapped). */
.qz-lu-row > .avatar { position: relative; left: -.5px; top: -.5px; transform: translate(.5px, .5px); }

/* Professor Pip web onboarding: Onboarding-23-ThreeDaysFree (.td-), Onboarding-26-Paywall (.pw-) with its
   retention offer (.pw-of-) and sign-in sheet (.pw-sheet-), and the done screen (.dn-).
   Ports of Step23ThreeDaysFree and PaywallScreen (progression variant, hard paywall) + _SpecialOfferDialog. */

/* ------------------------------------------------------ 23 ThreeDaysFree */
/* Padding(24, 8, 24, 20) › Column: speech row · 16 · Expanded(gift) · 14 · button. */
.td { display: flex; flex-direction: column; padding: 8px 24px 20px; }
.td-row { flex: none; display: flex; align-items: center; }
/* _TailedPipBubble: painted path behind padded content (26 left, 14 top, 18 right, 16 bottom), as on 21. */
.td-bubble { position: relative; isolation: isolate; flex: 1 1 0; min-width: 0; margin-left: 4px; padding: 14px 18px 16px 26px; }
.td-bubble > .tw { margin-top: 6px; color: #000000; }
.td-bubble .tw-reveal { top: 1.66px !important; }
.td-bubble .tw-caret { height: 1em; vertical-align: -.114em; }
.td-stage { flex: 1 1 0; min-height: 0; margin: 16px 0 14px; display: flex; align-items: center; justify-content: center; }
.td-gift { width: 100%; height: 100%; max-width: 340px; max-height: 340px; }

/* The gift (lottie-web's SVG fills the box, aspect kept), or its still fallback. */
.gift { position: relative; }
.gift > svg { display: block; }
.gift-still { display: block; width: 100%; height: 100%; object-fit: contain; }

/* ------------------------------------------------------------- paywall */
/* Padding(20, 4, 20, 12) › Column(stretch). No legal row on the web (see 60-paywall.js), so its 27 px
   (8 + the 19 px links) sit under the price line instead. */
.pw { display: flex; flex-direction: column; padding: 4px 20px 39px; }
.pw-top { flex: none; display: flex; align-items: center; }
/* _RoundCloseButton: 46 px white disc, giftRedDeep ring 2.5 and Icons.close 26, ink shadow (2, 3). */
.pw-x {
  flex: none; width: 46px; height: 46px; margin-right: 10px; border-radius: 50%; display: grid; place-items: center;
  --b-ring: var(--gift-red-deep); --b-dx: 2px; --b-dy: 3px; cursor: pointer;
}
.pw-x[disabled] { cursor: default; }
.pw-top > .lb { min-width: 0; }

/* Row(Pip 64 · 6 · bubble), 14 below the top row. */
.pw-row { flex: none; display: flex; align-items: center; margin-top: 14px; }
.pw-row > .avatar { margin-right: 6px; }

/* _PipBubble: painted path behind padded content (26 left, 14 top, 18 right, 16 bottom). */
.pw-bubble { position: relative; isolation: isolate; flex: 1 1 0; min-width: 0; padding: 14px 18px 16px 26px; }
.pw-bubble-art { position: absolute; left: 0; top: 0; z-index: -1; overflow: visible; }
/* "Professor Pip": Lora 500 asked for w800 → Skia faux bold (13px → .52px). */
.pw-name {
  font-size: 13px; line-height: 19px; letter-spacing: .5px; color: var(--burgundy);
  -webkit-text-stroke: .52px currentColor;
}
/* The pitch: Inter 800 at --pw-fs (24), height 1.2. */
.pw-pitch {
  position: relative; margin-top: 6px; color: var(--ink);
  font-family: Inter, system-ui, sans-serif; font-weight: 800; font-size: var(--pw-fs, 24px); line-height: 1.2;
}
.pw-pitch-sizer { visibility: hidden; letter-spacing: .25px; }
.pw-pitch-on { position: absolute; left: 0; top: 0; right: 0; letter-spacing: 0; }
/* '▍' (a fallback-font glyph in Flutter): 1em advance, ink .418em wide. */
.pw-pitch .tw-caret { width: .418em; height: 1.03em; vertical-align: -.122em; margin-right: .582em; }

/* Expanded(Center(SingleChildScrollView(vertical 14))): centred while it fits, top-aligned and scrollable
   when not. The 8px side gutters keep the card's shadow out of the scroll clip while it fits. */
.pw-mid {
  flex: 1 1 0; min-height: 0; display: flex; flex-direction: column;
  overflow: hidden auto; margin: 0 -8px; padding: 14px 8px; scrollbar-width: none;
}
.pw-mid.scroll { margin: 0; padding: 14px 0; }
.pw-mid::-webkit-scrollbar { display: none; }
.pw-inner { position: relative; flex: none; margin: auto 0; }
.pw-mid.scroll > .pw-inner { overflow: hidden; overflow: clip; }   /* hidden: Safari < 16 */

/* _TrialTimeline: white card, radius 28, padding 22/18, ring 2.5, shadow (3, 4); the green rail (success,
   44 wide, radius 22, ringed) behind the icons; rows 20 apart: icon box 44 · 14 · text (6 down). */
.pw-tl { border-radius: 28px; padding: 22px 18px; --b-dx: 3px; --b-dy: 4px; }
.pw-tl-rail {
  position: absolute; left: 18px; top: 22px; bottom: 22px; width: 44px; border-radius: 22px;
  background: var(--success); box-shadow: inset 0 0 0 2.5px var(--ink);
}
.pw-tl-row { position: relative; display: flex; align-items: flex-start; }
.pw-tl-row + .pw-tl-row { margin-top: 20px; }
.pw-tl-ic { flex: none; width: 44px; height: 44px; display: grid; place-items: center; }
.pw-tl-badge { width: 28px; height: 28px; border-radius: 50%; background: #FFFFFF; display: grid; place-items: center; }
/* appText(21, height 1.15): the day (and a dayless title) w800 = Lora 500 faux bold, the title w500. */
.pw-tl-text { flex: 1 1 0; min-width: 0; margin: 6px 0 0 14px; font-size: 21px; line-height: 1.15; color: var(--ink); }
.pw-tl-day, .pw-tl-title.fb { --fb: .778px; }
.pw-tl-row.done .pw-tl-text { text-decoration: line-through; text-decoration-thickness: 2.5px; }

/* Error line above the button (13px burgundy). */
.pw-error { flex: none; margin: 0 0 6px; text-align: center; color: var(--burgundy); font-size: 13px; line-height: 18px; }
.pw-error[hidden] { display: none; }
/* PrimaryButton (64) + room for its shadow; the cursor shows once the wallet is ready (.live). */
.pw-pay { flex: none; position: relative; height: 70px; }
.pw-pay.live .pbtn-face:not([disabled]) { cursor: pointer; }
/* 12 below the button: "$4.99/month, billed yearly as " + bold "$59.99/year" (Inter 15, w500 / w800). */
.pw-price {
  flex: none; margin-top: 6px; text-align: center; color: var(--ink);
  font-family: Inter, system-ui, sans-serif; font-weight: 500; font-size: 15px; line-height: 21px;
}
.pw-price > b { font-weight: 800; }

/* ------------------------------------------------------ retention offer */
/* Dialog(insetPadding horizontal 32, radius 28, ring 2.5, cream) centred over the 55 % scrim. */
.pw-of-center, .pw-sheet-center {
  position: absolute; top: 0; right: 0; bottom: 0; left: 0; display: flex; align-items: center; justify-content: center;
  padding: 24px 32px; overflow: auto;
}
.pw-of-card {
  position: relative; flex: none; width: 100%; max-width: 400px; margin: auto 0; padding: 8px 24px 2px;
  border-radius: 28px; background: var(--cream); box-shadow: inset 0 0 0 2.5px var(--ink);
  display: flex; flex-direction: column; align-items: stretch;
}
/* The 290 tall hero: Pip (200, scale 1.3, shadow (3, 4)) centred; the gift (185) at his bottom-left, 75 px
   out, bouncing 6 → −16 (easeInOut, 850 ms each way). */
.pw-of-hero { position: relative; flex: none; align-self: center; width: 200px; height: 290px; display: grid; place-items: center; }
.pw-of-gift {
  position: absolute; left: -75px; bottom: 0; width: 185px; height: 185px;
  animation: pw-gift-bounce 850ms cubic-bezier(0.42, 0, 0.58, 1) infinite alternate;
}
@keyframes pw-gift-bounce { from { transform: translateY(6px); } to { transform: translateY(-16px); } }
.pw-of-title { margin: 0; text-align: center; font-size: 24px; line-height: 1.15; color: var(--ink); --fb: .861px; }
.pw-of-body {
  margin: 10px 0 20px; text-align: center; color: var(--ink);
  font-family: Inter, system-ui, sans-serif; font-weight: 500; font-size: 17px; line-height: 1.35;
}
.pw-of-body > b { font-weight: 800; }
.pw-of-body > s { color: var(--muted); }
.pw-of-card > .pw-error { margin-top: -10px; }
.pw-of-fine { margin: 6px 0 12px; text-align: center; color: var(--muted); font-size: 14px; line-height: 20px; }
/* _DiscountBadge: 88 px success disc, ring 2.5, shadow (2, 3), tilted .18 rad, breathing .92 → 1.1. */
.pw-of-badge {
  position: absolute; top: 12px; right: 12px; width: 88px; height: 88px;
  animation: pw-badge-pulse 850ms cubic-bezier(0.42, 0, 0.58, 1) infinite alternate;
}
@keyframes pw-badge-pulse { from { transform: scale(.92); } to { transform: scale(1.1); } }
.pw-of-disc {
  width: 100%; height: 100%; border-radius: 50%; transform: rotate(.18rad); --b-fill: var(--success); --b-dx: 2px; --b-dy: 3px;
  display: flex; flex-direction: column; align-items: center; justify-content: center; color: #FFFFFF;
  font-family: Inter, system-ui, sans-serif; font-weight: 800;
}
.pw-of-pct { font-size: 26px; line-height: 1; }
.pw-of-off { font-size: 16px; line-height: 1.1; letter-spacing: 1.5px; }
@media (prefers-reduced-motion: reduce) { .pw-of-gift, .pw-of-badge { animation: none; } }

/* ---------------------------------------------------------- sign-in sheet */
/* The phone step's form on a cream card, for a visitor who skipped it (see 60-paywall.js). */
.pw-sheet-card {
  position: relative; flex: none; width: 100%; max-width: 420px; height: min(620px, 100%); margin: auto 0;
  border-radius: 28px; background: var(--cream); box-shadow: inset 0 0 0 2.5px var(--ink);
  display: flex; flex-direction: column; overflow: hidden;
}
.pw-sheet-card > * { flex: 1 1 0; min-height: 0; }
.pw-sheet-card .sgn { padding-top: 20px; }

/* ------------------------------------------------------------ done screen */
.dn-root { position: relative; isolation: isolate; display: flex; flex-direction: column; background: var(--cream); }
.dn-root > * { flex: 1 1 0; min-height: 0; }
/* The view being replaced, fading out beneath the new one. */
.dn-root > .dn-out { position: absolute; top: 0; right: 0; bottom: 0; left: 0; z-index: -1; pointer-events: none; }
.dn { display: flex; flex-direction: column; align-items: center; padding: 24px; }
.dn-sp1 { flex: 1 1 0; }
.dn-sp2 { flex: 2 1 0; }
.dn > .pb { flex: none; width: fit-content; max-width: 100%; }
.dn-gap { flex: none; height: 22px; }
.dn-hero { flex: 1 1 0; min-height: 0; width: 100%; display: flex; justify-content: center; align-items: flex-start; }
.dn-hero-box { flex: none; position: relative; }
.dn-hero-box > .avatar { position: absolute; left: 0; top: 0; transform-origin: 0 0; }
.dn-pay { flex: none; width: 100%; display: flex; flex-direction: column; }
/* The done screen's own price line (Lora 500 faux bold 14px, muted). */
.dn-pay > .dn-price {
  margin-top: 10px; color: var(--muted); font-family: inherit; font-weight: inherit;
  font-size: 14px; line-height: 20px; letter-spacing: .25px; -webkit-text-stroke: .56px currentColor;
}
/* Text Pip (Pip never texts first): the paid view's bottom block under the iMessage screenshot
   (Pip.imessageShot, up to its natural height): the Text Pip button until this browser has tapped the link,
   and Pip's number for good. The number also sits above the offer view's plan. */
.dn > .shot-slot { margin-bottom: 24px; }
.dn-textpip { flex: none; width: 100%; display: flex; flex-direction: column; }
.dn-textpip > .pbtn + .tp-number { margin-top: 14px; }
.dn-pay > .tp-number { margin-bottom: 14px; }
.dn-signin { display: flex; flex-direction: column; }
.dn-signin > * { flex: 1 1 0; min-height: 0; }

