/**
 * Nihongo Dominos — Design System v1 ("Mahjong Hall")
 *
 * Shared visual language across all pages: dark ink canvas, mahjong-tile
 * aesthetic with bone faces / engraved kanji / bevels, restrained typography
 * (Noto Serif JP for Japanese, Source Serif for English headers, Inter for
 * UI labels), single vermilion accent + brass / indigo / charcoal supporting.
 *
 * Each page <link>s this stylesheet and adds only its page-specific overrides.
 * Cache-busted by the GitHub Pages deploy workflow (rewrites the ?v=...
 * query string to the commit SHA — see project_cache_busting.md memory).
 *
 * Loaded from:
 *   - root pages         <link rel="stylesheet" href="css/design.css?v=...">
 *   - per-app subpages   <link rel="stylesheet" href="../css/design.css?v=...">
 *
 * Fonts (Google) loaded via <link> in each page <head>:
 *   Noto Serif JP — 400 / 600 / 700 / 900
 *   Source Serif 4 — 400 / 600 / 700
 *   Inter — 400 / 500 / 600
 */

/* ============================================================
   1. DESIGN TOKENS
   ============================================================ */
:root {
  /* Canvas + surface */
  --canvas: #1a1d2c;
  --canvas-elev: #232739;
  --canvas-deep: rgba(20, 23, 33, 0.5);

  /* Tile material (bone / ivory) */
  --bone:        #e8dcc4;
  --bone-bright: #f0e5d0;
  --bone-shadow: #b8a888;
  --tile-edge:   #4a4533;
  /* Engraved tile-glyph ink. The tile face is bone in BOTH themes, so the
     glyph must stay dark in both — a FIXED dark ink (was `var(--canvas)`, which
     vanished in light mode when the canvas turned cream). Dark mode unchanged. */
  --tile-engrave: #1a1d2c;

  /* Text on dark canvas */
  --text:       #f0e8d3;
  --text-soft:  #a89f8a;
  --text-faint: #6e6a5c;

  /* Accents — used sparingly */
  --vermilion: #d44a3a;   /* primary accent */
  --brass:     #c9a464;   /* secondary accent */
  --indigo:    #2a3458;   /* supporting */
  --hanada:    #5e85bb;   /* hanada blue (Joshua's preferred cool accent; never green) */

  /* Flip-card surfaces — subtle inset panel + the floating-card outline. These
     flip in light mode (see body[data-theme="light"]) so cards adopt day mode too. */
  --card-inset:   rgba(255, 255, 255, 0.06);
  --card-outline: rgba(255, 255, 255, 0.85);

  /* Flip-card reading accents (color-coded readings on the cards). Shared with
     hanashi verbatim; light values deepen in the day-mode block so they stay legible. */
  --read-furi:    #c8e0ff;   /* furigana reading (Goi/Bunpou) */
  --read-pattern: #9ec5ff;   /* bunpou pattern reading */
  --read-kun:     #e87560;   /* kanji kun-yomi */
  --read-on:      #8aa0d8;   /* kanji on-yomi */
  --read-label:   #d8c08a;   /* kanji card labels */

  /* Lines / rules */
  --rule: rgba(232, 220, 196, 0.12);

  /* Radii */
  --radius-tile: 12px;
  --radius-card: 14px;
  --radius-pill: 100px;

  /* Type stacks */
  --font-jp:   'Noto Serif JP', Georgia, serif;
  --font-en:   'Source Serif 4', Georgia, serif;
  --font-ui:   'Inter', -apple-system, system-ui, sans-serif;
}

/* ============================================================
   LIGHT THEME — in-app day/night toggle (floating sun/moon button →
   :root[data-theme="light"], set on <html> by the per-page head flash-guard +
   js/theme.js). A washi-paper palette: warm paper canvas, sumi-ink text,
   brass/vermilion/hanada deepened so accents read on a light ground. Tokens
   cascade, so most components flip for free.

   DESIGN NOTE — the mahjong tiles are dominos's identity. On the dark canvas
   they pop as light bone on ink; on the light canvas they'd wash out, so light
   mode BRIGHTENS the bone face toward ivory-white and DEEPENS the tile edge so
   the tiles still lift off the cream ground via brightness + edge + shadow.
   ============================================================ */
:root[data-theme="light"] {
  --canvas:      #efe6d2;
  --canvas-elev: #e6dabf;
  --canvas-deep: rgba(230, 218, 191, 0.6);

  /* Bone tiles: brighten the face + deepen the edge for light-ground contrast. */
  --bone:        #fbf4e3;
  --bone-bright: #fffaf0;
  --bone-shadow: #d8c8a4;
  --tile-edge:   #b0a079;

  --text:        #2b2620;
  --text-soft:   #6c6354;
  --text-faint:  #978d7a;

  --vermilion:   #c2402f;
  --brass:       #8a6a1e;
  --indigo:      #3a4675;
  --hanada:      #3f6aa0;

  --rule: rgba(60, 50, 30, 0.20);

  /* Flip cards adopt day mode too — inset panels + outline go dark-on-light. */
  --card-inset:   rgba(43, 38, 32, 0.06);
  --card-outline: rgba(43, 38, 32, 0.28);
  /* Deepen card reading accents so they stay legible on the paper card. */
  --read-furi:    #41699e;
  --read-pattern: #3a5f93;
  --read-kun:     #bf4030;
  --read-on:      #41699e;
  --read-label:   #8a6a1e;
}

/* Floating day/night toggle — fixed top-right, present on every page (injected
   by js/theme.js). Shows the CURRENT theme's icon; tapping flips it. Hidden
   while a flip-card overlay is open so it never floats over a card. */
.theme-toggle {
  position: fixed;
  top: calc(env(safe-area-inset-top, 0px) + 10px);
  right: calc(env(safe-area-inset-right, 0px) + 12px);
  z-index: 90;
  width: 38px; height: 38px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%;
  background: var(--canvas-elev);
  color: var(--brass);
  border: 1px solid var(--rule);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.28);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: transform 0.12s ease;
}
.theme-toggle:active { transform: scale(0.92); }
.theme-toggle .icon-sun,
.theme-toggle .icon-moon { display: inline-flex; }
.theme-toggle .icon-sun { display: none; }
:root[data-theme="light"] .theme-toggle .icon-moon { display: none; }
:root[data-theme="light"] .theme-toggle .icon-sun { display: inline-flex; }
body.flipped-active .theme-toggle { display: none; }

/* ============================================================
   2. BASE — html, body, page background (felt texture)
   ============================================================ */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

html, body {
  background: var(--canvas);
  color: var(--text);
  font-family: var(--font-en);
  min-height: 100vh;
  min-height: 100dvh;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* Respect iOS Dynamic Type: 'auto' lets WKWebView apply the user's
     system text-size preference where layout permits. Tile grids stay
     pixel-tuned via explicit font-size: <px> rules; body copy on
     about/settings/dashboard pages flexes with the user's preference. */
  -webkit-text-size-adjust: auto;
  text-size-adjust: auto;
}

/* Subtle table-felt texture: red + brass radial glows + diagonal weave.
   Lives on body::before so it's behind everything but doesn't block clicks. */
body::before {
  content: '';
  position: fixed; inset: 0;
  background-image:
    radial-gradient(ellipse at 20% 0%, rgba(212, 74, 58, 0.06) 0%, transparent 50%),
    radial-gradient(ellipse at 80% 100%, rgba(201, 164, 100, 0.05) 0%, transparent 50%),
    repeating-linear-gradient(45deg, rgba(255,255,255,0.008) 0px, transparent 1px, transparent 4px);
  pointer-events: none;
  z-index: 0;
}

