:root {
    --bg: #1a1f2e;
    --card-front: #4d7eb8;
    --card-back: #2d8659;
    --text: #f5f5f5;
    --muted: rgba(255,255,255,0.7);
    --accent: #ffd166;
    --btn-bg: rgba(255,255,255,0.12);
    --btn-bg-hover: rgba(255,255,255,0.22);
  }
  * { box-sizing: border-box; }
  html, body {
    margin: 0; padding: 0;
    background: linear-gradient(135deg, #1a1f2e 0%, #2c3e50 100%);
    color: var(--text);
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    min-height: 100vh;
  }
  /* Only `body` is the flex container — applying display:flex to `html` AS WELL
     would make body the sole flex item of html, and the `align-items: center`
     would then shrink body to its intrinsic content width on phones (compact
     mode drops the min-width floor below), leaving huge empty margins on both
     sides. Mirrors keyboard.css / settings.css / memory.css / links.css. */
  body {
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 24px 16px;
  }
  /* Web only, non-phone: never collapse below settings-page width (560px content + 32px padding).
     Phones (≤480px) drop the min-width so the page fills the viewport instead of horizontally scrolling. */
  @media (min-width: 481px) {
    html:not(.compact) body { min-width: 592px; }
  }
  header {
    text-align: center;
    margin-bottom: 20px;
    position: relative;
    width: 100%;
    max-width: 560px;
  }
  h1 {
    font-size: 22px;
    font-weight: 600;
    margin: 40px 0 4px;
  }
  /* The title line owns the vertical margins (h1 reset to 0) so the absolutely
     positioned ? button centers exactly on the title. */
  .title-row {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    margin: 40px 0 4px;
  }
  .title-row h1 { margin: 0; }
  .compact .title-row { margin-top: 0; }
  /* Help (?) button — same circle size as the .header-icon toolbar buttons. */
  .help-tour-btn {
    /* Vertically center via top/bottom:0 + margin:auto rather than a translateY
       transform — the generic `button:active { transform: translateY(1px) }` rule
       (higher specificity) would otherwise override the centering transform on
       press, jerking the button down so mouseup/click land on the parent row. */
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    margin: auto 0;
    width: 42px;
    height: 42px;
    border-radius: 50%;
    background: rgba(255,255,255,0.08);
    border: 1px solid rgba(255,255,255,0.25);
    color: var(--text);
    /* Longhands, not the `font` shorthand: a CSS-wide keyword like `inherit` is
       not a valid font-family token in the shorthand, which invalidates the whole
       declaration and drops the size/weight. */
    font-weight: 600;
    font-size: 20px;
    line-height: 1;
    font-family: inherit;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    cursor: pointer;
    transition: background 0.15s ease, border-color 0.15s ease;
  }
  .help-tour-btn:hover {
    background: rgba(255,255,255,0.18);
    border-color: rgba(255,255,255,0.4);
  }
  @media (max-width: 480px) {
    .help-tour-btn { width: 38px; height: 38px; font-size: 18px; }
  }
  /* Wraps the title row + lesson (.subtitle) line; the relative context the help
     (?) and search buttons center within, so they land midway between the two lines. */
  /* Reserve the two button gutters. The circles are absolutely positioned against this
     box, and an abs-positioned child anchors to the PADDING box, so padding insets the
     centered title and lesson label without moving the buttons. Without it a long lesson
     name (French's "Lesson 5: Eating and shopping") runs under both circles at 320px. */
  .title-block { position: relative; padding: 0 46px; }
  /* Search button — mirrors .help-tour-btn on the opposite side of the title.
     Same absolute top/bottom + margin:auto centering (no translateY transform,
     which `button:active` would override — see the .help-tour-btn note above);
     the circle styling and the royal-blue border come from .header-icon /
     #searchBtn in the header toolbar rules below. */
  .title-search-btn {
    position: absolute;
    right: 0;
    top: 0;
    bottom: 0;
    margin: auto 0;
    cursor: pointer;
  }
  .subtitle {
    color: var(--muted);
    font-size: 17px;
    margin-bottom: 10px;  /* owns the selector→count gap on web (see header/.progress web overrides below); compact overrides to 0 */
  }
  /* Web (non-compact) only: extra breathing room between the page title and the
     lesson selector below it. Compact (native shell / mobile-web phone) keeps the
     tighter default gap to conserve vertical space. */
  html:not(.compact) .subtitle { margin-top: 15px; }
  .progress {
    margin: 16px 0 8px;
    color: var(--muted);
    font-size: 16px;
    font-variant-numeric: tabular-nums;
    /* Span the same box as .progress-bar below (width/max-width match) and
       right-align so the count ends on the slider's right edge. */
    width: 100%;
    max-width: 560px;
    text-align: right;
  }
  /* Web only: the lesson-selector → card-count gap is .subtitle margin-bottom (10)
     + header margin-bottom + .progress margin-top. Zero the latter two on web so
     the .subtitle value alone defines a clean 10px gap. Compact keeps its own
     spacing (header margin-bottom 10 via the .compact override + .compact .progress margin-top 10). */
  html:not(.compact) header { margin-bottom: 0; }
  html:not(.compact) .progress { margin-top: 0; }
  .progress-bar {
    width: 100%;
    max-width: 560px;
    height: 8px;
    background: rgba(255,255,255,0.1);
    border-radius: 4px;
    overflow: hidden;
    margin-bottom: 20px;
    cursor: pointer;
    touch-action: none;  /* let JS scrubber consume the gesture instead of scrolling */
  }
  .progress-bar:hover { background: rgba(255,255,255,0.16); }
  .progress-bar-fill {
    height: 100%;
    background: var(--accent);
    transition: width 0.25s ease;
    pointer-events: none;  /* clicks land on the parent for the scrubber */
  }
  /* Set by setCommittingSwipe() for the length of a swipe / archive commit. See the
     comment there: the card is mid-flight, so let taps fall through to the page rather
     than land on a control that has moved out from under the finger. */
  .card-container.committing { pointer-events: none; }
  .card-container {
    width: 100%;
    max-width: 560px;
    height: 313px;
    perspective: 1200px;
    margin-bottom: 24px;
  }
  .card {
    width: 100%;
    height: 100%;
    position: relative;
    transform-style: preserve-3d;
    transition: transform 0.55s cubic-bezier(0.4, 0.0, 0.2, 1);
    cursor: pointer;
    /* Kill the iOS/Safari blue tap-highlight flash on mobile when the card is tapped. */
    -webkit-tap-highlight-color: transparent;
  }
  /* In compact mode (Capacitor native shell OR mobile-web phone browser), also disable
     selection and the long-press callout — the card is a tap/swipe target on touch
     devices, not text to be selected, and the OS callout would interfere with the
     custom card-menu long-press trigger. Desktop web keeps normal text-selection so
     users can copy a phrase or translation to look it up. */
  .compact .card {
    -webkit-user-select: none;
    user-select: none;
    -webkit-touch-callout: none;
  }
  .card.flipped {
    transform: rotateX(180deg);
  }
  .card-face {
    position: absolute;
    inset: 0;
    backface-visibility: hidden;
    -webkit-backface-visibility: hidden;
    border-radius: 18px;
    padding: 32px 28px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    box-shadow: 0 18px 40px rgba(0,0,0,0.35);
    text-align: center;
  }
  .card-front { background: var(--card-front); }
  .card-back  { background: var(--card-back); transform: rotateX(180deg); }
  .category {
    /* Absolutely positioned + centered along the top of the card so the speaker
       and menu buttons in the corners (and the phrase below) stay perfectly
       independent of label length. */
    position: absolute;
    top: 18px;
    left: 50%;
    transform: translateX(-50%);
    font-size: 13px;
    letter-spacing: 1.5px;
    text-transform: uppercase;
    color: rgba(255,255,255,0.7);
    font-weight: 600;
    max-width: calc(100% - 120px);  /* don't bleed under the corner buttons */
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    pointer-events: none;            /* a long category label should never block taps */
  }
  /* Shared base for the round corner buttons (speaker / card menu).
     Sized at 48×48 (70% of the earlier 68×68 thumb-friendly size). */
  .speaker-btn,
  .card-menu-btn,
  .youtube-btn {
    position: absolute;
    top: 10px;
    background: rgba(255,255,255,0.15);
    border: none;
    color: var(--text);
    width: 48px;
    height: 48px;
    border-radius: 50%;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    transition: background 0.15s ease, transform 0.1s ease;
  }
  .speaker-btn { right: 14px; }
  .card-menu-btn { left: 14px; }
  /* YouTube link button (optional per card) shares the speaker's top-right slot.
     Rendered as the YouTube play mark (red glyph + white triangle, in the inline
     SVG) on a dark circular chip so it reads as a "watch video" action. Dimmed to
     70% opacity so it doesn't read as harsh against the bright card background. */
  .youtube-btn { right: 14px; background: #212121; opacity: 0.7; }
  .youtube-btn:hover { background: #383838; }
  .youtube-btn:active { transform: scale(0.92); }
  .speaker-btn:hover,
  .card-menu-btn:hover { background: rgba(255,255,255,0.28); }
  .speaker-btn:active,
  .card-menu-btn:active { transform: scale(0.92); }
  .speaker-btn.playing { background: var(--accent); color: #1a1f2e; }
  .speaker-btn svg,
  .card-menu-btn svg,
  .youtube-btn svg { width: 26px; height: 26px; }
  /* The speaker button shows one of three icons depending on playback mode + submode:
       - default (manual / auto-speak): speaker (volume_up)
       - hands-free, playing:           pause bars (button pauses the loop)
       - hands-free, paused:            play triangle (button resumes the loop)
     applyHandsFreeUIState() in app.js toggles the body classes that gate these rules. */
  .speaker-btn .speaker-icon-pause,
  .speaker-btn .speaker-icon-play { display: none; }
  body.playback-mode-handsfree .speaker-btn .speaker-icon-default { display: none; }
  body.playback-mode-handsfree .speaker-btn .speaker-icon-pause { display: block; }
  body.playback-mode-handsfree.handsfree-paused .speaker-btn .speaker-icon-pause { display: none; }
  body.playback-mode-handsfree.handsfree-paused .speaker-btn .speaker-icon-play { display: block; }
  .phrase {
    font-size: clamp(28px, 5vw, 42px);
    font-weight: 600;
    line-height: 1.25;
    margin: 0;
  }
  .example {
    margin-top: 28px;
    max-width: 92%;
    text-align: center;
  }
  .example-sv {
    font-size: clamp(15px, 2.4vw, 18px);
    font-weight: 500;
    color: rgba(255,255,255,0.95);
    line-height: 1.35;
  }
  .example-en {
    margin-top: 4px;
    font-size: 15px;
    color: rgba(255,255,255,0.7);
    font-style: italic;
    line-height: 1.4;
  }
  .literal {
    margin-top: 12px;
    font-size: 17px;
    color: rgba(255,255,255,0.75);
    line-height: 1.6;
    max-width: 92%;
    text-align: center;
  }
  .literal .lit-sv {
    font-weight: 600;
    color: rgba(255,255,255,0.95);
  }
  .literal .lit-en {
    color: rgba(255,255,255,0.65);
    font-style: italic;
  }
  .literal .lit-sep {
    color: rgba(255,255,255,0.4);
    margin: 0 6px;
  }
  .notes {
    margin-top: 12px;
    font-size: 16px;
    color: rgba(255,255,255,0.6);
    font-style: italic;
    max-width: 90%;
  }
  .controls {
    display: flex;
    gap: 10px;
    flex-wrap: wrap;
    justify-content: center;
    max-width: 560px;
    width: 100%;
  }
  button {
    background: var(--btn-bg);
    color: var(--text);
    border: none;
    padding: 12px 18px;
    border-radius: 10px;
    font-size: 16px;
    font-weight: 500;
    cursor: pointer;
    transition: background 0.15s ease, transform 0.05s ease;
    font-family: inherit;
  }
  button:hover { background: var(--btn-bg-hover); }
  button:active { transform: translateY(1px); }
  button.primary {
    background: var(--accent);
    color: #1a1f2e;
    font-weight: 600;
  }
  button.primary:hover { background: #ffdd85; }
  .nav-row { display: flex; gap: 10px; flex: 1; min-width: 240px; }
  .nav-row button {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    padding: 10px 14px;
  }
  .btn-arrow {
    width: 20px;
    height: 20px;
    display: inline-block;
  }
  .btn-arrow.updown {
    display: inline-flex;
    width: auto;          /* let the two child SVGs determine width (~36px), not the 20px from .btn-arrow */
    gap: 0;
  }
  .btn-arrow.updown svg {
    width: 18px;
    height: 18px;
  }
  button.small {
    padding: 5px 12px;
    font-size: 14px;
    font-weight: 500;
    opacity: 0.75;
    display: inline-flex;
    align-items: center;
    gap: 6px;
  }
  button.small:hover { opacity: 1; }
  button.small .btn-icon { width: 14px; height: 14px; flex-shrink: 0; }
  .utility-buttons {
    margin-top: 12px;
    padding-top: 12px;
    border-top: 1px solid rgba(255, 255, 255, 0.1);
    /* Match the row above (selector-row / controls): full body-width up to 560px,
       so the side columns have room to push apart. */
    width: 100%;
    max-width: 560px;
    /* 3-column grid, same reasoning as .header-icons: the 1fr side columns put the
       Memory Squares circle on the page's centerline, which space-between would not
       (it centers the gap, so the circle drifts toward whichever side's text is
       narrower). The middle column is `auto`, so it collapses to nothing while the
       circle is hidden and the side buttons stay at the edges. */
    display: grid;
    /* minmax(0, 1fr), not a bare 1fr: `1fr` means minmax(auto, 1fr), so a side column
       still grows to its content's min-content width, and the wider side would push
       the middle column (and the circle with it) off the page's centerline. The 0
       minimum keeps the two side columns exactly equal. */
    grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
    align-items: center;
    gap: 8px;
  }
  /* Explicit column placement, not source order: #memoryGameLink is hidden whenever
     the language has too few pair-eligible cards, and a hidden grid item is not
     placed at all — auto-placement would then slide Shuffle into the middle column. */
  .utility-buttons > .utility-start { grid-column: 1; justify-self: start; }
  .utility-buttons > #memoryGameLink { grid-column: 2; }
  .utility-buttons > .utility-end   { grid-column: 3; justify-self: end; }
  /* Keyboard-shortcuts and tutor (ChatGPT) links: shown only on web (NOT the
     Capacitor native shell) AND when the window is wider than a phone screen.
     Hidden in the native shell at any width, and hidden at phone widths (≤480px)
     regardless of UA — so a desktop browser narrowed to phone size hides them too.
     The phone-width rule qualifies with .header-icon to outweigh the later
     `.header-icon { display: flex }` declaration (same specificity, source order). */
  .native .desktop-only { display: none; }
  @media (max-width: 480px) {
    .header-icon.desktop-only { display: none; }
  }
  /* Page-level scroll IS allowed in compact mode (so controls below the fold remain
     reachable if content overflows). The .card-container has `touch-action: none`
     (see compact overrides at end of file) so dragging on the card itself never
     bubbles up as a scroll — vertical drags on the card stay reserved for the flip
     gesture. */
  html.compact,
  html.compact body {
    min-height: 100vh;
    min-height: 100dvh;
    overscroll-behavior: none;
  }
  /* The card-category and speech-mode dropdowns, side by side and centered as a
     pair with clear space between them. */
  .selector-row {
    /* Symmetric spacing: match the gap below the dropdowns, which is this rule's
       margin-bottom (22px) + .utility-buttons margin-top (12px) = 34px (body is
       a flex column, so these sibling margins don't collapse). */
    margin-top: 34px;
    margin-bottom: 22px;
    display: flex;
    justify-content: center;
    /* Bottom-align so the two selects line up even when one label wraps to a
       second line on a narrow screen. */
    align-items: flex-end;
    gap: 28px;
    font-size: 15px;
    color: var(--muted);
    /* Same body-width column as .controls and .utility-buttons. Without the cap the row
       takes its max-content width (628px with these labels), which is wider than the
       560px column at every width and spills off the left edge of the viewport below
       ~660px: at 481px the row starts at -18px and the page scrolls 129px sideways. */
    width: 100%;
    max-width: 560px;
  }
  /* One labeled dropdown: caption above, <select> below. The caption is left-aligned
     with its dropdown's left edge rather than centered over it. */
  .selector-field {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 6px;
    min-width: 0;   /* let the select shrink inside the row instead of overflowing */
    /* flex-basis 0, not auto: with `auto` the columns are sized in proportion to their
       max-content width, and the speech-mode option strings are so much longer than any
       category name that the category column collapsed to ~32% of the row (95px at a
       320px viewport), ellipsizing most category labels. An equal split gives each
       column ~50%, which is enough for both. */
    flex: 1 1 0;
  }
  .selector-field label {
    font-size: 13px;
    color: var(--muted);
    padding-left: 2px;   /* optical alignment with the select's rounded left edge */
    /* Caption only — clicks must not reach it. A <label for> click focuses its
       <select> WITHOUT opening the picker, and the global keydown handler
       early-returns on SELECT, so that half-open state would silently kill every
       keyboard shortcut (Space / F / arrows / "/"). The <label for> association
       is kept for screen readers; Tab still focuses the select normally. */
    pointer-events: none;
  }
  .selector-row select {
    /* Fill the field's share of the capped row rather than sizing to the longest option,
       which is what pushed the row past its column. */
    width: 100%;
    background: rgba(255,255,255,0.1);
    color: var(--text);
    border: 1px solid rgba(255,255,255,0.15);
    border-radius: 8px;
    padding: 8px 30px 8px 12px;
    font-size: 16px;
    font-family: inherit;
    cursor: pointer;
    appearance: none;
    -webkit-appearance: none;
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23ffffff' opacity='0.7'><path d='M7 10l5 5 5-5z'/></svg>");
    background-repeat: no-repeat;
    background-position: right 6px center;
    background-size: 18px;
    /* The two dropdowns split the row on a phone, where the longest option text
       ("Hands-free: auto-speak card then auto-advance") no longer fits — truncate it
       with an ellipsis rather than clipping mid-character. Chromium ellipsizes from
       text-overflow alone, but WebKit (Safari + the iOS shell) needs the overflow /
       white-space pair as well or it clips mid-character with no ellipsis. */
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    transition: background-color 0.15s ease, border-color 0.15s ease;
  }
  .selector-row select:hover {
    background-color: rgba(255,255,255,0.18);
    border-color: rgba(255,255,255,0.25);
  }
  .selector-row select:focus {
    outline: none;
    border-color: var(--accent);
  }
  .selector-row select option {
    background: #1a1f2e;
    color: var(--text);
  }
  /* Star toggle (bottom-right) and Trash toggle (bottom-left) — always visible,
     tappable buttons that flip the card's starred / archived state. Each button
     holds two SVGs (outline + filled); the active one is revealed via CSS based
     on the card's .starred / .archived class. The buttons sit on both faces so
     the user can toggle either state regardless of which side is up. */
  .card-star-toggle,
  .card-trash-toggle {
    position: absolute;
    bottom: 14px;
    width: 40px;
    height: 40px;
    padding: 0;
    background: transparent;
    border: none;
    cursor: pointer;
    display: block;
    filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.35));
    /* Strip every "this is a button" indicator the browser/WebView paints on
       press: WebKit tap-highlight rectangle (touch), focus outline (rectangle
       around a focused button), and the UA appearance / box-shadow that
       Android Chrome's WebView applies to <button> by default. Without
       appearance:none + -webkit-appearance:none, Android still painted a
       rectangular background around the icon when the button was active. */
    -webkit-tap-highlight-color: transparent;
    -webkit-appearance: none;
    appearance: none;
    outline: none;
    box-shadow: none;
  }
  .card-star-toggle:focus,
  .card-trash-toggle:focus,
  .card-star-toggle:focus-visible,
  .card-trash-toggle:focus-visible,
  .card-star-toggle:active,
  .card-trash-toggle:active {
    outline: none;
    background: transparent;
    box-shadow: none;
  }
  .card-star-toggle  { right: 14px; }
  .card-trash-toggle { left: 14px; }
  .card-star-toggle svg,
  .card-trash-toggle svg { width: 100%; height: 100%; }
  /* Default: show outline, hide filled. Outline reads as muted (low-opacity white)
     so an unstarred / unarchived card doesn't look like it's in a special state. */
  .card-star-toggle  .star-icon-filled,
  .card-trash-toggle .trash-icon-filled { display: none; }
  .card-star-toggle  .star-icon-outline  { display: block; color: rgba(255, 255, 255, 0.45); }
  .card-trash-toggle .trash-icon-outline { display: block; color: rgba(255, 255, 255, 0.45); }
  /* When the card carries the active state class, swap to the filled glyph in its
     accent color (gold for starred, soft red for archived). */
  .card.starred  .card-star-toggle .star-icon-outline   { display: none; }
  .card.starred  .card-star-toggle .star-icon-filled    { display: block; color: #f5c518; }
  .card.archived .card-trash-toggle .trash-icon-outline { display: none; }
  .card.archived .card-trash-toggle .trash-icon-filled  { display: block; color: #ef9a9a; }
  /* Empty-state card (no cards in the active category — e.g. all archived). The
     star / trash / hamburger handlers all read `order[idx]`, which is undefined
     in this state, so they short-circuit — but the buttons are still rendered
     and clickable. Hide them so the empty-state surface reads as fully inert.
     The speaker buttons are already hidden in JS by renderEmptyState(). */
  .card.empty .card-star-toggle,
  .card.empty .card-trash-toggle,
  .card.empty .card-menu-btn { display: none; }

  /* Archived cards: diagonal hatched overlay on top of the existing face background.
     Applied to .card.archived (both faces), so the indicator shows whichever side is up. */
  .card.archived .card-face::before {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
    pointer-events: none;
    background-image: repeating-linear-gradient(
      45deg,
      rgba(255,255,255,0.10) 0,
      rgba(255,255,255,0.10) 2px,
      transparent 2px,
      transparent 12px
    );
  }
  /* Empty-state card (no cards in the active category — e.g. all archived). Hide
     the back face since it's not meaningful, and dim the surface so it reads as inert. */
  .card.empty { cursor: default; }
  .card.empty .card-back { display: none; }
  .card.empty .phrase {
    font-size: clamp(16px, 2.6vw, 19px);
    font-weight: 500;
    color: rgba(255,255,255,0.75);
    line-height: 1.4;
    max-width: 90%;
  }

  /* Long-press / right-click action menu. Full-viewport backdrop dims the page;
     the menu itself is centered with the card title at the top and stacked buttons. */
  .card-menu-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,0.55);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 1000;
    padding: 24px;
  }
  .card-menu-backdrop[hidden] { display: none; }
  .card-menu {
    position: relative;
    background: #2c3e50;
    color: var(--text);
    border-radius: 14px;
    padding: 16px;
    max-width: 360px;
    width: 100%;
    box-shadow: 0 20px 60px rgba(0,0,0,0.5);
    display: flex;
    flex-direction: column;
    gap: 8px;
  }
  .card-menu-title {
    font-size: 18px;
    font-weight: 600;
    text-align: center;
    /* extra side padding keeps a long centered title clear of the corner close button */
    padding: 6px 40px 12px;
    border-bottom: 1px solid rgba(255,255,255,0.12);
    margin-bottom: 4px;
    word-break: break-word;
  }
  /* Corner close button shared by the card-action and lesson menus. Anchored to the
     top-right of the (position: relative) panel as an alternative to the backdrop/Esc. */
  .menu-close-btn {
    position: absolute;
    top: 10px;
    right: 10px;
    width: 30px;
    height: 30px;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    appearance: none;
    background: rgba(255,255,255,0.08);
    border: 1px solid rgba(255,255,255,0.14);
    border-radius: 50%;
    color: var(--text);
    cursor: pointer;
    transition: background 0.12s ease, border-color 0.12s ease, transform 0.12s ease;
  }
  .menu-close-btn:hover { background: rgba(255,255,255,0.18); border-color: rgba(255,255,255,0.28); }
  .menu-close-btn:active { transform: scale(0.92); }
  .menu-close-btn svg { width: 16px; height: 16px; }
  .card-menu-item {
    appearance: none;
    background: rgba(255,255,255,0.06);
    border: 1px solid rgba(255,255,255,0.14);
    color: var(--text);
    border-radius: 10px;
    padding: 12px 16px;
    font-size: 15px;
    font-family: inherit;
    text-align: center;
    cursor: pointer;
    transition: background 0.12s ease, border-color 0.12s ease;
  }
  .card-menu-item:hover { background: rgba(255,255,255,0.12); border-color: rgba(255,255,255,0.24); }
  .card-menu-item:active { transform: translateY(1px); }
  /* Menu items with an inline icon switch to flex layout so the icon sits to the
     left of the label. Plain-text items (no .card-menu-icon child) keep the original
     centered text-only layout. */
  .card-menu-item:has(.card-menu-icon) {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
  }
  .card-menu-item .card-menu-icon { width: 18px; height: 18px; flex-shrink: 0; }

  /* ---------- "Install the App" prompt ---------- */
  /* Same dimmed-backdrop recipe as .search-backdrop, but the panel is centered and
     sized to its own content: it holds one line of text and two buttons. Purely
     JS-gated (app.js only ever unhides it in a mobile-web phone browser), so there
     is no platform selector here. */
  .install-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,0.55);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 1000;
    padding: 24px;
  }
  .install-backdrop[hidden] { display: none; }
  .install-panel {
    background: #2c3e50;
    color: var(--text);
    border-radius: 14px;
    padding: 22px 24px;
    max-width: 360px;
    width: 100%;
    box-shadow: 0 20px 60px rgba(0,0,0,0.5);
    text-align: center;
  }
  .install-title {
    margin: 0 0 18px;
    font-size: 20px;
    font-weight: 600;
  }
  .install-actions {
    display: flex;
    justify-content: center;
    gap: 12px;
  }
  /* Override button.small's 0.75 resting opacity: these two are the only actions in
     the dialog, so neither should read as de-emphasized. */
  .install-actions > button.small {
    opacity: 1;
    padding: 9px 18px;
    font-size: 15px;
  }
  /* "Yes!" is focused on open (it is the default action), so give both buttons a
     ring that reads against the accent fill instead of the browser's default blue. */
  .install-actions > button.small:focus-visible {
    outline: 2px solid var(--text);
    outline-offset: 2px;
  }
  /* "Yes!" is the primary action (filled accent against the panel); "Not now" stays
     an outlined secondary.
     Written as `button.install-accept` (0,1,1) rather than a bare `.install-accept`
     (0,1,0): `button:hover` above is (0,1,1), so the bare class loses the background
     on hover and leaves the dark #1a1f2e label on a barely-lightened panel. Phones
     latch :hover after a tap, so a tap that doesn't commit would stick there. Same
     pattern `button.primary` / `button.primary:hover` already uses. */
  button.install-accept {
    background: var(--accent);
    border: 1px solid var(--accent);
    color: #1a1f2e;
    font-weight: 600;
  }
  button.install-accept:hover { background: #ffdd85; }
  button.install-dismiss {
    background: transparent;
    border: 1px solid rgba(255,255,255,0.28);
    color: var(--text);
  }
  button.install-dismiss:hover { background: rgba(255,255,255,0.12); }

  /* ---------- cross-lesson search overlay ---------- */
  /* Same dimmed-backdrop recipe as .card-menu-backdrop, but the panel is anchored
     toward the top so the autocomplete list grows downward into the dimmed area
     instead of pushing the input off-center. */
  .search-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,0.55);
    display: flex;
    align-items: flex-start;
    justify-content: center;
    z-index: 1000;
    padding: 64px 24px 24px;
  }
  .search-backdrop[hidden] { display: none; }
  .search-panel {
    background: #2c3e50;
    color: var(--text);
    border-radius: 14px;
    padding: 14px;
    max-width: 460px;
    width: 100%;
    max-height: calc(100vh - 96px);
    box-shadow: 0 20px 60px rgba(0,0,0,0.5);
    display: flex;
    flex-direction: column;
    gap: 10px;
  }
  .search-input {
    appearance: none;
    width: 100%;
    box-sizing: border-box;
    background: rgba(255,255,255,0.06);
    border: 1px solid rgba(255,255,255,0.18);
    color: var(--text);
    border-radius: 10px;
    padding: 12px 14px;
    font-size: 16px;   /* >=16px avoids iOS Safari zoom-on-focus */
    font-family: inherit;
  }
  .search-input::placeholder { color: rgba(255,255,255,0.45); }
  .search-input:focus {
    outline: none;
    border-color: var(--accent);
  }
  .search-results {
    list-style: none;
    padding: 0;
    margin: 0;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    min-height: 0;   /* let the list shrink + scroll inside the capped flex panel */
  }
  .search-results:empty { display: none; }
  .search-result {
    display: flex;
    align-items: baseline;
    gap: 8px;
    padding: 10px 12px;
    border-radius: 8px;
    cursor: pointer;
    border: 1px solid transparent;
  }
  .search-result:hover,
  .search-result.active {
    background: rgba(255,255,255,0.12);
    border-color: rgba(255,255,255,0.22);
  }
  .search-result-source { font-weight: 600; }
  .search-result-english { color: var(--muted, rgba(255,255,255,0.7)); font-size: 14px; }
  .search-result-lesson {
    margin-left: auto;
    padding-left: 8px;
    color: rgba(255,255,255,0.45);
    font-size: 12px;
    white-space: nowrap;
    flex-shrink: 0;
  }
  /* Non-selectable informational rows: the "no matches" message and the
     truncation note. Muted, centered, no hover affordance. */
  .search-note {
    padding: 10px 12px;
    color: rgba(255,255,255,0.5);
    font-size: 13px;
    text-align: center;
  }

  /* ---------- lesson picker ---------- */
  /* The lesson line under the app title is a button that opens the lesson menu.
     It sits inside the .subtitle div (which keeps the original spacing/color);
     this just strips the native button chrome and adds a subtle hover + caret. */
  .subtitle-lesson-btn {
    appearance: none;
    -webkit-appearance: none;
    background: none;
    border: none;
    font-family: inherit;
    font-size: inherit;
    color: inherit;          /* muted, from .subtitle */
    line-height: inherit;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 3px 8px;
    border-radius: 8px;
    transition: background 0.12s ease, color 0.12s ease;
  }
  /* Let the label shrink inside the gutters and ellipsize rather than overflow: a flex
     item defaults to min-width:auto, which refuses to shrink below its text. */
  .subtitle-lesson-btn { min-width: 0; max-width: 100%; }
  #appSubtitleText { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .subtitle-lesson-btn:hover { background: rgba(255,255,255,0.08); color: var(--text); }
  .subtitle-lesson-btn:active { transform: translateY(1px); }
  .subtitle-lesson-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
  .subtitle-caret { width: 12px; height: 12px; flex-shrink: 0; opacity: 0.75; }
  /* Single-lesson languages: nothing to pick → plain label, not a trigger. */
  .subtitle-lesson-btn.single { cursor: default; }
  .subtitle-lesson-btn.single:hover { background: none; color: inherit; }
  .subtitle-lesson-btn.single .subtitle-caret { display: none; }

  /* Same dimmed-backdrop + top-anchored panel recipe as the search overlay. */
  .lesson-menu-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,0.55);
    display: flex;
    align-items: flex-start;
    justify-content: center;
    z-index: 1000;
    padding: 64px 24px 24px;
  }
  .lesson-menu-backdrop[hidden] { display: none; }
  .lesson-menu {
    position: relative;
    background: #2c3e50;
    color: var(--text);
    border-radius: 14px;
    padding: 16px;
    max-width: 420px;
    width: 100%;
    max-height: calc(100vh - 96px);
    box-shadow: 0 20px 60px rgba(0,0,0,0.5);
    display: flex;
    flex-direction: column;
    gap: 10px;
  }
  .lesson-menu-title {
    font-size: 18px;
    font-weight: 600;
    text-align: center;
    /* extra side padding keeps the title clear of the corner close button */
    padding: 6px 40px 12px;
    border-bottom: 1px solid rgba(255,255,255,0.12);
  }
  .lesson-menu-list {
    display: flex;
    flex-direction: column;
    gap: 8px;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    min-height: 0;   /* let the list shrink + scroll inside the capped flex panel */
  }
  .lesson-menu-item {
    appearance: none;
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    text-align: left;
    background: rgba(255,255,255,0.06);
    border: 1px solid rgba(255,255,255,0.14);
    color: var(--text);
    border-radius: 10px;
    padding: 12px 14px;
    font-family: inherit;
    cursor: pointer;
    transition: background 0.12s ease, border-color 0.12s ease, box-shadow 0.12s ease;
  }
  .lesson-menu-item:hover { background: rgba(255,255,255,0.12); border-color: rgba(255,255,255,0.24); }
  .lesson-menu-item:active { transform: translateY(1px); }
  /* Selected lesson + keyboard focus: replace the browser's default blue focus ring
     (the current item is auto-focused when the menu opens) with an on-brand accent
     border + soft yellow glow. The rgba is --accent (#ffd166) at low alpha. */
  .lesson-menu-item.current {
    border-color: var(--accent);
    box-shadow: 0 0 12px 1px rgba(255, 209, 102, 0.35);
  }
  .lesson-menu-item:focus { outline: none; }
  .lesson-menu-item:focus-visible {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 12px 1px rgba(255, 209, 102, 0.35);
  }
  .lesson-menu-item-label {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
    flex: 1;
  }
  .lesson-menu-item-title { font-size: 15px; font-weight: 600; }
  .lesson-menu-item-helper { font-size: 13px; color: var(--muted); }
  .lesson-menu-check {
    flex-shrink: 0;
    color: var(--accent);
    font-size: 18px;
    font-weight: 700;
    line-height: 1;
  }

  /* When the front face shows a bullet list (a topic header card, or a hybrid
     card with a sentence + supplementary bullets), shrink the phrase from the
     standard big-vocab-headword size to something that leaves room for the
     bullets below and reads as a heading rather than a single-word answer. */
  .card-front:has(.ref-bullets:not(:empty)) .phrase {
    font-size: clamp(18px, 3.2vw, 22px);
    font-weight: 600;
    margin: 4px 0 12px;
    line-height: 1.3;
  }
  .ref-bullets {
    list-style: none;
    padding: 0;
    margin: 0;
    font-size: 15px;
    line-height: 1.55;
    text-align: left;
    color: rgba(255,255,255,0.92);
    max-width: 92%;
    align-self: center;
  }
  .ref-bullets:empty { display: none; }
  .ref-bullets li {
    padding: 3px 0 3px 16px;
    position: relative;
  }
  .ref-bullets li::before {
    content: "•";
    position: absolute;
    left: 2px;
    color: var(--accent);
    opacity: 0.85;
  }
  .header-icons {
    /* 3-column grid: link icon (left) | centered middle group | settings gear (right).
       The 1fr outer columns make the middle group truly centered on the page rather
       than off-center toward the right edge the way `justify-content: space-between`
       (the previous flex layout) would leave it when link and settings have different
       visual weight. */
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    margin-bottom: 17px;
  }
  .header-icons > .header-icon-left { justify-self: start; }
  .header-icons > .settings-gear    { justify-self: end; }
  .header-icons-center {
    display: flex;
    align-items: center;
    gap: 18px;
    justify-self: center;
  }
  .header-icon {
    width: 42px;
    height: 42px;
    border-radius: 50%;
    background: rgba(255,255,255,0.08);
    border: 1px solid rgba(255,255,255,0.12);
    color: var(--text);
    display: flex;
    align-items: center;
    justify-content: center;
    text-decoration: none;
    /* Reset <button> user-agent padding so the SVG icon fills the circle the same way
       an <a class="header-icon"> would (#recallModeToggle and #searchBtn are the
       <button> cases; every other .header-icon is an <a>). */
    padding: 0;
    transition: background 0.15s ease, transform 0.4s ease, border-color 0.15s ease;
  }
  /* Author-origin `display: flex` above otherwise wins over the UA `[hidden]`
     rule (same specificity, author beats UA), so el.hidden=true wouldn't hide
     these. */
  .header-icon[hidden] { display: none; }
  .header-icon:hover {
    background: rgba(255,255,255,0.18);
    border-color: rgba(255,255,255,0.25);
  }
  .header-icon svg { width: 20px; height: 20px; }
  /* Per-button accent borders. Placed after `.header-icon:hover` and specificity-
     matched (two-class / id selectors) so the color persists on hover instead of
     reverting to the gray hover border. */
  .header-icon.header-icon-left      { border-color: #ff6b9d; }            /* useful links — pink */
  .header-icon[href$="keyboard.html"] { border-color: #c8a2c8; }           /* keyboard shortcuts — lilac */
  .header-icon[href$="custom_cards.html"] { border-color: #5bc0de; }       /* add custom cards — light blue */
  .header-icon.recall-mode-toggle   { border-color: #ff8c42; }             /* SV/EN — orange */
  #searchBtn                         { border-color: #4169e1; }             /* search — royal blue */
  /* Memory Squares launcher — the one .header-icon that lives outside the header,
     centered in the bottom utility row (see .utility-buttons above). */
  #memoryGameLink                    { border-color: #3ddc84; }             /* memory game — green */
  .header-icon.settings-gear         { border-color: #e74c3c; }            /* settings — red */
  /* The recall-mode toggle is a text-labeled circle ("SV" / "EN") rather than an SVG icon. */
  .recall-mode-toggle {
    /* Longhands, not the `font` shorthand: `inherit` is invalid as the shorthand's
       <font-family> token and would drop the whole declaration (see app.css:75-81,
       and the .help-tour-btn fix in commit bad14bc). */
    font-weight: 600;
    font-size: 12px;
    line-height: 1;
    font-family: inherit;
    letter-spacing: 0.5px;
    cursor: pointer;
  }
  .settings-gear:hover { transform: rotate(60deg); }
  @media (max-width: 480px) {
    .card-container { height: 273px; }
    .controls { flex-direction: column; }
    .nav-row { width: 100%; }
    .header-icon { width: 38px; height: 38px; }
    /* Stretch the pair so its outer edges align with the Prev / Next buttons above. */
    /* Width, max-width, flex sizing and select width now apply at every width (see the
       base rules); only the tighter spacing is phone-specific. */
    .selector-row { gap: 12px; }
    /* Tighter than the desktop 8px/30px/8px/12px — every pixel of padding here is a
       character lost off the option text. Font-size stays 16px: below that, iOS
       zooms the page when the picker opens. */
    .selector-row select { padding: 8px 24px 8px 10px; }
  }
  /* Compact full-screen layout — applies to BOTH the Capacitor native shell AND
     the mobile-web phone browser (see app.js init()): minimal margins inside the
     device's safe-area insets, and the rows from the card down share any leftover
     height through auto margins (see the margin-top:auto rule below) so the utility
     row sits on the bottom edge instead of that space pooling underneath it. (On a
     desktop web browser env(safe-area-inset-*) is 0, but compact mode isn't applied
     there anyway.)
     The 18px bottom floor is the requested gap between the screen edge and the
     Shuffle / 1st Card buttons. On an iPhone the home-indicator inset is larger and
     wins, which is the point of the max(): the buttons must not sit under it. */
  html.compact body {
    padding: max(env(safe-area-inset-top), 11px)
             max(env(safe-area-inset-right), 12px)
             max(env(safe-area-inset-bottom), 18px)
             max(env(safe-area-inset-left), 12px);
  }
  /* 8px: this plus .compact .progress's 10px margin-top is the whole gap between the
     title block and the card count, so 18px in total. They are flex siblings, so the
     two margins add rather than collapsing. */
  .compact header { margin-bottom: 8px; }
  .compact h1 { margin-top: 0; }
  .compact .subtitle { margin-bottom: 0; }
  /* 18px, up from the original 13px: more air under the toolbar row than the 11px above
     it. Both are floors, so a notched iPhone's larger safe-area-inset-top still wins at
     the top of the screen. */
  .compact .header-icons { margin-bottom: 18px; }
  .compact .progress { margin: 10px 0 6px; }
  .compact .progress-bar { margin-bottom: 12px; }
  .compact .card-container {
    /* Adaptive height — larger on tall phones, but capped so the card never dominates
       the screen or pushes the controls (Prev/Flip/Next, Category, Shuffle)
       below the home indicator on iPhone. Previously 52vh / 460px, which overflowed
       on iPhone 15 Pro inside the safe-area insets. */
    height: clamp(243px, calc(46vh - 7px), 403px);
    flex: 0 0 auto;
    margin-bottom: 12px;
    /* The browser must not interpret touches on the card as a page scroll — the JS
       gesture handler (setupNativeTouchGestures, called for both native and
       mobile-web in init()) owns vertical drags here. Without this, enabling page
       scroll (above) would let a down-drag flip-AND-scroll, fighting the flip animation. */
    touch-action: none;
  }
  /* These gaps live on the preceding row's margin-bottom rather than on the following
     row's margin-top, because margin-top is `auto` below and would overwrite them.
     Both are 12px, matching .progress-bar's and .card-container's, so that every
     growable gap has the same floor and they stay equal to each other (the dropdowns
     row used to carry a 14px-wider one). */
  .compact .controls { margin-bottom: 12px; }
  .compact .selector-row { margin-bottom: 12px; }
  /* Internal, not a gap: the space between the separator line and the buttons. */
  .compact .utility-buttons { padding-top: 10px; }
  /* Pin the utility row to the bottom edge and spread leftover height into the gaps
     above it instead of letting it pool underneath. Flexbox splits free space equally
     among all auto margins on the main axis, so these four gaps (progress bar → card,
     card → Prev/Flip/Next, → the dropdowns, → the utility row) each grow by the same
     amount. .controls is display:none whenever the Prev/Flip/Next row is switched off
     (the default on mobile; see loadSettings() in app.js), and a display:none element
     is not a flex item at all, so its share is redistributed to the other three.
     Each gap keeps a 12px floor for when there is no free space to hand out (phone
     landscape, where the page scrolls instead), taken from the preceding row's
     margin-bottom: .progress-bar's, .card-container's, .controls' and .selector-row's.
     .controls needs its own even though it is usually hidden, because with the
     Prev/Flip/Next row switched on it, not .card-container, precedes the dropdowns. */
  .compact .card-container,
  .compact .controls,
  .compact .selector-row,
  .compact .utility-buttons { margin-top: auto; }

  /* ---------- tooltips (Semantic-UI-style popups) ----------
     Created and positioned by setupTooltips() in app.js: shown on hover where a real
     mouse pointer exists (desktop web), and on press-and-hold on touch devices (the
     native shell / mobile web), where the same popup also drives the guided ? tour. A
     light bubble with a small arrow, matching Semantic UI's default popup look. */
  .ui-tooltip {
    position: fixed;
    top: 0;
    left: 0;
    z-index: 1000;
    padding: 0.55em 0.8em;
    background: #fff;
    color: rgba(0,0,0,0.87);
    border: 1px solid #d4d4d5;
    border-radius: 4px;
    box-shadow: 0 2px 4px rgba(34,36,38,0.12), 0 2px 10px rgba(34,36,38,0.15);
    font-size: 13px;
    line-height: 1.3;
    /* Cap the width and let long help text wrap to several lines; short toolbar
       labels stay on one line since they're narrower than the cap. */
    max-width: 16rem;
    white-space: normal;
    text-align: left;
    pointer-events: none;       /* never intercept the mouse */
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.12s ease, visibility 0.12s ease;
  }
  .ui-tooltip.visible { opacity: 1; visibility: visible; }
  /* Guided walkthrough: the appended "click here" hint line (muted gray), and the
     interactive state that lets the popup itself receive the advance click. */
  .ui-tooltip-hint {
    display: block;
    margin-top: 5px;
    color: #888;
    font-size: 0.9em;
  }
  .ui-tooltip.interactive {
    pointer-events: auto;
    cursor: pointer;
  }
  /* Arrow: a small rotated square sharing the bubble's fill + border, with only
     its two outer edges bordered so it reads as a pointer. --arrow-x (set in JS)
     is the trigger-center offset from the bubble's left edge. */
  .ui-tooltip::after {
    content: "";
    position: absolute;
    left: var(--arrow-x, 50%);
    margin-left: -5px;
    width: 9px;
    height: 9px;
    background: #fff;
    transform: rotate(45deg);
  }
  .ui-tooltip[data-placement="bottom"]::after {
    top: -5px;
    border-left: 1px solid #d4d4d5;
    border-top: 1px solid #d4d4d5;
  }
  .ui-tooltip[data-placement="top"]::after {
    bottom: -5px;
    border-right: 1px solid #d4d4d5;
    border-bottom: 1px solid #d4d4d5;
  }

/* Android-style toast for the long-press "Text copied" confirmation. Created on
   demand in app.js (#appToast), bottom-center, never intercepts taps. Sits above
   the card menus / backdrops (z-index 1000) so it's visible over anything open. */
.app-toast {
  position: fixed;
  left: 50%;
  bottom: 40px;
  transform: translateX(-50%) translateY(8px);
  max-width: 80vw;
  padding: 10px 18px;
  border-radius: 22px;
  background: rgba(0, 0, 0, 0.85);
  color: #fff;
  font-size: 0.95rem;
  line-height: 1.2;
  text-align: center;
  pointer-events: none;
  opacity: 0;
  z-index: 1100;
  transition: opacity 0.18s ease, transform 0.18s ease;
}
.app-toast.is-visible {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

/* ---------- tablet (native shell only) ----------
   Without this, the native iPad app renders a 560px-wide, top-anchored phone column
   that leaves ~50% of the screen empty: .compact caps the card at 403px for iPhone
   safe-area reasons, and that cap is far too small on a tablet.

   Two guards, both load-bearing:

   `min-width: 700px` — wide enough to exclude every iPhone (the widest iPhone portrait
   viewport is 440pt) but low enough to include the iPad mini, whose portrait viewport is
   only 744pt. A 768px gate looked safe but silently skipped the iPad mini in portrait,
   leaving exactly the half-empty phone column this block exists to fix, and then snapping
   to the tablet layout on rotation.

   `min-height: 600px` — a width-only gate ALSO matches an iPhone in
   landscape. 430pt is the iPhone *portrait* width; rotated, the viewport is 852-956px
   wide (Info.plist enables both landscape orientations, and the viewport meta is
   width=device-width). In that 430px-tall viewport `clamp(430px, 52vh, 660px)` pins
   to its 430px floor and the card alone fills the screen, pushing the header and
   controls off it. The shortest iPad viewport in either orientation is the iPad mini's
   744px (its portrait width, rotated), so a 600px gate keeps every tablet while still
   excluding phone landscape, whose tallest case is 440px.

   `html.compact` — .compact is set only for the native shell and mobile-web phones
   (src/app.js init()). Bare selectors here would also restyle the desktop web build
   at swedishflashcards.app, which is served from this same stylesheet and is NOT what
   this block is for. The html.compact prefix also outranks the .compact rules above
   (0,2,1 vs 0,2,0), which is what makes the card height override take effect. */
@media (min-width: 700px) and (min-height: 600px) {
  html.compact header,
  html.compact .progress,
  html.compact .progress-bar,
  html.compact .controls,
  html.compact .utility-buttons,
  html.compact .card-container,
  html.compact .selector-row {
    max-width: 720px;
  }
  html.compact .selector-row { width: 100%; }
  /* Auto margins consume free space before justify-content ever sees it, so drop the
     phone rule's here and let the centering below do its job. Every gap floor lives on
     the preceding row's margin-bottom, so the spacing itself is unaffected. */
  html.compact .card-container,
  html.compact .controls,
  html.compact .selector-row,
  html.compact .utility-buttons { margin-top: 0; }
  html.compact .card-container {
    height: clamp(430px, 52vh, 660px);
  }
  /* Centre the column vertically so the leftover space reads as deliberate padding
     instead of pooling into one dead band under the content (31% bottom-only becomes
     ~16% top / ~16% bottom on a 13" iPad). Two declarations, in this order, on
     purpose: iOS 15.0-15.3 predates the `safe` alignment keyword and keeps plain
     `center`, while iOS 15.4+ uses `safe center`, which degrades to flex-start rather
     than clipping the top if the content ever outgrows the viewport. */
  html.compact body {
    justify-content: center;
    justify-content: safe center;
  }
}