/* iPad-only seigaiha (青海波 — overlapping wave) pattern overlay.
   Traditional Edo motif, used here as a low-contrast ambient backdrop on
   the wider iPad canvas where the iPhone's tight column doesn't leave
   room for ornament. Brass strokes at ~4% effective opacity (combined
   alpha + opacity) — readable as texture from arm's length, invisible
   under tiles + popup chrome. The SVG is inline-data-URL so there's no
   extra HTTP request and it ships with the existing precache.
   Layered above body::before's weave so both compose. iPhone untouched. */
html.ipad body::after {
  content: '';
  position: fixed;
  inset: 0;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='80' height='40' viewBox='0 0 80 40'><g fill='none' stroke='%23c9a464' stroke-width='0.6'><circle cx='40' cy='40' r='38'/><circle cx='40' cy='40' r='28'/><circle cx='40' cy='40' r='18'/><circle cx='40' cy='40' r='8'/><circle cx='0' cy='0' r='38'/><circle cx='0' cy='0' r='28'/><circle cx='0' cy='0' r='18'/><circle cx='0' cy='0' r='8'/><circle cx='80' cy='0' r='38'/><circle cx='80' cy='0' r='28'/><circle cx='80' cy='0' r='18'/><circle cx='80' cy='0' r='8'/></g></svg>");
  background-repeat: repeat;
  background-size: 80px 40px;
  opacity: 0.08;
  pointer-events: none;
  z-index: 0;
}

/* ============================================================
   3. APP CONTAINER — phone-width column, padded for safe areas
   ============================================================ */
.app {
  position: relative;
  z-index: 1;
  max-width: 480px;
  margin: 0 auto;
  padding:
    calc(env(safe-area-inset-top, 0px) + 32px)
    24px
    calc(env(safe-area-inset-bottom, 0px) + 100px);
  min-height: 100vh;
  min-height: 100dvh;
}

/* ----- Default link color (Mahjong Hall dark canvas) -----
   Inline body links inside .app default to brass on dark canvas so they
   remain readable. Browser-default `<a>` blue is unreadable on our dark
   indigo background. This is a UNIVERSAL default — applies to every
   page that uses the .app container. Per-component overrides (e.g. nav
   tabs, hero tiles, special states) still take precedence via specificity.
   See `feedback_link_color_dark_canvas.md` in memory — this default
   exists because container-scoped rules kept getting missed for new pages. */
.app a {
  color: var(--brass);
  text-decoration-color: rgba(201, 164, 100, 0.4);
  text-underline-offset: 2px;
}
.app a:hover {
  text-decoration-color: var(--brass);
}

/* ============================================================
   4. HEADER — brand mark, brand name, tagline
   ============================================================ */
.header {
  text-align: center;
  padding: 16px 0 28px;
  border-bottom: 1px solid var(--rule);
  margin-bottom: 36px;
}

.brand-mark {
  display: inline-block;
  width: 36px; height: 36px;
  background: linear-gradient(155deg, var(--bone-bright), var(--bone) 60%, var(--bone-shadow));
  color: var(--vermilion);
  font-family: var(--font-jp);
  font-weight: 900;
  font-size: 22px;
  line-height: 36px;
  border-radius: 6px;
  margin-bottom: 14px;
  box-shadow:
    0 2px 0 var(--tile-edge),
    0 4px 12px rgba(0, 0, 0, 0.4);
}

.brand-name {
  font-family: var(--font-jp);
  font-weight: 700;
  font-size: 42px;          /* upsized from 28px for more presence on home/landing pages */
  letter-spacing: 0.04em;
  color: var(--text);
  line-height: 1.1;
}

.brand-name-en {
  font-family: var(--font-en);
  font-weight: 600;          /* upsized weight from 400 to match the larger Japanese title */
  font-size: 15px;           /* upsized from 12px */
  color: var(--text-soft);   /* lifted from text-faint for better contrast */
  letter-spacing: 0.22em;
  text-transform: uppercase;
  margin-top: 10px;
}

/* Tagline kicker — leads the home-page header copy. "INTERMEDIATE JAPANESE"
   in brass small-caps. Acts as the self-selection signal (Joshua's strategic
   positioning — see project_positioning_and_messaging.md in user memory).
   The .tagline below delivers the methodology benefit (frequency + three
   pillars). */
.tagline-kicker {
  font-family: var(--font-ui);
  font-weight: 600;
  font-size: 14px;
  color: var(--brass);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  margin-top: 18px;
  line-height: 1.2;
}

.tagline {
  font-family: var(--font-en);
  font-style: italic;
  font-size: 13px;
  color: var(--text-soft);
  margin-top: 6px;  /* tighter to the kicker above; was 14px when tagline was the only line */
  line-height: 1.5;
}
.credit {
  display: block;
  margin-top: 4px;
  font-size: 11px;
  font-style: normal;
  color: var(--text-faint);
  letter-spacing: 0.04em;
}

/* Page-section title (used by per-app indexes for "Choose a category" etc.) */
.section-title {
  font-family: var(--font-ui);
  font-weight: 500;
  font-size: 11px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--text-faint);
  text-align: center;
  margin: 0 0 22px;
}
.section-title::before, .section-title::after {
  content: '— ';
  color: var(--vermilion);
}
.section-title::after { content: ' —'; }

/* "Back to ..." link — small, restrained */
.back-link {
  display: inline-block;
  font-family: var(--font-ui);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-soft);
  text-decoration: none;
  margin-top: 12px;
  -webkit-tap-highlight-color: transparent;
}
.back-link:hover { color: var(--text); }
.back-link::before { content: '← '; color: var(--vermilion); }

/* ============================================================
   5. HERO 2×2 GRID — mahjong-tile-shaped navigation cards
   ============================================================ */
.hero-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  margin-bottom: 24px;
}

.hero-tile {
  /* Square tiles — keeps the bone-tile aesthetic but uses the page real
     estate efficiently. Affects every landing page that uses .hero-grid. */
  aspect-ratio: 1 / 1;
  position: relative;
  cursor: pointer;
  perspective: 600px;
  transition: transform 0.18s ease;
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
}
.hero-tile:hover { transform: translateY(-3px); }
.hero-tile:active { transform: translateY(0); }
.hero-tile:focus-visible {
  outline: 2px solid var(--brass);
  outline-offset: 4px;
  border-radius: var(--radius-card);
}

/* The visible face of each hero tile — bone material with bevel + drop shadow.
   Standalone class so it can also be used outside .hero-tile for visual demos. */
.tile-face {
  position: absolute;
  inset: 0;
  background: linear-gradient(155deg, var(--bone-bright) 0%, var(--bone) 50%, var(--bone-shadow) 100%);
  border-radius: var(--radius-tile) var(--radius-tile) 8px 8px;
  box-shadow:
    0 3px 0 var(--tile-edge),
    inset -2px -2px 4px rgba(74, 69, 51, 0.3),
    inset 2px 2px 4px rgba(255, 255, 255, 0.4),
    0 8px 18px rgba(0, 0, 0, 0.45);
  padding: 24px 14px 18px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: center;
  text-align: center;
}
/* Backwards-compat alias for the older .hero-tile-face name used in
   prototypes — pages can use either selector. */
.hero-tile-face { /* same as .tile-face */
  position: absolute;
  inset: 0;
  background: linear-gradient(155deg, var(--bone-bright) 0%, var(--bone) 50%, var(--bone-shadow) 100%);
  border-radius: var(--radius-tile) var(--radius-tile) 8px 8px;
  box-shadow:
    0 3px 0 var(--tile-edge),
    inset -2px -2px 4px rgba(74, 69, 51, 0.3),
    inset 2px 2px 4px rgba(255, 255, 255, 0.4),
    0 8px 18px rgba(0, 0, 0, 0.45);
  /* Padding tightened for the square aspect so kanji + label fit cleanly. */
  padding: 18px 12px 14px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: center;
  text-align: center;
}

/* Engraved kanji — text-shadow stack creates the recessed-into-bone look.
   Sized for the square tile (was 76px on the previous portrait version). */
.hero-kanji {
  font-family: var(--font-jp);
  font-weight: 900;
  font-size: 60px;
  line-height: 1;
  color: var(--vermilion);   /* default — overridden by tile color class */
  text-shadow:
    0 1px 0 rgba(255,255,255,0.5),
    0 -1px 0 rgba(74, 69, 51, 0.4),
    1px 1px 0 rgba(74, 69, 51, 0.3);
  letter-spacing: -0.03em;
}
.hero-tile.brass    .hero-kanji { color: var(--brass); }
.hero-tile.indigo   .hero-kanji { color: var(--indigo); }
.hero-tile.charcoal .hero-kanji { color: var(--tile-engrave); }

.hero-tile-label {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.hero-en {
  font-family: var(--font-en);
  font-weight: 700;
  font-size: 16px;
  color: var(--tile-engrave);
  line-height: 1;
}
.hero-en-sub {
  font-family: var(--font-ui);
  font-weight: 500;
  font-size: 9px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--tile-edge);
}

/* ============================================================
   5b. REFERENCE SECTION — secondary tile pair below the four
       main apps. Visually less prominent than .hero-tile (smaller
       glyphs, dimmer bone surface, no engraved depth) so they read
       as a side-rail rather than a fifth pillar. Brass double-rule
       above echoes the divider on the kanji back-face top header.
   ============================================================ */
.reference-section {
  margin-top: 8px;
  margin-bottom: 24px;
}

/* Brass double-rule with a small "Reference" label centered on it.
   Layout: a flex row where the label sits between two flex-1 lines. */
.reference-divider {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  margin: 0 6px 16px;
  /* The two horizontal rules are drawn as ::before / ::after pseudo-
     elements — each one a 3px brass double-line. */
}
.reference-divider::before,
.reference-divider::after {
  content: '';
  flex: 1 1 auto;
  height: 0;
  border-top: 3px double var(--brass);
  opacity: 0.7;
}
.reference-label {
  font-family: var(--font-ui);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--brass);
  flex: 0 0 auto;
}

/* Two-column grid mirroring .hero-grid but with shorter aspect ratio
   so the reference cards look distinctly secondary at a glance. */
.reference-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
}

.reference-tile {
  /* Wider than tall — half the height of the .hero-tile squares above —
     reads as a secondary side-rail, not another pillar. */
  aspect-ratio: 2 / 1;
  position: relative;
  cursor: pointer;
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
  transition: transform 0.18s ease;
}
.reference-tile:hover { transform: translateY(-2px); }
.reference-tile:active { transform: translateY(0); }
.reference-tile:focus-visible {
  outline: 2px solid var(--brass);
  outline-offset: 4px;
  border-radius: var(--radius-card);
}

/* Dimmer surface than .hero-tile — same material concept (gradient bone)
   but pulled toward the canvas dark so it sits visually behind the four
   main tiles. No drop shadow stack; just a thin brass stroke + soft fill. */
.reference-tile-face {
  position: absolute;
  inset: 0;
  background: linear-gradient(155deg, rgba(232, 220, 196, 0.10) 0%, rgba(232, 220, 196, 0.05) 50%, rgba(232, 220, 196, 0.02) 100%);
  border: 1px solid rgba(201, 164, 100, 0.35);
  border-radius: 10px;
  box-shadow: inset 0 1px 0 rgba(232, 220, 196, 0.06);
  padding: 10px 14px;
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: flex-start;
  gap: 14px;
  text-align: left;
}

/* Smaller glyph than .hero-kanji — 38px instead of 60px, brass tint
   instead of vermilion/indigo. Engraved-style text-shadow is dropped
   because the surface is dim, not bone-colored. */
.reference-kanji {
  font-family: var(--font-jp);
  font-weight: 700;
  font-size: 38px;
  line-height: 1;
  color: var(--brass);
  letter-spacing: -0.02em;
  flex: 0 0 auto;
}

.reference-tile-label {
  display: flex;
  flex-direction: column;
  gap: 3px;
  flex: 1 1 auto;
  min-width: 0;
}
.reference-en {
  font-family: var(--font-en);
  font-weight: 600;
  font-size: 14px;
  color: var(--text);
  line-height: 1;
}
.reference-en-sub {
  font-family: var(--font-ui);
  font-weight: 500;
  font-size: 9px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-faint);
  /* Tight line-height so a wrapped sub (e.g. Kana's "Hiragana / Katakana"
     or Bushu's "Radicals / 214") feels like one logical unit rather than
     two disconnected items. */
  line-height: 1.15;
}

/* ============================================================
   6. BOTTOM UTILITY BAR — Home / Settings / About
   ============================================================ */
.bottom-bar {
  position: fixed;
  bottom: 0; left: 0; right: 0;
  background: rgba(20, 23, 33, 0.92);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-top: 1px solid rgba(232, 220, 196, 0.1);
  z-index: 50;
  /* Flexbox row layout — bulletproof across every iOS version, avoids any
     CSS Grid edge cases on older WebKit. The dark blur background still
     stretches edge-to-edge thanks to position:fixed left/right 0; only
     the tab CONTENTS are constrained near the center via max-width below. */
  display: flex;
  flex-direction: row;
  justify-content: center;
  align-items: stretch;
  padding: 10px 12px calc(10px + env(safe-area-inset-bottom));
}
/* Each tab takes equal share within a 480px-wide content envelope.
   flex: 1 1 0 = grow + shrink + zero basis (forces equal distribution).
   max-width caps so on tablet/desktop the row doesn't sprawl. */
.bottom-bar > .bottom-tab {
  flex: 1 1 0;
  min-width: 0;
  max-width: 160px;
}
/* Backwards-compat: any page that wraps tabs in .bottom-bar-inner still
   works because display:contents lets its children participate in the
   parent flex layout directly. */
.bottom-bar-inner {
  display: contents;
}
.bottom-tab {
  /* Horizontal layout — icon + label side-by-side. Saves vertical real
     estate vs. the iOS-default stacked layout. */
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 8px 6px;
  cursor: pointer;
  font-family: var(--font-ui);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-faint);
  text-decoration: none;
  transition: color 0.15s ease;
  -webkit-tap-highlight-color: transparent;
}
.bottom-tab.active { color: var(--brass); }
.bottom-tab svg { width: 18px; height: 18px; stroke-width: 1.5; flex-shrink: 0; }
.bottom-tab:hover { color: var(--text-soft); }
.bottom-tab:focus-visible {
  outline: 2px solid var(--brass);
  outline-offset: 2px;
  border-radius: 4px;
}

/* ── Bottom Control Center Bar (bccb) — symbol-only, injected by js/bccb.js on
   EVERY page. The single source of app chrome: replaces the labelled .bottom-bar
   tab rows, the top "Back to X" links, and the floating theme toggle. ───────── */
.bccb {
  position: fixed;
  bottom: 0; left: 0; right: 0;
  z-index: 80;
  display: flex;
  flex-direction: row;
  justify-content: center;
  align-items: stretch;
  gap: 2px;
  padding: 6px 8px calc(6px + env(safe-area-inset-bottom, 0px));
  background: rgba(20, 23, 33, 0.92);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-top: 1px solid rgba(232, 220, 196, 0.1);
}
:root[data-theme="light"] .bccb {
  background: rgba(239, 230, 210, 0.92);
  border-top-color: rgba(0, 0, 0, 0.08);
}
.bccb-btn {
  flex: 1 1 0;
  min-width: 42px;
  max-width: 64px;
  height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  color: var(--text-faint);
  cursor: pointer;
  text-decoration: none;
  border-radius: 9px;
  -webkit-tap-highlight-color: transparent;
  transition: color 0.15s ease, background 0.15s ease, transform 0.1s ease;
}
.bccb-btn:active { transform: scale(0.9); }
.bccb-btn:hover { color: var(--text-soft); }
.bccb-btn svg { width: 22px; height: 22px; stroke-width: 1.7; flex-shrink: 0; }
.bccb-glyph {
  font-family: 'Noto Serif JP', serif;
  font-size: 20px; line-height: 1; font-weight: 600;
}
.bccb-active { color: var(--brass); }          /* current page (home/settings/about) */
.bccb-return { color: var(--text-soft); }      /* the back arrow — primary nav affordance */
.bccb-toggle.bccb-on {                          /* an ON toggle glows brass in a soft pill */
  color: var(--brass);
  background: rgba(201, 162, 96, 0.16);
}
.bccb-btn:focus-visible { outline: 2px solid var(--brass); outline-offset: 2px; }
body.flipped-active .bccb { display: none; }    /* clear of a flip-card overlay */
body.has-bccb { padding-bottom: calc(58px + env(safe-area-inset-bottom, 0px)); }
/* Study mode — match the bccb to the calm Hanada-blue study ground (the bar's
   default navy/porcelain would otherwise clash with the blue canvas). */
body.study-mode .bccb {
  background: rgba(24, 36, 56, 0.92);
  border-top-color: rgba(94, 133, 187, 0.22);
}
:root[data-theme="light"] body.study-mode .bccb {
  background: rgba(221, 228, 238, 0.94);
  border-top-color: rgba(63, 106, 160, 0.18);
}

/* ============================================================
   7. UTILITY — buttons, motion, etc.
   ============================================================ */
.btn {
  display: inline-block;
  font-family: var(--font-ui);
  font-weight: 500;
  font-size: 13px;
  letter-spacing: 0.08em;
  padding: 10px 18px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--rule);
  background: rgba(232, 220, 196, 0.05);
  color: var(--text);
  text-decoration: none;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease;
}
.btn:hover { background: rgba(232, 220, 196, 0.10); border-color: rgba(232, 220, 196, 0.25); }
.btn-primary {
  background: var(--vermilion);
  border-color: var(--vermilion);
  color: var(--bone);
}
.btn-primary:hover { background: #e85544; border-color: #e85544; }

/* ============================================================
   AUDIO BUTTON — tap-to-hear speaker icon on every back face.

   Markup (built in js/audio.js → NihonAudio.buttonHtml):
     <button class="audio-btn" data-audio="<jp text>" aria-label="…">
       <svg …>…</svg>
     </button>

   The icon uses currentColor so the brass / vermilion color flows
   through the button's `color` rule. 44×44 outer touch target keeps
   it iOS-HIG-friendly even though the visual glyph is ~20px. The
   .is-playing class is toggled by the audio.js click delegate while
   speech is in progress (vermilion fill) and removed on end / cancel.

   Pronunciation playback is always on (2026-06-16) — the speaker icon is
   never hidden; the old audioPlayback toggle / .audio-playback-off were removed.
   ============================================================ */
.audio-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 6px;
  border: none;
  background: transparent;
  color: var(--brass);
  cursor: pointer;
  flex-shrink: 0;
  border-radius: 50%;
  transition: color 0.15s ease, background 0.15s ease, transform 0.18s ease;
  -webkit-tap-highlight-color: transparent;
  vertical-align: middle;
}
.audio-btn svg {
  width: 100%;
  height: 100%;
  display: block;
}
.audio-btn:hover {
  color: var(--text);
  background: rgba(232, 220, 196, 0.06);
}
.audio-btn:active {
  transform: scale(0.94);
}
.audio-btn:focus-visible {
  outline: 2px solid var(--brass);
  outline-offset: 2px;
}
.audio-btn.is-playing {
  color: var(--vermilion);
  animation: audio-btn-pulse 1.1s ease-in-out infinite;
}
@keyframes audio-btn-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.55; }
}


/* ============================================================
   STUDY MODE — Hanada (indigo-blue) theme

   Per-session toggle on each game page (Goi / Bunpou / Kanji).
   Wired by js/study-mode.js. When ON the body gets `study-mode`
   class and the entire UI re-tints to a Hanada-blue palette —
   restful, clearly distinct from the main vermilion-accented mode,
   but still inside the Mahjong Hall family. Brass stays unchanged
   (it pairs perfectly with blue and anchors the visual identity).

   Hanada (縹) is a traditional Japanese indigo-derived blue —
   between sky and deep indigo. Same role bamboo green used to fill
   (calls-to-action, glow progression, verb badges) without the
   "alert" connotation that's wrong for a learning phase.

   Why these specific overrides:
   - Tile face (bone) shifts to a cool porcelain blue — visible cue
     that the mode is on every time you look at the grid.
   - The vermilion accent → Hanada blue (used for CTAs and glows).
   - Canvas darkens with a deeper blue tint for ambient mood — clearly
     distinct from the default navy-purple canvas.

   The button itself (#btn-study) flips to a "filled" look when
   active so the toggle state is unambiguous.
   ============================================================ */
body.study-mode {
  --canvas:        #182438;
  --canvas-elev:   #232f48;
  --canvas-deep:   rgba(20, 28, 50, 0.5);
  --vermilion:     #5e85bb;
  --bone:          #d6dfeb;
  --bone-bright:   #e3ecf5;
  --bone-shadow:   #98a3b6;
  --indigo:        #2a334a;
  --tile-edge:     #3a4258;
}

/* Study mode in LIGHT theme — keep the calm Hanada-blue ambient but on a light
   porcelain ground (the dark study palette above would otherwise force navy
   cards/canvas in light mode; reference pages run in study mode, so their cards
   stayed dark). Text/glyph stay dark via --text / --tile-engrave. */
:root[data-theme="light"] body.study-mode {
  --canvas:        #dde4ee !important;
  --canvas-elev:   #e9eef6 !important;
  --canvas-deep:   rgba(200, 212, 230, 0.5) !important;
  --vermilion:     #3f6aa0 !important;
  --bone:          #eef2f8 !important;
  --bone-bright:   #f7fafd !important;
  --bone-shadow:   #bcc7da !important;
  --indigo:        #3a4675 !important;
  --tile-edge:     #aab4c6 !important;
}

/* Hide elements that don't belong in the calm-list study experience. */
body.study-mode .progress-panel { display: none; }
body.study-mode #btn-shuffle { display: none; }
body.study-mode .auto-shuffle-info { display: none; }

/* Study toggle button — base shape inherits from .btn .btn-secondary.
   When pressed (.is-on) it flips to a filled Hanada-blue pill so the
   active state is visually unambiguous. */
#btn-study.is-on {
  background: var(--vermilion);
  border-color: var(--vermilion);
  color: #f1f4f8;
}
#btn-study.is-on:hover {
  background: #7098ca;
  border-color: #7098ca;
}

/* ============================================================
   GAME-PAGE CONTROLS — keep all four buttons (Study · Reset ·
   Shuffle · Finish) on a single row even on iPhone SE width
   (~375px). The per-pillar .btn rules in goi/kanji css already
   shrink the buttons, but a 4th button pushes them over the
   line on the narrowest devices. This block tightens horizontal
   padding + gap inside .controls only — game-over buttons and
   other .btn instances elsewhere keep their roomy sizing.

   Specificity beats `body.game-page .btn` (0,0,2,1) via
   `body.game-page .controls .btn` (0,0,3,1).
   ============================================================ */
body.game-page .controls {
  gap: 0.4rem;
  flex-wrap: nowrap;
}
body.game-page .controls .btn {
  padding: 0.5rem 0.7rem;
  font-size: 0.78rem;
  letter-spacing: 0.04em;
  flex: 0 1 auto;
  min-width: 0;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    /* Stop infinite animations after one iteration so they don't cycle
       at near-zero duration (which would produce visual flicker — the
       OPPOSITE of what reduced-motion users want). */
    animation-iteration-count: 1 !important;
  }
  /* Audio-button pulsing breathe loop has no semantic meaning at speed —
     just hide the throb entirely when motion is reduced. The button is
     still tappable and visually present (vermilion text-shadow stays). */
  .audio-btn[data-active="1"] {
    animation: none !important;
  }
}

/* ============================================================
   GRID-OFF (full-pool list mode) — Settings: Grid format = off

   When the user disables the standard 3×8 / 4×5 game grid, each
   subsection's main.js renders the entire entry pool as a single
   tall grid. The .grid-list co-class signals "this is list mode"
   so we can widen the column to match normal-play readability
   instead of the narrow shrunk-grid fallback (.grid-3 = 360px).

   Game mechanics (peek, double-click, win) still apply.
   ============================================================ */
.grid-container.grid-list {
  max-width: min(100%, 420px);
}
body.game-page .grid-container.grid-list {
  max-width: 100%;
  width: 100%;
}

/* ============================================================
   FLIPPED OVERLAY — body-level modal for the back-face popup

   The .domino in the grid stays a closed tile when flipped. JS
   clones the active tile's .domino-face-back HTML into the overlay
   at body level, wraps it in <div class="domino domino-{type} flipped">,
   and shows the overlay. Existing back-face content rules
   (.domino.domino-goi.flipped .goi-word etc.) keep matching the
   cloned wrapper, so no per-rule rewriting needed. The override
   block below neutralizes the closed-tile chrome (.domino's
   aspect-ratio, background, transforms, compositor hints) so the
   cloned wrapper doesn't fight the overlay's own dimensions.

   Why body-level: iOS Safari's compositor inherits 3D-card hints
   from .grid-container/.domino if the modal is a descendant; we
   spent multiple sessions chasing that. At body level there's no
   transformed/contained ancestor and iOS has nothing to glitch on.
   See feedback_ios_compositor_pitfall.md in user memory.
   ============================================================ */
.flipped-overlay {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: min(92vw, 420px);
  /* Vertical-only +10% bumps applied across pillars (82vh → 90vh → 99vh;
     540px → 600px → 660px) so most example sentences fit without scrolling. */
  height: min(99vh, 660px);
  z-index: 1000;
  border-radius: 14px;
  cursor: pointer;
  overflow: visible;
  /* Defensive iOS opt-out — keep the overlay in the normal paint flow. */
  will-change: auto;
  backface-visibility: visible;
  -webkit-backface-visibility: visible;
  transform-style: flat;
  -webkit-transform-style: flat;
}
.flipped-overlay[hidden] { display: none !important; }

/* Force the cloned .domino inside the body-level overlay to FILL the
   overlay regardless of any per-pillar legacy rules. Some pillars (e.g.,
   bunpou, goi) carry pre-overlay-era rules like
   `.domino.domino-bunpou.flipped { height: min(82vh, 540px) }` that were
   designed for the older in-place modal sizing — those have higher
   specificity than `.flipped-overlay > .domino` and would otherwise cap
   the inner content well below the overlay's universal 660px ceiling.
   Using !important here is intentional: these are layout invariants for
   the overlay architecture, not stylistic preferences.
   The user-visible effect: bunpou's grammar back-faces (and any other
   pillar with a legacy in-place height cap) now get the same vertical
   room as kanji, which has no such legacy rule. */
.flipped-overlay > .domino {
  width: 100% !important;
  height: 100% !important;
  position: relative !important;
  top: auto !important;
  left: auto !important;
  transform: none !important;
  aspect-ratio: auto !important;
}

/* Neutralize the legacy in-place 3D-flip rotation on the cloned .domino
   inside the body-level overlay. The legacy rule
   `.domino.flipped .domino-inner { transform: rotateY(180deg) }` (still
   present in goi/bunpou/challenge css for back-compat) would mirror the
   back-face content here. goi/bunpou each have a per-pillar override that
   sets transform:none on their own .domino-{type}.flipped .domino-inner
   in-grid tiles — but kanji has no such override, so kanji items in the
   Challenge Box render mirrored without this rule.
   Scoped to `.flipped-overlay .domino` so in-grid behavior is untouched. */
.flipped-overlay .domino .domino-inner {
  transform: none !important;
  -webkit-transform: none !important;
}

/* Dim + blur backdrop is a body::before pseudo-element so the overlay
   itself stays a centered card (same architecture as before — no
   full-screen click target to race with iOS synthesized clicks).
   pointer-events: none means clicks in the dim area pass through; the
   .grid-container is set to pointer-events: none while flipped, so
   those clicks land harmlessly on the body. The card is the only
   active dismiss target — tap the card to close. */
body.flipped-active::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 999; /* below .flipped-overlay (1000) */
  background: rgba(20, 23, 33, 0.82);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  pointer-events: none;
  animation: flipped-dim-fade-in 0.18s ease-out;
}
@keyframes flipped-dim-fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}
/* Block the grid from receiving clicks while a tile is flipped — the
   user's only intended interaction is with the centered card (or the
   bottom controls, which sit outside the grid). Without this, taps in
   the dim area would punch through to whatever tile is below. */
body.flipped-active .grid-container {
  pointer-events: none;
}

/* The cloned .domino wrapper is just a class-hook so existing back-face
   rules match. Strip everything that would otherwise size or paint it as
   a closed tile. */
.flipped-overlay > .domino {
  position: relative;
  width: 100%;
  height: 100%;
  aspect-ratio: auto;
  background: transparent;
  box-shadow: none;
  margin: 0;
  padding: 0;
  cursor: pointer;
  transform: none;
  will-change: auto;
  backface-visibility: visible;
  -webkit-backface-visibility: visible;
  transform-style: flat;
  -webkit-transform-style: flat;
  transition: none;
}
.flipped-overlay > .domino .domino-inner {
  position: relative;
  width: 100%;
  height: 100%;
  transform: none;
  transform-style: flat;
  -webkit-transform-style: flat;
  transition: none;
}
.flipped-overlay > .domino .domino-face {
  position: relative;
  width: 100%;
  height: 100%;
  inset: auto;
  transform: none;
  backface-visibility: visible;
  -webkit-backface-visibility: visible;
  padding: 0;
}
.flipped-overlay > .domino .domino-face-front {
  display: none;
}

/* === Kanji card opened from a Goi headword kanji ============================
   goi-jump.js openKanjiCard renders the Kanji card into a .tile-overlay-card
   inside #goi-jump-overlay (lifted to z1100 via GOI_KANJI_CARD_Z). Frame that
   host as a centered modal + let the .domino wrapper fill it, exactly like
   .flipped-overlay frames the in-game kanji card. (Hanashi frames this via
   tile-panel.css, which dominos does not load.) */
#goi-jump-overlay .tile-overlay-card {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: min(92vw, 420px);
  height: min(99vh, 660px);
  border-radius: 14px;
  box-shadow: 0 18px 48px rgba(0, 30, 60, 0.35), 0 6px 16px rgba(0, 30, 60, 0.18);
}
#goi-jump-overlay .tile-overlay-card > .domino {
  position: relative;
  width: 100%;
  height: 100%;
  aspect-ratio: auto;
  background: transparent;
  box-shadow: none;
  margin: 0;
  padding: 0;
  transform: none;
}
#goi-jump-overlay .tile-overlay-card > .domino .domino-inner,
#goi-jump-overlay .tile-overlay-card > .domino .domino-face {
  position: relative;
  width: 100%;
  height: 100%;
  inset: auto;
  transform: none;
  padding: 0;
}
#goi-jump-overlay .tile-overlay-card > .domino .domino-face-front { display: none; }

/* === Suppress iOS text selection across all game-page UI ===
 * Without these, a long-press on iPhone fires the system text-selection
 * magnifier and callout menu BEFORE our 400ms long-press timer can
 * complete — the user sees text highlighted across tiles instead of the
 * tile being removed. Same issue surfaces on any popup that appears
 * after a long-press (game-over card, "all due reviewed" message, etc.)
 * because the residual touchend lands on whatever is now under the
 * finger. Scope: every element on a game page (body.game-page) plus
 * the body-level .flipped-overlay (which is a sibling of #app, also
 * inside body.game-page but safe to list explicitly for clarity). Game
 * pages have no copy-paste use case; suppression is universal there. */
body.game-page,
body.game-page *,
.domino,
.flipped-overlay,
.flipped-overlay * {
  -webkit-touch-callout: none;     /* no iOS magnifier / share-sheet on long-press */
  -webkit-user-select: none;       /* prefixed for older WebKit */
  user-select: none;
}

/* === Long-press feedback (touch "I knew it" gesture) ===
 * The .long-press-active class is added by main.js (in goi/, bunpou/,
 * kanji/, challenge/) on touchstart and removed on touchend / touchcancel
 * / movement-cancel. While the user holds, the tile grows a building
 * vermilion glow; when the timer fires (~400ms), the tile is removed.
 * Animates over 0.4s so the glow visibly "fills in" during the hold.
 * Filter-only — avoids fighting with existing transform / box-shadow
 * rules on the various .domino-{type} classes. */
.domino.long-press-active {
  transition:
    box-shadow 0.3s ease-out,
    filter 0.3s ease-out;
  box-shadow:
    0 0 0 4px var(--hanada),
    0 0 28px 5px rgba(94, 133, 187, 0.85),
    inset 0 0 38px rgba(42, 52, 88, 0.78) !important;
  filter: brightness(0.78) saturate(1.1) !important;
}

/* ============================================================
   CONFIRM / REMINDER MODAL — Mahjong Hall styled

   Shared base for: settings.html (Reset progress + Save Study
   mode progress off), and each game.html (Study mode reminder
   on cold start). Backdrop blurs the canvas; card sits on top
   with a tinted border depending on intent. Buttons inherit
   .btn pill shape. Esc / backdrop click cancels (wired in JS).

   Variants:
     .modal-card           — neutral (rule-colored border)
     .modal-card-danger    — vermilion border + glow
                             (destructive: Reset, Turn off persistence)
     .modal-card-study     — Hanada-blue border + glow
                             (informational: "Study mode is on")
   ============================================================ */
.modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
  background: rgba(20, 23, 33, 0.74);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  animation: modal-fade-in 0.18s ease-out;
}
.modal-backdrop[hidden] {
  display: none;
}
.modal-card {
  width: 100%;
  max-width: 420px;
  background: linear-gradient(180deg, #232739 0%, #1d2031 100%);
  border: 1px solid var(--rule);
  border-radius: 16px;
  padding: 1.5rem 1.4rem 1.3rem;
  box-shadow:
    0 22px 48px rgba(0, 0, 0, 0.5),
    inset 0 1px 0 rgba(232, 220, 196, 0.06);
  animation: modal-rise 0.22s ease-out;
}
.modal-card-danger {
  border-color: rgba(212, 74, 58, 0.5);
  box-shadow:
    0 22px 48px rgba(0, 0, 0, 0.5),
    inset 0 1px 0 rgba(232, 220, 196, 0.06),
    0 0 0 1px rgba(212, 74, 58, 0.18);
}
.modal-card-study {
  border-color: rgba(94, 133, 187, 0.55);
  box-shadow:
    0 22px 48px rgba(0, 0, 0, 0.5),
    inset 0 1px 0 rgba(232, 220, 196, 0.06),
    0 0 0 1px rgba(94, 133, 187, 0.20);
}
.modal-card h2 {
  font-family: var(--font-jp);
  font-weight: 700;
  font-size: 1.4rem;
  letter-spacing: 0.02em;
  color: var(--vermilion);
  margin: 0 0 0.2rem;
  text-align: center;
}
.modal-card-study h2 {
  color: #6cae7d;
}
.modal-card .modal-subtitle {
  font-family: var(--font-en);
  font-style: italic;
  font-size: 0.9rem;
  color: var(--text-soft);
  text-align: center;
  margin: 0 0 1.1rem;
  padding-bottom: 1rem;
  border-bottom: 1px solid var(--rule);
}
.modal-card p {
  font-family: var(--font-en);
  font-size: 0.95rem;
  line-height: 1.55;
  color: var(--text);
  margin: 0 0 1.1rem;
}
.modal-list {
  list-style: none;
  padding: 0;
  margin: 0 0 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.modal-list li {
  position: relative;
  padding: 0.55rem 0.7rem 0.55rem 1.6rem;
  font-family: var(--font-en);
  font-size: 0.92rem;
  color: var(--text);
  background: rgba(232, 220, 196, 0.04);
  border-left: 2px solid rgba(212, 74, 58, 0.45);
  border-radius: 0 6px 6px 0;
}
.modal-list li::before {
  content: '';
  position: absolute;
  left: 0.7rem;
  top: 50%;
  width: 5px;
  height: 5px;
  background: var(--vermilion);
  border-radius: 50%;
  transform: translateY(-50%);
}
.modal-list li strong {
  color: var(--text);
  font-weight: 600;
}
.modal-foot {
  font-family: var(--font-en);
  font-size: 0.82rem;
  color: var(--text-soft);
  font-style: italic;
  margin: 0 0 1.2rem;
  line-height: 1.5;
  text-align: center;
}
.modal-actions {
  display: flex;
  gap: 0.6rem;
  justify-content: center;
}
.modal-actions .btn {
  flex: 1 1 auto;
  max-width: 11rem;
}
.btn-danger {
  background: var(--vermilion);
  border-color: var(--vermilion);
  color: #f4ebd9;
}
.btn-danger:hover {
  background: #e85544;
  border-color: #e85544;
}
@keyframes modal-fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes modal-rise {
  from { opacity: 0; transform: translateY(8px) scale(0.98); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}

/* ============================================================
   iPad-only settings hide rule

   The "Grid format" toggle is iPhone-only. On iPad, GRID is
   force-off in js/game-loop.js (so the iPad's wider canvas always
   uses list-mode auto-fill at 137px cells — no awkward shrink-to-
   smaller-grid transitions). The setting is hidden here so users
   don't see a toggle that wouldn't change anything. iPhone is
   unaffected — it still shows + honors the toggle.
   ============================================================ */
html.ipad #setting-row-grid { display: none; }

/* ============================================================
   iPad landscape — non-game selection / landing pages

   Game pages use #app (id) and have their own iPad layout in
   css/game-page.css. Every OTHER page (tier selectors, index,
   settings, about-*, dashboard, support, privacy) uses .app
   (class). On iPad landscape those .app pages currently render
   as a centered 480px column with vertical scroll — wasting the
   ~700px of empty canvas on each side.

   These rules widen the .app container and reshape the .hero-grid
   from 2-col → 4-col so 8 tier tiles fit in 2 rows instead of 4.
   Tile design itself is unchanged. iPhone untouched (no html.ipad
   means these rules never fire).

   Gated by @media (orientation: landscape) and (min-width: 700px)
   for the PWA safety path — Capacitor's iPad target is locked
   landscape natively via Info.plist.
   ============================================================ */
@media (orientation: landscape) and (min-width: 700px) {
  /* ---------- iPad home page: lock it still (no scroll, no flicker) ----------
     The home page is designed to fit one iPad-landscape screen, so it should
     never scroll. Without a lock, dragging on it triggers iOS rubber-band
     overscroll, which repaints the position:fixed background layers
     (body::before felt + html.ipad body::after seigaiha) and makes the screen
     flicker (Joshua-flagged 2026-06-16). Pin html+body to the viewport and kill
     overscroll so there is no movement at all. Scoped to body.home-page so the
     scrollable pages (settings/about/dashboard) are unaffected. */
  html.ipad body.home-page,
  html.ipad:has(body.home-page) {
    height: 100dvh;
    overflow: hidden;
    overscroll-behavior: none;
    -webkit-overflow-scrolling: auto;
    touch-action: none;        /* no pan/zoom gesture → no rubber-band on a non-scrolling page */
  }
  html.ipad body.home-page .app {
    min-height: 0;
    height: 100dvh;
    overflow: hidden;
  }

  html.ipad .app {
    max-width: 1100px;
    padding-left: max(48px, env(safe-area-inset-left, 0px));
    padding-right: max(48px, env(safe-area-inset-right, 0px));
  }

  /* 8 tier tiles → 4 cols × 2 rows on iPad landscape. */
  html.ipad .hero-grid {
    grid-template-columns: repeat(4, 1fr);
    gap: 20px;
    max-width: 980px;
    margin-left: auto;
    margin-right: auto;
  }
  /* 6-tile pages (Goi sub-pillar select, JLPT-lens Kanji, JLPT-lens
     Bunpou) become 3 cols × 2 rows so the last row isn't a lonely
     "2 tiles aligned left" — full symmetry across both rows. The
     :has() selector auto-detects "exactly 6 hero-tile children"
     (the 6th tile is also the last). Tile width matches the 4-col
     case by tightening max-width proportionally (3×230 + 2×20 gap). */
  html.ipad .hero-grid:has(.hero-tile:nth-child(6):last-child) {
    grid-template-columns: repeat(3, 1fr);
    max-width: 730px;
  }

  /* Slightly tighter aspect on iPad to keep both rows on screen
     without scrolling at iPad Air 11" landscape height (820px). */
  html.ipad .hero-tile {
    aspect-ratio: 1 / 1;
  }

  /* Bump the engraved kanji a touch on the wider tiles so it reads
     at iPad viewing distance. */
  html.ipad .hero-kanji {
    font-size: 72px;
  }

  /* Section title gets a bit more presence on the wider canvas. */
  html.ipad .section-title {
    font-size: 12px;
    margin-bottom: 28px;
  }

  /* ----- Tighten the header on iPad so both tile rows fit above
     the fold on iPad Air 11" landscape (820px viewport height).
     The iPhone-portrait spec wastes ~40% of iPad landscape height
     on header chrome. We:
       - hide the small decorative brand-mark icon (the big 漢字
         title carries the brand identity at any size)
       - hide the "← BACK TO HOME" link (bottom nav already has Home)
       - shrink the Japanese title font and tighten margins
       - pull the English subtitle closer
     iPhone layout untouched. ----- */
  html.ipad .header {
    padding: 8px 0 14px;
    margin-bottom: 18px;
  }
  html.ipad .header .brand-mark { display: none; }
  html.ipad .header .back-link { display: none; }
  html.ipad .brand-name {
    font-size: 28px;
  }
  html.ipad .brand-name-en {
    font-size: 13px;
    margin-top: 6px;
  }

  /* Home page brand-mark removed on iPad (Joshua 2026-05-18 evening).
     Earlier iterations re-enabled the small 日 icon above the wordmark;
     it was redundant with the much-larger 日本語ドミノ title and ate
     vertical space the title can now reclaim. Sub-pages keep the
     compact header (icon hidden) from the rule above. iPhone keeps
     the brand-mark unchanged. */
  html.ipad .app:has(.reference-section) .header .brand-mark {
    display: none;
  }

  /* Home-page-only typography bump (2026-05-18 Joshua-requested).
     The split layout leaves the left header with plenty of room, so the
     ~28px iPad header text was reading as cramped vs. the canvas. Bump
     the whole left-top stack ~100%: brand-name → 56, brand-name-en →
     26, tagline-kicker/tagline scaled to match, brand-mark icon
     enlarged proportionally so the glyph keeps its visual weight next
     to the much-larger 日本語ドミノ wordmark. Sub-pages stay at the
     compact 28px (selector gated on .app:has(.reference-section)).

     `clamp(min, preferred, max)` lets the sizes shrink relatively on
     narrower iPads (mini ≈ 1133pt landscape vs. Air ≈ 1180pt vs. Pro
     13" ≈ 1366pt). The vw-based preferred value is tuned to land on
     the max on iPad Pro 11/13" (where there's plenty of room) while
     scaling down a few px on iPad mini. Floors keep the text safely
     readable if a future Split View permutation falls inside this
     media query but at a still-narrow effective viewport. */
  /* Brand title sizing v6 (Joshua 2026-05-18 night) — bumped up
     again now that the layout has tiles in a single row at the
     bottom and the left column has plenty of width to fill. The
     wordmark anchors the top-left visually opposite the chips +
     picture stack on the top-right; bigger type balances the
     visual weight of the picture across the page. */
  html.ipad .app:has(.reference-section) .brand-name {
    font-size: clamp(56px, 6vw, 84px);
    line-height: 1.05;
    white-space: nowrap;
  }
  html.ipad .app:has(.reference-section) .brand-name-en {
    font-size: clamp(20px, 2.2vw, 28px);
    margin-top: 10px;
  }
  html.ipad .app:has(.reference-section) .tagline-kicker {
    font-size: clamp(16px, 1.85vw, 22px);
    margin-top: 26px;
  }
  html.ipad .app:has(.reference-section) .tagline {
    font-size: clamp(15px, 1.7vw, 20px);
    margin-top: 10px;
  }

  /* ----- Home-page only: references move to top-right, small.

     Joshua-flagged 2026-05-17: the Bushu + Kana "reference" tiles
     are not first-class pillars. They were already styled as a
     secondary side-rail (dim bone surface, brass border, 38px
     glyph, 2:1 aspect) — but on iPad's wider canvas they were
     stacking BELOW the four main pillars and consuming space.

     Fix: use CSS Grid on .app to place the reference-section in
     the top-right corner of the page, paired horizontally with
     the header. The four main pillars span the full width below.
     Brass "Reference" divider is hidden (it's no longer separating
     anything visually). Reference tiles shrink to small chips.

     The :has(.reference-section) gate scopes this layout to the
     home page only — sub-pages without .reference-section keep the
     simpler tier-select layout. iPhone untouched (html.ipad gate). ----- */
  /* Home page layout v4 (Joshua 2026-05-18 late evening) — picture
     on the LEFT half, 2×2 hero tiles on the RIGHT, brand text +
     reference chips stacked above each side. The .app widens to fit
     the canvas (max-width 1500) so every iPad — mini, Air, Pro 11",
     Pro 13" — fills the screen edge-to-edge; tiles + picture both
     scale with viewport rather than getting stuck at a static size.

     Grid topology v6 (Joshua-directed 2026-05-18 night):
       row 1 (auto):    [ brand text ]    [ chips + divider ]
       row 2 (auto):    [    "      ]    [ geisha picture  ]
       row 3 (1fr):     [ 4-tile single row spanning both cols ]

     Header spans rows 1+2 on the left so the brand stack sits
     opposite the chip→picture stack on the right. Picture moved up
     to row 2 (directly under the chips) instead of filling the
     remaining canvas height — reads as a small atmospheric pair in
     the top-right corner. Row 3 hosts a single horizontal row of 4
     hero tiles spanning the full width, anchored to the bottom of
     its 1fr cell so the page reads "identity + atmosphere up top,
     primary actions along the bottom." iPhone is untouched. */
  html.ipad .app:has(.reference-section) {
    display: grid;
    grid-template-columns: 1fr auto;
    grid-template-rows: auto auto 1fr;
    grid-template-areas:
      "header   reference"
      "header   art"
      "main     main";
    column-gap: 36px;
    row-gap: 16px;
    align-items: start;
    max-width: 1500px;
  }
  html.ipad .app:has(.reference-section) > .header {
    grid-area: header;
    text-align: left;
    border-bottom: none;
    margin-bottom: 0;
    padding-bottom: 0;
  }
  /* Picture in row 2 (just under the chips). Width clamped so it
     stays atmospheric, not dominant. Right-aligned to keep the
     chips→picture pair visually unified on the right edge.
     Transparent PNG BG lets the iPad seigaiha pattern show through
     behind the figure. */
  html.ipad .app:has(.reference-section) > .ipad-home-art-side {
    grid-area: art;
    width: clamp(260px, 28vw, 360px);
    aspect-ratio: 2002 / 1694;
    background-image: url('../img/art/geisha-mahjong.png');
    background-size: contain;
    background-position: center;
    background-repeat: no-repeat;
    justify-self: end;
    align-self: start;
    pointer-events: none;
    position: relative;
  }
  /* Brass double-rule "horizon" anchored to the table's left-corner
     height inside the picture. Left endpoint sits at the .app's left
     padding edge (48px), right endpoint stops 24px short of the
     picture's left edge so the line approaches the picture without
     touching it. Vertical position via `top: 60%` follows the picture
     automatically across viewports — the line moves up/down with
     where the picture sits in the layout. Same 3px brass double
     stroke as the REFERENCE divider so the visual vocabulary stays
     consistent. */
  html.ipad .app:has(.reference-section) > .ipad-home-art-side::after {
    content: '';
    position: absolute;
    top: 60%;
    right: calc(100% + 24px);
    /* width = canvas content width - picture width - left padding (48)
       - small gap (24). Using min(100vw,1500px) caps it at the
       .app's max-width on hypothetical larger displays. 100% inside
       this calc evaluates to the .ipad-home-art-side's own width
       (the picture's clamped width). */
    width: calc(min(100vw, 1500px) - 120px - 100%);
    height: 0;
    border-top: 3px double var(--brass);
    opacity: 0.7;
    pointer-events: none;
  }
  /* Single row of 4 hero tiles spanning both columns at the bottom.
     `repeat(4, 1fr)` splits the full canvas width evenly across the
     tiles. Per-tile `max-width: 210px` (see rule below) keeps them
     from blowing up on Pro 13" and — equally important — leaves room
     for the in-tile labels above the bottom-nav on iPad mini's tight
     744px landscape height. `align-self: end` anchors the row to the
     bottom of its 1fr cell so vertical space above breathes between
     the picture and the buttons. */
  html.ipad .app:has(.reference-section) > .hero-grid {
    grid-area: main;
    grid-template-columns: repeat(4, 1fr);
    gap: 20px;
    margin: 0;
    max-width: none;
    width: 100%;
    align-self: end;
    margin-left: 0;
    margin-right: 0;
  }
  html.ipad .app:has(.reference-section) .hero-tile {
    width: 100%;
    max-width: 210px;
    justify-self: center;
    /* aspect-ratio: 1/1 inherited from html.ipad .hero-tile.
       Tightened from 260px → 210px on 2026-05-19 after iPad mini
       sim verification: the 244×244 tiles at the cap pushed labels
       past the 744px landscape height. 210 keeps labels comfortably
       above the bottom-nav on iPad mini and still reads cleanly on
       larger iPads. */
  }
  /* .reference-section sizes to its content width (the chip-pair),
     so the brass divider line below stops at the chips' right edge
     instead of stretching across the column. Right-aligned in the
     column so it sits over the picture below. */
  html.ipad .app:has(.reference-section) > .reference-section {
    grid-area: reference;
    margin: 0;
    align-self: start;
    padding-top: 4px;
    width: fit-content;
    justify-self: end;
  }
  /* Brass "Reference" divider — re-enabled on iPad home 2026-05-18
     (Joshua request), but moved BELOW the two chips instead of above
     so it reads as a caption underline rather than a section heading.
     .reference-section becomes flex column on iPad so we can use
     `order:` to swap grid (first) and divider (second) without
     reordering the DOM (which would break the iPhone layout where
     the divider sits ABOVE the chips). */
  html.ipad .app:has(.reference-section) > .reference-section {
    display: flex;
    flex-direction: column;
  }
  html.ipad .reference-section .reference-grid {
    order: 1;
  }
  html.ipad .reference-section .reference-divider {
    order: 2;
    display: flex;
    margin: 12px 6px 0;
  }
  /* Compact reference tiles: 2 side-by-side small chips, horizontally
     centered as a group inside the right column so they share an
     axis with the REFERENCE divider + geisha picture frame below.
     The default .reference-grid (display:grid, 1fr 1fr) left-aligned
     each fixed-width chip inside its 1fr cell, leaving the pair
     visibly shifted left of the picture's center. Flex with
     justify-content:center fixes the alignment. */
  html.ipad .reference-grid {
    display: flex;
    justify-content: center;
    gap: 10px;
  }
  html.ipad .reference-tile {
    aspect-ratio: auto;
    width: 130px;
    height: 56px;
  }
  html.ipad .reference-tile-face {
    padding: 8px 12px;
    gap: 10px;
  }
  html.ipad .reference-kanji { font-size: 28px; }
  html.ipad .reference-en { font-size: 13px; }
  html.ipad .reference-en-sub { font-size: 8px; }
}
