/* ═══════════════════════════════════════════
   FONTS — M PLUS Rounded 1c (OFL)
   Loaded via Google Fonts CDN (wght 400/500/700/800).
   No 600 weight in this family — use 500 for semibold roles.
   ═══════════════════════════════════════════ */

/* Material Symbols Rounded — UI icon helper */
.ms {
  font-family: 'Material Symbols Rounded';
  font-weight: normal;
  font-style: normal;
  font-size: 20px;
  line-height: 1;
  letter-spacing: normal;
  text-transform: none;
  display: inline-block;
  white-space: nowrap;
  word-wrap: normal;
  direction: ltr;
  -webkit-font-smoothing: antialiased;
  user-select: none;
}

/* ═══════════════════════════════════════════
   RESET & VARIABLES
   ═══════════════════════════════════════════ */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

:root {
  --bg: #f2f1ef;
  --surface: #ffffff;
  --surface2: #f9f9f7;
  --border: rgba(0,0,0,0.07);
  --text-primary: #1a1a1a;
  --text-secondary: #666;
  --text-tertiary: #999;
  /* READING TYPE SCALE — #79. Three steps, not a slider: 1.00 / 1.15 / 1.32.
     Set on documentElement by applyTextSize(); declared here so the default is
     correct before any JavaScript runs and a visitor never sees a reflow.
     It scales the surfaces a person READS — the modal's title, description,
     tags, file strip and Details panel — and deliberately NOT the app's chrome:
     nav, breadcrumbs and buttons are furniture, and growing them costs the
     reading area the space it was the point of the setting to give it. */
  --text-scale: 1;
  --nav-w: 252px;
  --radius-card: 20px;
  --shadow-card: 0 2px 16px rgba(0,0,0,0.06), 0 0 0 1px rgba(0,0,0,0.04);
  --shadow-card-hover: 0 10px 40px rgba(0,0,0,0.12), 0 0 0 1px rgba(0,0,0,0.06);
  --transition: 0.13s ease-out;

  /* ── Grid card background and size ────────────────────────────────────────
     Every illustration is a transparent PNG, so what sits behind it is a
     choice rather than a given. Both controls write ONLY these variables on
     :root, which is what makes them free at 1,960 cards and correct across
     infinite scroll — no per-card DOM work, and cards appended later inherit
     the current setting without being told about it.

     Card size is a THREE-WAY CHOICE, not a zoom stepper: there is no counter
     to accumulate, so re-pressing an option repaints the same values. The
     label scales GENTLER than the card (see --label-scale) because 0.75 × 12px
     is 9px, which is unreadable Japanese — and Guidelines/19 §3 makes the
     Qualifier part of the title rather than metadata about it, so the second
     label line is not the thing that gets sacrificed at small size. */
  --thumb-bg: #f5f4f2;
  --thumb-bg-image: none;
  --thumb-bg-size: auto;
  --thumb-bg-position: 0 0;
  --card-scale: 1;
  --label-scale: 1;
  /* Two breakpoints below pin an exact column COUNT rather than a minimum
     width (5 and 3), and a minmax() multiplier cannot reach them — the count
     is what scales there instead. Without these the control would silently do
     nothing on iPad and phone. */
  --card-cols-5: 5;
  --card-cols-3: 3;
  --font: "M PLUS Rounded 1c", "Hiragino Sans", "Yu Gothic UI", "Noto Sans JP", "Segoe UI", -apple-system, BlinkMacSystemFont, sans-serif;
  --hub: #7c5cbf;
  --hub-light: #f3f0fb;
  /* The app's interactive colour: links, hover and active states, the loading
     spinner, the active-filter dot. Referenced in twelve rules since the app
     was written and never defined until 2026-07-29, so all twelve were silently
     inert — `var(--accent)` with no fallback resolves to the initial value, so
     `background` went transparent and `color` just inherited.

     School & Learning blue, which is already this app's interactive colour in
     two places: the :focus-visible ring is the same hue, and
     .top-bar-link--web is this exact value — so the info-panel links now match
     the top-bar links instead of quietly differing. 5.29:1 on --surface and
     4.68:1 on --bg, both AA. Deliberately not the Sports red, which already
     means "Clear" on the colour bar and "external link" in the modal. */
  --accent: hsl(205, 62%, 40%);
  /* Two more names that were referenced and never defined, found by the same
     sweep as --accent. Aliases rather than new colours: --surface2 is already
     this app's subtle raised surface, used by .search-btn:hover,
     .section-picker-item:hover and .color-bar-clear:hover. --hover backs the
     hamburger's hover state and the tinted panels on the How to Search and
     Visit Irasutoya pages, which had been rendering with no background at all;
     --bg-secondary backs one more of those panels. */
  --hover: var(--surface2);
  --bg-secondary: var(--surface2);
  /* Red button — the exact recipe .modal-action-link uses for the Irasutoya
     link in the detail modal, promoted to tokens so the two cannot drift.
     Sports & Recreation / Red (h:5 s:65) at the 60% tint, with that family's
     `text` value on top, per Guidelines/15-Irasutoya-Color-System-v1.md.
     The modal's own rules still carry the literals; see the note there. */
  --btn-red:        hsl(5, 65%, 92%);
  --btn-red-hover:  hsl(5, 65%, 86%);
  --btn-red-border: hsl(5, 58%, 83%);
  --btn-red-text:   hsl(5, 70%, 27%);
}

body {
  font-family: var(--font);
  background: var(--bg);
  color: var(--text-primary);
  min-height: 100vh;
  -webkit-font-smoothing: antialiased;
}

/* ── Keyboard focus (a11y) — visible ring for Tab users only ── */
:focus-visible {
  outline: 3px solid hsl(205, 62%, 55%);
  outline-offset: 2px;
}
.ill-card:focus-visible, .big-card:focus-visible, .subcat-card:focus-visible {
  outline-offset: 0;
}
/* Dialog containers receive programmatic focus — no ring needed */
.modal-scene:focus, .info-panel:focus { outline: none; }

/* ═══════════════════════════════════════════
   NAV LOGO
   ═══════════════════════════════════════════ */
.nav-logo-wrap {
  padding: 22px 16px 16px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 6px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  cursor: pointer;
}
.nav-logo {
  width: 80%;
  height: auto;
  display: block;
}
.nav-logo-subtitle {
  font-size: 11px;
  font-weight: 500;
  color: var(--text-tertiary);
  text-align: center;
  letter-spacing: 0.1px;
  line-height: 1.4;
}
/* Sits directly under the subtitle, pulled up out of the wrapper's 8px gap so
   the two read as one block. Deliberately NOT faded: this is the one thing a
   first-time visitor should register, so it matches the subtitle's size and
   sits a step darker and heavier than it. */
.nav-logo-tagline {
  margin-top: -5px;
  font-size: 11px;
  font-weight: 600;
  color: var(--text-secondary);
  text-align: center;
  letter-spacing: 0.2px;
  line-height: 1.35;
}

/* ── Top bar ── */
.top-bar {
  position: sticky;
  top: 0;
  z-index: 50;
  background: rgba(242, 241, 239, 0.92);
  backdrop-filter: blur(20px) saturate(180%);
  -webkit-backdrop-filter: blur(20px) saturate(180%);
  border-bottom: 1px solid var(--border);
  padding: 0 14px;
  height: 48px;
  display: grid;
  /* Was `1fr auto 1fr` with the wordmark in the middle cell. With it gone the
     bar is two cells: links take the free space, controls sit hard right. */
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 8px;
}
.top-bar-left {
  display: flex;
  align-items: center;
  gap: 4px;
}
.top-bar-right {
  display: flex;
  align-items: center;
  gap: 8px;
  justify-content: flex-end;
  flex-shrink: 0;
}

/* ── Language picker ── */
.lang-flag-btn {
  width: 36px;
  height: 36px;
  border: none;
  background: none;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform 0.15s, opacity 0.15s;
  padding: 0;
}
.lang-flag-btn:hover { transform: scale(1.08); }
.lang-flag-btn:active { transform: scale(0.96); }
/* The flags are Irasutoya's own illustrations rather than emoji, so they sit on
   the page like everything else here — no chrome around them. */
.lang-flag-img {
  width: 30px;
  height: 30px;
  object-fit: contain;
  display: block;
  pointer-events: none;
}

/* ── Search icon button (mobile + iPad only) ── */
.search-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  border-radius: 50%;
  width: 36px; height: 36px;
  color: var(--text-secondary);
  cursor: pointer;
  font-family: var(--font);
  transition: background 0.15s;
  padding: 0;
}
.search-btn:hover { background: var(--surface2); }
.search-btn .ms { font-size: 20px; line-height: 1; }
/* Desktop: hide icon button, show input form instead */
@media (min-width: 1200px) { .search-btn { display: none; } }

/* ── Desktop search input (≥1200px) ── */
.search-bar-desktop {
  display: none;
  align-items: center;
  gap: 6px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
  padding: 9px 12px;
  cursor: text;
  transition: border-color 0.15s, box-shadow 0.15s;
}
@media (min-width: 1200px) { .search-bar-desktop { display: flex; } }
.search-bar-desktop:focus-within {
  border-color: #aaa;
  box-shadow: 0 0 0 2px rgba(0,0,0,0.06);
}
.search-bar-desktop .ms {
  font-size: 18px;
  color: var(--text-tertiary);
  flex-shrink: 0;
}
.search-bar-input {
  border: none;
  background: none;
  font-family: var(--font);
  font-size: 13px;
  color: var(--text-primary);
  outline: none;
  width: 165px;
  /* Fills the remaining width so a click anywhere in the bar lands on the
     input rather than on dead padding. The form's onclick covers the rest. */
  flex: 1;
  min-width: 0;
}
.search-bar-input::placeholder { color: var(--text-tertiary); }
.search-bar-input::-webkit-search-cancel-button { display: none; }
.search-bar-kbd {
  font-family: var(--font);
  font-size: 11px;
  background: #f0f0f0;
  border: 1px solid #ddd;
  border-radius: 4px;
  padding: 1px 5px;
  color: var(--text-tertiary);
  flex-shrink: 0;
}
/* The ⌘K hint STAYS while typing. Hiding it on focus removed a fixed-width
   element mid-interaction, so the bar visibly resized the moment you clicked
   into it. Dimmed instead: still legible as a hint, no longer competing with
   what is being typed, and the geometry never moves. */
.search-bar-desktop:focus-within .search-bar-kbd {
  opacity: 0.45;
  transition: opacity 0.15s;
}

/* ── Mobile/iPad search overlay ── */
.search-overlay {
  display: none;
  position: absolute;
  top: 0; left: 0; right: 0; bottom: 0;
  background: rgba(242, 241, 239, 0.97);
  backdrop-filter: blur(20px) saturate(180%);
  -webkit-backdrop-filter: blur(20px) saturate(180%);
  align-items: center;
  padding: 0 12px;
  gap: 8px;
  z-index: 60;
}
.search-overlay.open { display: flex; }
@media (min-width: 1200px) { .search-overlay { display: none !important; } }
.search-overlay .ms {
  font-size: 20px;
  color: var(--text-tertiary);
  flex-shrink: 0;
}
.search-overlay-input {
  flex: 1;
  border: none;
  background: none;
  font-family: var(--font);
  font-size: 15px;
  color: var(--text-primary);
  outline: none;
  min-width: 0;
}
.search-overlay-input::placeholder { color: var(--text-tertiary); }
.search-overlay-input::-webkit-search-cancel-button { display: none; }
.search-overlay-cancel {
  background: none;
  border: none;
  font-family: var(--font);
  font-size: 14px;
  color: var(--text-secondary);
  cursor: pointer;
  padding: 4px 6px;
  border-radius: 6px;
  white-space: nowrap;
  flex-shrink: 0;
}
.search-overlay-cancel:hover { color: var(--text-primary); background: var(--surface2); }

/* ── Search results page ── */
.search-results-wrap { padding: 28px 24px; }
/* Autocomplete list under the search input. Anchored to the form, which is
   position:relative, so it follows the input in both the desktop bar and the
   mobile overlay. */
.search-suggest {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  z-index: 1200;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: var(--shadow-card);
  overflow: hidden;
  max-height: 320px;
  overflow-y: auto;
}
.search-suggest[hidden] { display: none; }
.search-suggest-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 9px 14px;
  cursor: pointer;
  font-size: 14px;
  color: var(--text-primary);
}
.search-suggest-item:hover,
.search-suggest-item.selected { background: var(--surface2); }
.search-suggest-count {
  font-size: 12px;
  color: var(--text-tertiary);
  flex-shrink: 0;
}
.search-bar-desktop, .search-overlay form { position: relative; }

.search-corrected {
  margin: -4px 0 14px;
  font-size: 13px;
  color: var(--text-secondary);
}

.search-results-header {
  display: flex;
  align-items: baseline;
  gap: 12px;
  margin-bottom: 20px;
  flex-wrap: wrap;
}
.search-results-title {
  font-size: 22px;
  font-weight: 700;
  color: var(--text-primary);
  margin: 0;
}
.search-results-count {
  font-size: 14px;
  color: var(--text-tertiary);
}
/* Quieter than .search-corrected: that one reports what the search DID, this
   one explains a word. Same column, no alarm colour — it is information, not a
   problem. */
.search-term-note {
  max-width: 640px;
  margin: 4px 0 14px;
  padding: 10px 14px;
  border-left: 3px solid var(--border);
  background: var(--bg-secondary);
  border-radius: 0 8px 8px 0;
  font-size: 13px;
  line-height: 1.6;
  color: var(--text-secondary);
}
.search-no-results, .search-loading {
  text-align: center;
  padding: 80px 20px;
  color: var(--text-tertiary);
  font-size: 15px;
}
.search-no-results-img {
  width: 120px; height: 120px;
  object-fit: contain;
  display: block;
  margin: 0 auto 16px;
}
.search-no-results-text { font-size: 15px; color: var(--text-tertiary); }
@media (max-width: 1024px) {
  .search-results-wrap { padding: 20px 14px; }
  .search-results-title { font-size: 18px; }
}

/* ═══════════════════════════════════════════
   APP BODY: SIDEBAR + CONTENT
   ═══════════════════════════════════════════ */
.app-body {
  display: flex;
  align-items: flex-start;
  min-height: 100vh;
}

/* ═══════════════════════════════════════════
   NAVIGATION SIDEBAR (waterfall)
   ═══════════════════════════════════════════ */
.nav-sidebar {
  width: var(--nav-w);
  flex-shrink: 0;
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
  overflow-x: hidden;
  background: var(--surface);
  border-right: 1px solid var(--border);
  padding: 0 0 40px;
}

/* Sidebar scrollbar */
.nav-sidebar::-webkit-scrollbar { width: 4px; }
.nav-sidebar::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.12); border-radius: 2px; }

/* ── Shared row base ── */
.nav-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  cursor: pointer;
  transition: background 0.1s;
  white-space: nowrap;
  overflow: hidden;
}
.nav-row-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.nav-row-count { flex-shrink: 0; font-variant-numeric: tabular-nums; }

/* ── Domain rows — section header style ── */
.nav-domain-row {
  padding: 14px 14px 5px 14px;
  gap: 7px;
  user-select: none;
}
.nav-domain-row:hover { background: var(--d60); }
.nav-domain-row.ancestor { background: var(--d60); }
.nav-domain-row.ancestor .nav-domain-name { color: var(--dtext); opacity: 0.85; }

.nav-arrow {
  font-size: 8px;
  color: #ccc;
  flex-shrink: 0;
  transition: transform 0.18s ease;
}
.nav-arrow.open { transform: rotate(90deg); color: #999; }

/* The icon sits in a fixed square and is centred inside it, so every domain name
   starts on the same column whether its icon is an illustration or an emoji, and
   whatever that icon's aspect ratio. Sized up from 16px on 2026-07-31: at 16px
   these read as texture rather than as pictures, which defeats the point of
   replacing the emoji. font-size carries the emoji fallback iconHtml still
   supports — kept in step with the box so a half-migrated sidebar stays aligned. */
.nav-domain-icon {
  font-size: 22px; line-height: 1; flex-shrink: 0;
  width: 24px; height: 24px;
  display: flex; align-items: center; justify-content: center;
}
.nav-domain-icon img {
  width: 24px; height: 24px; object-fit: contain; display: block;
}
/* The modal breadcrumb is a different job — inline with 13px text, where 24px
   would tower over the line. It keeps the original optical-box sizing. */
.modal-taxon-domain img {
  width: 16px; height: 16px; object-fit: contain;
  vertical-align: -3px; display: inline-block;
}
.nav-domain-name {
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.1px;
  color: var(--text-tertiary);
  white-space: normal;
  text-overflow: clip;
  line-height: 1.3;
}
.nav-domain-count { font-size: 12px; color: #ccc; }

/* ── Category items ── */
.nav-cats { overflow: hidden; max-height: 0; transition: max-height 0.25s ease; }
.nav-cats.open { max-height: 1200px; }

.nav-cat-item {
  padding: 10px 14px 10px 18px;
  gap: 0;
}
.nav-cat-item:hover { background: var(--d60); }
/* Category is the selected leaf (no subcategory active) */
.nav-cat-item.active { background: var(--d100); }
.nav-cat-item.active .nav-row-name { color: var(--dtext); font-weight: 500; }
.nav-cat-item.active .nav-row-count { color: var(--dtext); opacity: 0.55; }
/* Category is an ancestor (subcategory below is the selection) */
.nav-cat-item.ancestor { background: var(--d80); }
.nav-cat-item.ancestor .nav-row-name { color: var(--dtext); font-weight: 500; }
.nav-cat-item.ancestor .nav-row-count { color: var(--dtext); opacity: 0.55; }

.nav-cat-item .nav-row-name {
  font-size: 15px;
  font-weight: 500;
  color: var(--text-primary);
}
.nav-cat-item .nav-row-count { font-size: 13px; color: #bbb; }

.nav-cat-arrow {
  font-size: 8px;
  color: #ddd;
  flex-shrink: 0;
  margin-right: 6px;
  transition: transform 0.18s ease;
}
.nav-cat-arrow.open { transform: rotate(90deg); color: #aaa; }
.nav-cat-item.active .nav-cat-arrow   { color: var(--dtext); opacity: 0.5; }
.nav-cat-item.ancestor .nav-cat-arrow { color: var(--dtext); opacity: 0.5; }

/* ── Subcategory items ── */
.nav-subcats { overflow: hidden; max-height: 0; transition: max-height 0.22s ease; }
.nav-subcats.open { max-height: 2000px; }

.nav-subcat-item {
  padding: 9px 14px 9px 28px;
}
.nav-subcat-item:hover { background: var(--d60); }
.nav-subcat-item.active { background: var(--d100); }
.nav-subcat-item.active .nav-row-name { color: var(--dtext); font-weight: 500; }
.nav-subcat-item.active .nav-row-count { color: var(--dtext); opacity: 0.55; }

.nav-subcat-item .nav-row-name {
  font-size: 14px;
  font-weight: 400;
  color: var(--text-secondary);
}
.nav-subcat-item .nav-row-count { font-size: 12px; color: #ccc; }

/* ═══════════════════════════════════════════
   CONTENT AREA
   ═══════════════════════════════════════════ */
.content-area {
  flex: 1;
  min-width: 0;
}
#main { padding-bottom: 48px; } /* clearance for fixed footer */

/* ── Footer — fixed to bottom of content area ── */
.site-footer {
  position: fixed;
  bottom: 0;
  left: var(--nav-w);
  right: 0;
  z-index: 40;
  padding: 11px 24px;
  border-top: 1px solid var(--border);
  background: rgba(242,241,239,0.92);
  backdrop-filter: blur(20px) saturate(180%);
  -webkit-backdrop-filter: blur(20px) saturate(180%);
  text-align: center;
  font-size: 11.5px;
  color: var(--text-tertiary);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  flex-wrap: wrap;
}
/* ── Offline indicator ── */
.offline-banner {
  display: none;
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 400;
  background: #1a1a1a;
  color: #fff;
  text-align: center;
  font-size: 13px;
  padding: 8px 16px;
}
.offline-banner.visible { display: block; }

/* ── PWA install banner (Android Chrome only) ── */
.install-banner {
  display: none;
  position: fixed;
  bottom: 0;
  left: 0; right: 0;
  z-index: 300;
  background: var(--surface);
  border-top: 1px solid var(--border);
  padding: 12px 16px;
  align-items: center;
  gap: 12px;
  box-shadow: 0 -4px 16px rgba(0,0,0,0.08);
}
.install-banner.visible { display: flex; }
.install-banner-icon {
  width: 40px; height: 40px;
  border-radius: 10px;
  flex-shrink: 0;
  object-fit: contain;
}
.install-banner-text { flex: 1; min-width: 0; }
.install-banner-title { font-size: 14px; font-weight: 600; color: var(--text-primary); }
.install-banner-sub   { font-size: 12px; color: var(--text-tertiary); margin-top: 1px; }
.install-banner-btn {
  flex-shrink: 0;
  padding: 7px 16px;
  border-radius: 20px;
  border: none;
  background: var(--text-primary);
  color: var(--surface);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}
.install-banner-dismiss {
  flex-shrink: 0;
  background: none; border: none;
  color: var(--text-tertiary);
  font-size: 20px;
  cursor: pointer;
  padding: 0 4px;
  line-height: 1;
}

.site-footer-sep { opacity: 0.4; }
.site-footer a {
  color: var(--text-tertiary);
  text-decoration: none;
  border-bottom: 1px solid currentColor;
  opacity: 0.8;
}
.site-footer a:hover { opacity: 1; }

/* ═══════════════════════════════════════════
   PAGE NAV TITLE (breadcrumb-as-title)
   ═══════════════════════════════════════════ */
.page-nav-title {
  display: flex;
  align-items: baseline;
  gap: 7px;
  flex-wrap: wrap;
  margin-bottom: 6px;
}
.page-nav-home {
  font-size: 20px;
  cursor: pointer;
  line-height: 1;
  opacity: 0.75;
  transition: opacity 0.15s;
  user-select: none;
}
.page-nav-home:hover { opacity: 1; }
.page-nav-sep {
  color: #ccc;
  font-size: 22px;
  /* 400, not 300 — the loaded webfont has no weight below 400, so this always
     painted at 400 anyway. See the note on .cls-section-title. */
  font-weight: 400;
  line-height: 1;
  user-select: none;
}
.page-nav-ancestor {
  font-size: 20px;
  font-weight: 500;
  color: var(--text-secondary);
  cursor: pointer;
  transition: color 0.15s;
  line-height: 1.2;
}
.page-nav-ancestor:hover { color: var(--accent); }
/* Same size as the ancestor crumbs — it sits inline in the trail now rather
   than on its own title line, so it reads as the last step of the path. Colour
   and weight still mark it as where you are. */
.page-nav-current {
  font-size: 20px;
  font-weight: 700;
  color: var(--text-primary);
  line-height: 1.2;
}
/* Current title + count share a flex row */
.page-nav-current-wrap {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  width: 100%;
  margin-top: 4px;
  gap: 12px;
}
.page-nav-count {
  font-size: 13px;
  font-weight: 400;
  color: var(--text-tertiary);
  white-space: nowrap;
  flex-shrink: 0;
}

/* ── Color picker button ──────────────────────────────────────────────────── */
.color-picker-btn {
  width: 32px; height: 32px;
  border-radius: 8px;
  border: none;
  background: none;
  cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  padding: 0;
  flex-shrink: 0;
  font-size: 18px;
  position: relative;
  transition: transform 0.15s;
}
.color-picker-btn:hover { transform: scale(1.15); }
.section-color-btn { width: 44px !important; height: 44px !important; transform: none; }
.section-color-btn:hover { transform: scale(1.1); }
.color-picker-btn .ms { font-size: 20px; line-height: 1; color: var(--text-secondary); }
/* Small accent dot when a color filter is active */
.color-picker-btn.has-filter::after {
  content: '';
  position: absolute;
  top: 2px; right: 2px;
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--accent);
  border: 1.5px solid var(--surface);
}

/* ── Colour filter bar ────────────────────────────────────────────────────────
   An inline bar in the page flow, not a floating panel: it stays open while you
   toggle swatches and while you navigate between subcategories, which is the
   whole point — colour filtering is exploratory and you rarely want just one.
   Swatches are grouped by hue with a hairline between groups; the row wraps as
   the viewport narrows, so no width is hardcoded and nothing is ever clipped. */
/* ── Facet reference page (#35) ──────────────────────────────────────────────
   A reference, not a filter: every facet and every value with its catalog count,
   each value a button that runs a search. Lives inside the info overlay, so it
   inherits that panel's scrolling and padding and needs no layout of its own. */
.facet-ref { margin-top: 4px; }
.facet-ref-group { margin: 0 0 22px; }
.facet-ref-group h3 {
  font-size: 14px;
  margin: 0 0 8px;
  display: flex;
  align-items: center;
  gap: 8px;
}
/* The axis (`glyph`) is not a filter dimension — its illustrations carry no
   subject to combine with — so it is labelled as a kind of thing instead. */
.facet-ref-axis {
  font-size: 10px;
  font-weight: 400;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--text-tertiary);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 1px 7px;
}
.facet-ref-values {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}
.facet-ref-chip {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  font: inherit;
  font-size: 12px;
  line-height: 1.3;
  color: inherit;
  background: var(--surface2);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 4px 10px;
  cursor: pointer;
}
.facet-ref-chip:hover { background: var(--hover); border-color: var(--text-tertiary); }
.facet-ref-chip-count { font-size: 10px; color: var(--text-tertiary); }
/* A general value labels GENERIC content — it is not a parent of the specific
   values beside it (P(animal|dog) is 6.2%), so it is set apart rather than made
   to look like a heading. Issue #45. */
.facet-ref-chip--general { background: var(--surface); border-style: dashed; }
.facet-ref-divider {
  width: 1px;
  align-self: stretch;
  min-height: 18px;
  background: var(--border);
  margin: 0 4px;
}
.facet-note { font-size: 12px; color: var(--text-tertiary); }
.facet-ref-link {
  font: inherit;
  color: inherit;
  background: var(--surface2);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 8px 14px;
  cursor: pointer;
}
.facet-ref-link:hover { background: var(--hover); border-color: var(--text-tertiary); }

.color-bar {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 10px 14px;
  /* Sits between a section header and its grid; the grid's own 18px top margin
     supplies the gap below, so this only needs the gap above. */
  margin: 18px 0 0;
  /* Full width of the section. The swatch groups below stretch to fill it, so
     there is no dead strip on the right and nothing to overflow. */
}
.color-bar[hidden] { display: none; }
/* The label's slot, kept out of the swatch grid so it never lands mid-row among
   the colours. Holds the label AND the Clear button, stacked in one grid cell
   and swapped with `visibility` rather than `display`: both keep contributing
   to the cell's intrinsic width, so the slot is always as wide as the wider of
   the two and selecting a colour never nudges the swatches sideways. */
.color-bar-aside {
  display: grid;
  align-items: center;
  justify-items: stretch;
  flex-shrink: 0;
  min-height: 24px;   /* same as a swatch, so the swap doesn't shift the row */
}
.color-bar-label,
.color-bar-clear { grid-area: 1 / 1; }
/* Not [hidden]/display:none — that would collapse the cell, which is the whole
   thing this layout exists to prevent. */
.color-bar-off { visibility: hidden; }
.color-bar-label {
  font-size: 13px; font-weight: 600;
  color: var(--text-secondary);
  white-space: nowrap;
  line-height: 24px;   /* matches swatch height, so it sits on the first row */
}
/* Shaped to match .modal-action-btn — same radius, border, weight and type
   size, just a little less vertical padding so it fits the 24px swatch row. */
.color-bar-clear {
  display: flex; align-items: center; justify-content: center; gap: 5px;
  font-size: 12px; font-weight: 500;
  color: var(--btn-red-text);
  background: var(--btn-red);
  border: 1px solid var(--btn-red-border);
  border-radius: 10px;
  cursor: pointer;
  font-family: var(--font);
  padding: 4px 13px;
  line-height: 16px;
  white-space: nowrap;
  transition: background 0.18s, border-color 0.18s;
}
.color-bar-clear:hover { background: var(--btn-red-hover); }
.color-bar-clear .ms { font-size: 15px; line-height: 1; }

/* Swatch groups sit in a grid with a fixed column count per range.

   A grid rather than wrapping flex, because the hairline dividers need to know
   which group starts a row and no selector can tell that when the break point
   moves freely — nth-child can, once the column count is fixed.

   `1fr` rather than `max-content`, which was the earlier mistake: max-content
   columns cannot shrink, so between two breakpoints the row kept its natural
   width and spilled out of the card. Equal fractional columns fill the bar
   exactly and rows always align, including a short final row.

   The counts below were measured, not guessed, against the real layout budget:
   viewport minus --nav-w (252/260/268/290 by range) minus .page-wrap's
   percentage padding (7%, or 4% once the sidebar becomes a drawer). One result
   is worth knowing: **available width is not monotonic**. A 1024px window has
   ~912px for this bar while a 1025px window has ~628px, because the sidebar
   collapses and the padding drops from 7% to 4%. The narrowest case in the
   whole app is a small laptop around 1025px, not a phone — which is why
   breakpoints chosen by eye kept overflowing there. Each count below is
   checked against the narrowest width in its own range. */
.color-bar-swatches {
  display: grid;
  grid-template-columns: repeat(10, 1fr);
  align-items: center;
  row-gap: 10px;
  flex: 1;
  min-width: 0;
}
.color-bar-group {
  display: flex;
  align-items: stretch;          /* chips fill the group's height */
  gap: 3px;
  --group-pad: 10px;             /* one source for the divider inset */
  padding: 0 var(--group-pad);
  border-left: 1px solid var(--border);
}
/* The merged skins+neutrals group is 7 swatches (184px) against a hue group's
   76px, so it cannot sit in an equal 1fr column. Spanning two keeps the columns
   equal AND keeps the nth-child maths below valid: hue groups 1-8 occupy slots
   1-8 and this one occupies slot 9, so group index still equals slot index. */
.color-bar-group.misc { grid-column: span 2; }

/* No divider at the start of a row. Each range below restores the previous
   range's rule and clears its own — the step must match the column count. */
.color-bar-swatches > .color-bar-group:nth-child(10n + 1) {
  padding-left: 0; border-left: none;
}
/* Rectangle chips, not circles.
   A circle has one size variable and a fixed footprint, so any column wider
   than the circle it holds becomes dead space — which is what left the ragged
   gaps when the bar was resized. A chip has width and height: it stretches to
   fill its group, the group fills its column, and the row reaches the divider
   at every width. It is also a much larger tap target.

   It collapses the breakpoint arithmetic too. A 3-chip group needs 3×minChip;
   the 7-chip misc group across two columns needs 7×minChip over 2 — the same
   per-column minimum of 68px. One number now governs every range, where the
   circles needed 204px for misc against 76px for a hue group. Chip widths come
   out within a pixel or two of each other across groups as a result. */
.color-picker-swatch {
  flex: 1 1 0;                   /* every chip in a group shares it equally */
  min-width: 0;
  height: 26px;
  border-radius: 4px;
  border: 2px solid transparent;
  cursor: pointer;
  transition: transform 0.1s;
  box-shadow: 0 1px 2px rgba(0,0,0,0.14);
  padding: 0;
}
/* The white/off-white chip is nearly the same value as --surface, so without a
   hairline it reads as an empty slot. Inset ring rather than `border`, since
   the border is what .active repurposes for its selected state. Declared before
   .active so that rule's box-shadow still wins at equal specificity. */
.color-picker-swatch[data-color-id="white"] {
  box-shadow: inset 0 0 0 1px rgba(0,0,0,0.16), 0 1px 2px rgba(0,0,0,0.14);
}
.color-picker-swatch:hover { transform: translateY(-2px); }
/* Colours absent from the current view. grayscale() rather than a flat grey fill
   so the swatch keeps its own lightness — a pale yellow and a dark navy stay
   distinguishable while both read as off, which a single grey would destroy.
   Opacity does the rest of the work of saying "not available". */
.color-picker-swatch.unavailable {
  filter: grayscale(1);
  opacity: 0.28;
  cursor: default;
}
.color-picker-swatch.unavailable:hover { transform: none; }
.color-picker-swatch.active {
  border-color: var(--surface);
  box-shadow: 0 0 0 2px var(--text-primary);
}
/* Three ranges, all governed by the same 68px-per-column minimum. Chips made
   that possible: with circles the misc group needed 204px against a hue
   group's 76px, which forced four ranges and put ten-across out of reach below
   1700px. Above 1399px the base rule's 10 columns apply — one row. */

/* 621–1399px → 5 across (2 rows). Binding case is still 1025px, the narrowest
   the bar ever gets — 496px of grid over 5 columns is 99px each, comfortably
   past the 68px floor. */
@media (max-width: 1399px) {
  .color-bar-swatches { grid-template-columns: repeat(5, 1fr); }
  .color-bar-swatches > .color-bar-group:nth-child(10n + 1) {
    padding-left: var(--group-pad); border-left: 1px solid var(--border);
  }
  .color-bar-swatches > .color-bar-group:nth-child(5n + 1) {
    padding-left: 0; border-left: none;
  }
}
/* ≤620px → the label stacks above, and the grid goes 4 across: the eight hues
   in two rows of four, the merged group taking the whole third row. A
   two-column span would straddle a row boundary at this count, so it spans the
   full row instead — which also gives the misc chips the most room they get
   anywhere, on the screen where fingers need it most. */
@media (max-width: 620px) {
  .color-bar { flex-direction: column; align-items: stretch; gap: 8px; }
  .color-bar-label { line-height: 1.4; }
  .color-bar-swatches { grid-template-columns: repeat(4, 1fr); }
  .color-bar-group.misc { grid-column: 1 / -1; }
  .color-bar-swatches > .color-bar-group:nth-child(5n + 1) {
    padding-left: var(--group-pad); border-left: 1px solid var(--border);
  }
  .color-bar-swatches > .color-bar-group:nth-child(4n + 1) {
    padding-left: 0; border-left: none;
  }
  /* It leads its own row, so it never carries a divider. */
  .color-bar-swatches > .color-bar-group.misc {
    padding-left: 0; border-left: none;
  }
  .color-picker-swatch { height: 30px; }   /* fatter tap target on a phone */
}
/* Chrome trims where space is tight. Placed after the range rules above, but
   `.color-bar-group` is less specific than their nth-child selectors, so the
   row-leading overrides still win. */
@media (max-width: 1024px) {
  .color-bar { gap: 12px; padding: 10px 12px; }
  .color-bar-group { --group-pad: 8px; }
  /* No chip size here: a chip's width comes from its column and its height is
     set once, then raised at 620px for touch. Fixing a width would reintroduce
     exactly the dead space chips exist to remove. */
}

/* ── Grid title row — removed 2026-07-29 ──────────────────────────────────────
   The subcategory name moved into the breadcrumb as its final crumb and the
   subcategory-wide total was dropped, so .grid-title-row, .grid-title-left,
   .grid-subcat-name and .grid-title-count went with it. .grid-title-chevron and
   .grid-section-btn outlived it as unreachable rules — a chevron for a row that
   no longer exists, and a fossil of the design where the section *name* opened
   the picker instead of the hamburger — and were deleted the same day. */

/* ── Section picker dropdown ──────────────────────────────────────────────── */
.cls-section-header {
  display: flex;
  align-items: center;
  gap: 10px;
  position: relative;
}
.section-hamburger-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; flex-shrink: 0;
  background: none; border: none; cursor: pointer;
  border-radius: 8px; color: var(--text-tertiary);
  transition: background 0.12s, color 0.12s;
}
.section-hamburger-btn:hover { background: var(--hover); color: var(--text-primary); }
.section-picker-dropdown {
  display: none;
  position: absolute;
  top: calc(100% + 4px); left: 0;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: 0 6px 24px rgba(0,0,0,0.14);
  z-index: 100;
  min-width: 180px;
  padding: 6px;
}
.section-picker-dropdown.open { display: block; }
.section-picker-item {
  display: flex; align-items: center;
  justify-content: space-between;
  padding: 8px 10px; border-radius: 8px;
  cursor: pointer; font-size: 14px;
  font-family: var(--font);
  background: none; border: none; width: 100%;
  text-align: left; color: var(--text-primary); gap: 8px;
}
.section-picker-item:hover { background: var(--surface2); }
/* Selected row, matching .nav-subcat-item.active in the sidebar — a filled row
   in the domain's own colour rather than bold blue text, so "where I am" looks
   the same in both navigation surfaces.
   The fallbacks matter: --d100/--dtext only exist under [data-domain], which the
   grid view now sets, but this dropdown also renders in views that have no single
   domain (search results). There it degrades to a neutral filled row rather than
   an invisible one. */
.section-picker-item.active {
  background: var(--d100, var(--surface2));
  color: var(--dtext, var(--accent));
  font-weight: 500;
}
.section-picker-item.active:hover { background: var(--d100, var(--surface2)); }
.section-picker-item.active .section-picker-item-count {
  color: var(--dtext, var(--text-tertiary));
  opacity: 0.55;
}
.section-picker-item-count { font-size: 12px; color: var(--text-tertiary); }

/* ═══════════════════════════════════════════
   PAGE LAYOUT
   ═══════════════════════════════════════════ */
.page-wrap {
  padding: 18px 7% 60px;
}


.page-title {
  font-size: 28px;
  font-weight: 700;
  letter-spacing: -0.6px;
  color: var(--text-primary);
  margin-bottom: 4px;
  line-height: 1.15;
}

.page-subtitle {
  font-size: 14px;
  color: var(--text-secondary);
  margin-bottom: 20px;
}

/* ═══════════════════════════════════════════
   2-COLUMN CARD GRID
   ═══════════════════════════════════════════ */
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
  gap: 14px;
}

/* ── Responsive breakpoints ── */

/* Ultrawide (≥ 1800px) — expand sidebar, bigger ill cells */
@media (min-width: 1800px) {
  :root { --nav-w: 290px; }
  .ill-grid, .cls-section-grid { grid-template-columns: repeat(auto-fill, minmax(calc(204px * var(--card-scale)), 1fr)); }
  .page-wrap { padding: 28px 6.5% 80px; }
}

/* Large desktop (≥ 1440px) */
@media (min-width: 1440px) and (max-width: 1799px) {
  :root { --nav-w: 268px; }
  .ill-grid, .cls-section-grid { grid-template-columns: repeat(auto-fill, minmax(calc(192px * var(--card-scale)), 1fr)); }
}

/* Small desktop / iPad landscape (1025px – 1199px) */
@media (max-width: 1199px) and (min-width: 1025px) {
  :root { --nav-w: 260px; }
  .ill-grid, .cls-section-grid { grid-template-columns: repeat(var(--card-cols-5), 1fr); }
  .card-grid { grid-template-columns: repeat(2, 1fr); }
  /* Search: icon-only (same as mobile) */
  .search-btn {
    width: 36px; height: 36px; border-radius: 50%;
    border: none !important; background: none !important; padding: 0 !important;
    font-size: 18px;
  }
  .search-btn-text { display: none; }
  /* iPad nav menu */
  .ipad-nav-wrap { display: flex; align-items: center; }
  .top-bar-link--secondary { display: none !important; }
  .top-bar-sep--secondary  { display: none !important; }
}

/* ── Mobile menu dropdown — hidden on desktop ── */
.mobile-menu-wrap {
  display: none;
  position: relative;
  flex-shrink: 0;
}
.mobile-menu-btn {
  display: flex;
  align-items: center;
  height: 32px;
  padding: 0 10px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: none;
  color: var(--text-secondary);
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  white-space: nowrap;
}
.mobile-menu-btn:active { background: var(--surface2); }
.mobile-menu-dropdown {
  display: none;
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  min-width: 220px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.12);
  padding: 6px;
  z-index: 500;
}
.mobile-menu-dropdown.open { display: block; }
.mobile-menu-item {
  display: block;
  width: 100%;
  text-align: left;
  padding: 10px 12px;
  border: none;
  background: none;
  border-radius: 8px;
  font-size: 14px;
  color: var(--text-secondary);
  cursor: pointer;
}
.mobile-menu-item:active { background: var(--surface2); }
.mobile-menu-item--web    { color: hsl(205,62%,40%); font-weight: 600; }
.mobile-menu-item--nature { color: hsl(128,42%,32%); font-weight: 600; }
.mobile-menu-item--sports { color: hsl(25,70%,40%);  font-weight: 600; }
.mobile-menu-item--lang   { color: var(--text-secondary); font-size: 13px; }
.mobile-menu-sep {
  height: 1px;
  background: var(--border);
  margin: 4px 6px;
}

/* Hamburger button — hidden on desktop, shown via media query below */
.hamburger-btn {
  display: none;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border: none;
  background: none;
  color: var(--text-primary);
  cursor: pointer;
  padding: 0;
  flex-shrink: 0;
  border-radius: 8px;
  transition: background 0.15s;
}
.hamburger-btn:hover { background: var(--surface2); }
.hamburger-btn svg { display: block; }
.hamburger-btn .ms { font-size: 22px; line-height: 1; }

/* Mobile + Tablet (≤ 1024px) — drawer nav, hamburger + menu button */
@media (max-width: 1024px) {
  /* Footer flows at bottom of page instead of fixed overlay */
  .site-footer { position: static; }
  #main { padding-bottom: 0; }
  /* Sidebar becomes a slide-in drawer */
  .nav-sidebar {
    position: fixed;
    top: 0; left: 0;
    height: 100dvh;
    z-index: 200;
    transform: translateX(-100%);
    transition: transform 0.28s cubic-bezier(0.4,0,0.2,1);
    box-shadow: 4px 0 24px rgba(0,0,0,0.15);
    width: 280px !important;
  }
  .nav-sidebar.drawer-open { transform: translateX(0); }

  /* Overlay behind the drawer */
  .nav-drawer-overlay {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 199;
    background: rgba(0,0,0,0.35);
    backdrop-filter: blur(2px);
  }
  .nav-drawer-overlay.visible { display: block; }

  /* Show hamburger + menu btn, hide the top-bar link list */
  .hamburger-btn { display: flex; }
  .mobile-menu-wrap { display: block; }
  .top-bar-links { display: none; }

  /* Hide ⌘K shortcut hint on touch devices */
  .search-btn kbd { display: none; }

  /* Grids */
  .card-grid { grid-template-columns: 1fr; }
  .ill-label { padding: 4px 5px 5px; }
  .ill-label-text, .ill-label-base, .ill-label-qual { font-size: var(--label-font-sm, 11px); }
  .page-wrap { padding: 14px 4% 60px; }

  /* Modal full-screen on mobile */
  .modal-overlay { padding: 0; align-items: flex-end; }
  .modal-scene {
    max-width: 100% !important;
    width: 100% !important;
    max-height: 92dvh;
    border-radius: 20px 20px 0 0;
  }
}


/* ═══════════════════════════════════════════
   DOMAIN / CATEGORY CARDS (horizontal layout)
   ═══════════════════════════════════════════ */
.big-card {
  background: var(--surface);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  overflow: hidden;
  cursor: pointer;
  display: flex;
  flex-direction: row;
  align-items: stretch;
  min-height: 148px;
  transition: transform var(--transition);
}
.big-card:hover {
  transform: scale(1.014) translateY(-2px);
  box-shadow: var(--shadow-card-hover);
}
.big-card:active { transform: scale(0.99); }

/* Left: square illustration — neutral gray matching modal */
.card-img-side {
  width: 148px;
  flex-shrink: 0;
  position: relative;
  overflow: hidden;
  background: #f5f4f2;
}
/* Domain color accent: left border on all card levels */
.big-card[data-domain],
.subcat-card[data-domain] {
  border-left: 10px solid var(--d100);
}
.card-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  padding: 14px;
}

/* Right: title + subcategory names */
.card-content {
  flex: 1;
  min-width: 0;
  padding: 18px 18px 16px;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.card-title-row {
  display: flex;
  align-items: flex-start;
  gap: 10px;
}

.card-icon {
  font-size: 32px;
  line-height: 1;
  flex-shrink: 0;
  margin-top: 1px;
}

.card-title-group { flex: 1; min-width: 0; }

.card-title {
  font-size: 19px;
  font-weight: 700;
  letter-spacing: -0.4px;
  line-height: 1.2;
  color: var(--text-primary);
}

.card-count {
  font-size: 13px;
  color: var(--text-tertiary);
  margin-top: 3px;
  font-weight: 500;
}
.card-cat-count {
  font-size: 13px;
  color: var(--text-tertiary);
  margin-top: 1px;
  font-weight: 400;
}

.card-subcats {
  font-size: 12px;
  color: var(--text-tertiary);
  line-height: 1.8;
  margin-top: 10px;
}
.card-subcats .card-subcat-line {
  display: block;
}

/* ═══════════════════════════════════════════
   SUBCATEGORY CARDS (category overview page)
   ═══════════════════════════════════════════ */
.subcat-card {
  background: var(--surface);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  overflow: hidden;
  cursor: pointer;
  display: flex;
  flex-direction: row;
  align-items: stretch;
  min-height: 130px;
  transition: transform var(--transition);
}
.subcat-card:hover {
  transform: scale(1.014) translateY(-2px);
  box-shadow: var(--shadow-card-hover);
}
.subcat-card:active { transform: scale(0.99); }

.subcat-card-img-side {
  width: 130px;
  flex-shrink: 0;
  position: relative;
  background: #f5f4f2;
  overflow: hidden;
}
.subcat-card-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  padding: 12px;
}

.subcat-card-content {
  flex: 1;
  min-width: 0;
  padding: 16px 16px 14px;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.subcat-card-name {
  font-size: 17px;
  font-weight: 700;
  letter-spacing: -0.3px;
  color: var(--text-primary);
  line-height: 1.25;
  margin-bottom: 6px;
}

.subcat-card-count {
  font-size: 12px;
  color: var(--text-tertiary);
  font-weight: 500;
}

/* ═══════════════════════════════════════════
   EXPANDED DOMAIN VIEW (single-page hierarchy)
   ═══════════════════════════════════════════ */
.domain-exp-list {
  display: flex;
  flex-direction: column;
  gap: 0;
}

/* Each category section: header + subcat card grid stacked vertically */
.domain-exp-cat {
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding: 28px 0;
  border-bottom: 1px solid var(--border);
}
.domain-exp-cat:last-child { border-bottom: none; }

/* Category header: image+title on top row, desc full width below */
.domain-exp-cat-header {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.domain-exp-cat-header-top {
  display: flex;
  align-items: center;
  gap: 20px;
}

.domain-exp-cat-img {
  width: 100px;
  height: 100px;
  flex-shrink: 0;
  border-radius: 14px;
  overflow: hidden;
  background: #f5f4f2;
  position: relative;
}
.domain-exp-cat-img img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  padding: 10px;
}

.domain-exp-cat-title-row {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.domain-exp-cat-title {
  font-size: 26px;
  font-weight: 700;
  color: var(--text-primary);
  line-height: 1.2;
}

.domain-exp-cat-count {
  font-size: 13px;
  font-weight: 400;
  color: var(--text-tertiary);
  white-space: nowrap;
}

.domain-exp-cat-desc {
  font-size: 15px;
  font-weight: 400;
  color: var(--text-secondary);
  line-height: 1.6;
}

/* ═══════════════════════════════════════════
   ILLUSTRATION GRID (leaf level)
   ═══════════════════════════════════════════ */
.grid-view {
  width: 100%;
}

.subcat-sidebar {
  display: none;
  width: 190px;
  flex-shrink: 0;
  position: sticky;
  top: 72px;
  max-height: calc(100vh - 88px);
  overflow-y: auto;
}

.subcat-sidebar::-webkit-scrollbar { width: 4px; }
.subcat-sidebar::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.12); border-radius: 2px; }

.subcat-sidebar-section {
  background: var(--surface);
  border-radius: 16px;
  box-shadow: var(--shadow-card);
  overflow: hidden;
}

.subcat-sidebar-title {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.1px;
  color: var(--text-tertiary);
  padding: 12px 13px 6px;
}

.subcat-sidebar-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 13px;
  font-size: 14px;
  font-weight: 500;
  color: var(--text-secondary);
  cursor: pointer;
  transition: background 0.12s, color 0.12s;
  border-bottom: 1px solid var(--border);
}
.subcat-sidebar-item:last-child { border-bottom: none; }
.subcat-sidebar-item:hover { background: var(--surface2); color: var(--text-primary); }
.subcat-sidebar-item.active { background: #0066cc; color: white; font-weight: 500; }
.subcat-sidebar-item.active .ss-count { color: rgba(255,255,255,0.6); }

.ss-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ss-count { font-size: 11px; color: #bbb; flex-shrink: 0; margin-left: 6px; }

.ill-grid-wrap { flex: 1; min-width: 0; }

.ill-count {
  font-size: 13px;
  color: var(--text-tertiary);
  margin-bottom: 12px;
  font-weight: 500;
}

.ill-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(calc(180px * var(--card-scale)), 1fr));
  gap: 10px;
}
/* Section headings inside the SEARCH grid (#35). `grid-column: 1 / -1` is
   load-bearing, not cosmetic: without it a heading is placed as an ordinary
   grid cell and sits in the first column beside three illustrations, which
   reads as a broken card rather than a heading. First heading has no top
   margin — the results heading is already directly above it. */
.search-section-head {
  grid-column: 1 / -1;
  margin: 34px 0 2px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--border);
}
.search-section-head:first-child { margin-top: 6px; }
.search-section-head .cls-section-title { font-size: 28px; }
/* Phase-2 loading skeleton */
.grid-loading-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 260px;
  gap: 14px;
  color: var(--text-tertiary);
}
.grid-loading-spinner {
  width: 32px; height: 32px;
  border: 3px solid var(--border);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
.grid-loading-label { font-size: 14px; font-weight: 500; }

/* ═══════════════════════════════════════════
   GRID DISPLAY CONTROLS — card background + card size
   Two segmented three-way choices, sitting opposite the breadcrumb. Page-level
   rather than per-section, for the same reason the colour bar is: the setting
   applies to the whole grid, so one copy of it says what it actually does.
   ═══════════════════════════════════════════ */
.grid-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
.grid-controls { display: flex; align-items: center; gap: 8px; }
.seg {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 3px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 11px;
}
.seg button {
  width: 30px; height: 30px; padding: 0;
  border: 0; border-radius: 8px;
  background: transparent;
  cursor: pointer;
  display: grid; place-items: center;
  transition: background var(--transition), box-shadow var(--transition);
}
.seg button:hover { background: var(--hover, #f0ece6); }
/* Selection LIFTS rather than repaints — this file's own idiom (.rc, .big-card).
   And the selected option is INERT: both controls are three-way choices, so
   pressing the active one must not compound. Killing the cursor and the hover
   response says so before the click rather than after. */
.seg button[aria-checked="true"] {
  background: #fff;
  box-shadow: var(--shadow-card);
  cursor: default;
}
.seg button[aria-checked="true"]:hover { background: #fff; }
.seg button:focus-visible { outline: 2.5px solid var(--accent); outline-offset: 2px; }

/* Each button DRAWS what it does — the background swatch is the background it
   applies, the size glyph is the density it produces. No text, so the two
   groups read as one family and neither needs translating into the button. */
.seg-sw { width: 18px; height: 18px; border-radius: 5px; border: 1px solid rgba(0,0,0,0.14); }
.seg-sw.is-default { background: #f5f4f2; }
.seg-sw.is-dark    { background: hsl(35 6% 18%); }
.seg-sw.is-checker {
  background-color: #fff;
  background-image:
    linear-gradient(45deg, #dcd7d0 25%, transparent 25%, transparent 75%, #dcd7d0 75%),
    linear-gradient(45deg, #dcd7d0 25%, transparent 25%, transparent 75%, #dcd7d0 75%);
  background-size: 10px 10px;
  background-position: 0 0, 5px 5px;
}
.seg-sz { display: grid; gap: 2px; width: 18px; height: 18px; }
.seg-sz i { background: #b7b0a8; border-radius: 1.5px; display: block; }
.seg-sz.is-s { grid-template-columns: repeat(3, 1fr); grid-template-rows: repeat(3, 1fr); gap: 1.5px; }
.seg-sz.is-m { grid-template-columns: repeat(2, 1fr); grid-template-rows: repeat(2, 1fr); }
.seg-sz.is-l { grid-template-columns: 1fr; grid-template-rows: 1fr; }
.seg button[aria-checked="true"] .seg-sz i { background: var(--text-secondary); }

/* Reading type scale (#79). A letter at three sizes, in the same 18px box the
   background swatch and the card-size grid use, so the three view settings read
   as one row of equals. Fixed px and NOT var(--text-scale): this control is
   chrome, and a button that grew when pressed would shove its own row sideways. */
.seg-tx { width: 18px; height: 18px; display: grid; place-items: center;
          font-weight: 800; color: #b7b0a8; line-height: 1; font-family: var(--font); }
.seg-tx.is-m  { font-size: 10px; }
.seg-tx.is-l  { font-size: 13px; }
.seg-tx.is-xl { font-size: 16px; }
.seg button[aria-checked="true"] .seg-tx { color: var(--text-secondary); }

.ill-card {
  background: var(--surface);
  border-radius: 14px;
  box-shadow: var(--shadow-card);
  overflow: hidden;
  cursor: pointer;
  /* transform only — GPU composited, zero repaint per frame */
  transition: transform 0.12s ease-out;
  /* isolate each card's layout/style scope so hover recalc doesn't cascade */
  contain: layout style;
}
.ill-card:hover {
  transform: scale(1.04);
}
/* Ring appears instantly on hover start (one repaint, not per-frame) */
.ill-card[data-domain]:hover {
  outline: 2.5px solid var(--d100);
  outline-offset: 2px;
}
.ill-card:active { transform: scale(0.98); }

.ill-thumb-wrap {
  width: 100%;
  aspect-ratio: 1;
  /* Four longhands, not the `background` shorthand: the shorthand would reset
     background-image every time only the colour changed, and the checkered
     mode needs colour and image set together. */
  background-color: var(--thumb-bg);
  background-image: var(--thumb-bg-image);
  background-size: var(--thumb-bg-size);
  background-position: var(--thumb-bg-position);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  position: relative; /* containing block for .hub-badge */
}
.ill-thumb {
  width: 100%;
  height: 100%;
  object-fit: contain;
  padding: 6px;
}

/* A card label holds either ONE line (no Qualifier) or TWO (Base + Qualifier
   under a hairline), and every card must be the same height or the grid goes
   ragged. The height therefore budgets for the worst two-line case: 2 Base
   lines + the separator + 1 Qualifier line.

   flex-direction is COLUMN, and that is load-bearing. This was a default-row
   flex container, which is indistinguishable from a block while it has a single
   child — adding the Qualifier line put the two side by side
   (「自動歩行 原始反射」) rather than stacked. */
.ill-label {
  padding: 5px 9px 7px;
  height: calc(var(--label-font, 12px) * 1.4 * 3 + 7px + 5px + 7px);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.ill-label-text, .ill-label-base {
  font-size: var(--label-font, 12px);
  font-weight: 500;
  color: var(--text-secondary);
  line-height: 1.4;
  text-align: center;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
/* When a Qualifier is present the Base gets its own line and clamps shorter,
   so the two lines together stay within the height one label used to take. */
.ill-label-base { -webkit-line-clamp: 2; }
/* Card qualifier line — Guidelines/14 → Anatomy, Guidelines/19 §7.
   Same size as the Base: the Qualifier is part of the title, not metadata about
   it, so shrinking it would demote the one fact that tells two set members
   apart. Separation comes from the hairline and a lighter colour.
   NOT a chip — the only chips are the hub badge here and tags in the modal, so
   a filled pill under a title would read as one.
   keep-all removes the between-character break Japanese allows by default,
   which is the actual defect; ・ stays legal, so a stacked Qualifier wraps at
   its own separator instead of shredding mid-word. */
.ill-label-qual {
  font-size: var(--label-font, 12px);
  color: var(--text-tertiary);
  max-width: 100%;
  line-height: 1.4;
  text-align: center;
  margin-top: 3px;
  padding-top: 2px;
  border-top: 1px solid var(--border);
  align-self: stretch;
  word-break: keep-all;
  overflow-wrap: normal;
  display: -webkit-box;
  -webkit-line-clamp: 1;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.sentinel { height: 80px; }

/* ── Classification sections (Characters / Things / Other) ── */
.cls-section { display: none; }
.cls-section.has-content { display: block; }

.cls-section-header {
  margin: 56px 0 0;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--border);
}

/* ── The header as a filter (#35) ─────────────────────────────────────────
   The title, the count and a filter glyph, wrapped in one button. It was inert
   — the most obvious thing on the page to click did nothing, while the real
   control hid in a hamburger.

   Unstyled as a button on purpose: no chrome at rest, so the grid still reads
   as headings with rules under them rather than a stack of five buttons. The
   affordance arrives on hover and focus, which is where someone is asking. */
.cls-section-toggle {
  display: flex; align-items: baseline; gap: 10px;
  width: 100%; padding: 0 6px 0 0;
  background: none; border: none; cursor: pointer;
  font-family: var(--font); text-align: left;
  color: inherit;
  border-radius: 8px;
  transition: opacity 0.12s;
}
.cls-section-toggle:hover .cls-section-title,
.cls-section-toggle:focus-visible .cls-section-title { color: var(--text-secondary); }

/* The glyph that says a header is clickable. Hidden until hover or focus, so
   five of them do not sit down the page competing with the titles. */
.cls-section-hint {
  margin-left: auto;
  font-size: 20px;
  color: var(--text-tertiary);
  opacity: 0;
  transition: opacity 0.12s;
}
.cls-section-toggle:hover .cls-section-hint,
.cls-section-toggle:focus-visible .cls-section-hint { opacity: 1; }

/* The chosen one stays lit… */
.cls-section.is-filtered .cls-section-title { color: var(--text-primary); }
.cls-section.is-filtered .cls-section-hint { opacity: 1; color: var(--text-primary); }

/* …and while a filter is on, the others recede. Only the headers dim — their
   grids are already empty, so dimming the section would say nothing. This is
   what makes one active filter read as a CHOICE rather than as "the rest
   happen to have no illustrations". */
.ill-grid-wrap.is-filtering .cls-section:not(.is-filtered) .cls-section-header { opacity: 0.45; }
.ill-grid-wrap.is-filtering .cls-section:not(.is-filtered) .cls-section-toggle:hover { opacity: 1; }

/* ── The relocated chrome (#35) ───────────────────────────────────────────
   One switcher and one palette button for the whole grid, in the head beside
   the display controls, instead of a copy inside every section header. */
.grid-filters {
  display: flex; align-items: center; gap: 4px;
  position: relative;
  margin-left: auto;
}
.grid-filters .section-hamburger-btn,
.grid-filters .color-picker-btn {
  width: 38px; height: 38px;
  display: inline-flex; align-items: center; justify-content: center;
  background: none; border: none; cursor: pointer;
  border-radius: 9px; color: var(--text-tertiary);
  transition: background 0.12s, color 0.12s;
}
.grid-filters .section-hamburger-btn .ms,
.grid-filters .color-picker-btn .ms { font-size: 22px; line-height: 1; }
.grid-filters .section-hamburger-btn:hover,
.grid-filters .color-picker-btn:hover { background: var(--hover); color: var(--text-primary); }
/* WEIGHT 400, NOT 300, AND THIS IS NOT A DESIGN CHANGE (2026-08-05).
   Both of these declared 300 and both have always PAINTED at 400: the Google
   Fonts request loads M PLUS Rounded 1c at 400/500/700/800 only, and CSS font
   matching for a target below 400 looks downward first, finds nothing, then
   takes the next weight up. So 300 described a weight the page could not use.
   Writing 400 changes no pixel while the webfont is present, and makes the two
   agree in the one case where they did not: if the webfont fails and a system
   fallback DOES carry a 300, the old declaration rendered genuinely lighter
   than every other title on the page. Do not restore 300 to "lighten" this —
   there is nothing lighter to reach, and opacity is the lever that works. */
.cls-section-title {
  font-size: 38px;
  font-weight: 400;
  letter-spacing: -0.01em;
  color: var(--text-tertiary);
  margin: 0;
  line-height: 1;
}
/* Count beside the section title, in parentheses. Same size and weight as the
   title, set back by opacity rather than a lighter weight — see the note above
   for why a lighter weight is not available. Opacity also tracks
   --text-tertiary if that token ever moves. Scales with the title at both
   breakpoints below. */
.cls-section-count {
  font-size: 38px;
  font-weight: 400;
  letter-spacing: -0.01em;
  line-height: 1;
  color: var(--text-tertiary);
  opacity: 0.5;
  flex-shrink: 0;
  white-space: nowrap;
}
.cls-section-count:empty { display: none; }
/* A section filtered to nothing keeps its header; fade its (0) so the eye still
   skips past it to the sections that do have results. */
.cls-section-count.is-zero { opacity: 0.3; }
.cls-section-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(calc(180px * var(--card-scale)), 1fr));
  gap: 10px;
  margin-top: 18px;
  margin-bottom: 48px;
}

@media (max-width: 900px) {
  .cls-section-title { font-size: 28px; }
  .cls-section-count { font-size: 28px; }
}
@media (max-width: 1024px) {
  /* 3-column grid — placed after base styles so it wins the cascade */
  .ill-grid, .cls-section-grid { grid-template-columns: repeat(var(--card-cols-3), 1fr); gap: 6px; }
  .ill-label { padding: 4px 5px 5px; }
  .ill-label-text, .ill-label-base, .ill-label-qual { font-size: var(--label-font-sm, 11px); }
  /* Category header: shrink image on mobile */
  .domain-exp-cat-img { width: 72px; height: 72px; }
  .domain-exp-cat-title { font-size: 20px; }
  /* Compact breadcrumb on mobile: ancestors small, current stays bold */
  .page-nav-home  { font-size: 13px; }
  .page-nav-sep   { font-size: 13px; }
  .page-nav-ancestor { font-size: 13px; }
  /* Current title smaller on mobile (wrap handles row layout) */
  .page-nav-current { font-size: 22px; }
  /* Language flag moves to Menu dropdown on mobile */
  .lang-flag-btn { display: none; }
}
@media (max-width: 600px) {
  .cls-section-title { font-size: 22px; }
  .cls-section-count { font-size: 22px; }
}

/* ═══════════════════════════════════════════
   DOMAIN COLOUR SYSTEM
   Each domain defines --d100/80/60 (pastel tints)
   and --dtext (darkened hue for contrast text).
   Used by nav ancestor/active states and card tints.
   ═══════════════════════════════════════════ */

/* ── Per-domain CSS variables ── */
[data-domain="people"]             { --d100:hsl(345,62%,76%); --d80:hsl(345,62%,84%); --d60:hsl(345,62%,91%); --dtext:hsl(345,67%,27%); }
[data-domain="sports-recreation"]  { --d100:hsl(5,65%,77%);   --d80:hsl(5,65%,85%);   --d60:hsl(5,65%,92%);   --dtext:hsl(5,70%,27%);   }
[data-domain="food"]               { --d100:hsl(26,68%,78%);  --d80:hsl(26,68%,86%);  --d60:hsl(26,68%,92%);  --dtext:hsl(26,73%,27%);  }
[data-domain="home-daily-life"]    { --d100:hsl(14,60%,80%);  --d80:hsl(14,60%,87%);  --d60:hsl(14,60%,93%);  --dtext:hsl(14,65%,27%);  }
[data-domain="animals"]            { --d100:hsl(32,55%,78%);  --d80:hsl(32,55%,86%);  --d60:hsl(32,55%,92%);  --dtext:hsl(32,60%,27%);  }
[data-domain="history-traditions"] { --d100:hsl(44,58%,77%);  --d80:hsl(44,58%,85%);  --d60:hsl(44,58%,91%);  --dtext:hsl(44,63%,27%);  }
[data-domain="design-elements"]    { --d100:hsl(56,68%,76%);  --d80:hsl(56,68%,83%);  --d60:hsl(56,68%,90%);  --dtext:hsl(56,73%,24%);  }
[data-domain="health-science"]     { --d100:hsl(92,48%,77%);  --d80:hsl(92,48%,85%);  --d60:hsl(92,48%,92%);  --dtext:hsl(92,53%,26%);  }
[data-domain="plants-nature"]      { --d100:hsl(128,42%,76%); --d80:hsl(128,42%,84%); --d60:hsl(128,42%,91%); --dtext:hsl(128,47%,27%); }
[data-domain="science-technology"] { --d100:hsl(183,52%,75%); --d80:hsl(183,52%,83%); --d60:hsl(183,52%,91%); --dtext:hsl(183,57%,25%); }
[data-domain="school-learning"]    { --d100:hsl(205,62%,76%); --d80:hsl(205,62%,84%); --d60:hsl(205,62%,91%); --dtext:hsl(205,67%,27%); }
[data-domain="current-events"]     { --d100:hsl(213,30%,75%); --d80:hsl(213,30%,83%); --d60:hsl(213,30%,91%); --dtext:hsl(213,35%,27%); }
[data-domain="work-society"]       { --d100:hsl(228,46%,78%); --d80:hsl(228,46%,86%); --d60:hsl(228,46%,92%); --dtext:hsl(228,51%,27%); }
[data-domain="arts-culture"]       { --d100:hsl(268,52%,78%); --d80:hsl(268,52%,86%); --d60:hsl(268,52%,92%); --dtext:hsl(268,57%,27%); }
[data-domain="world-geography"]    { --d100:hsl(168,48%,73%); --d80:hsl(168,48%,83%); --d60:hsl(168,48%,90%); --dtext:hsl(168,56%,24%); }
[data-domain="pop-culture"]        { --d100:hsl(308,56%,78%); --d80:hsl(308,56%,86%); --d60:hsl(308,56%,92%); --dtext:hsl(308,61%,27%); }

/* ── Card image-side tints (light fill behind illustration thumbnail) ── */
.tint-people            { background: linear-gradient(145deg, hsl(345,62%,97%), hsl(345,62%,91%)); }
.tint-sports-recreation { background: linear-gradient(145deg, hsl(5,65%,97%),   hsl(5,65%,91%));   }
.tint-food              { background: linear-gradient(145deg, hsl(26,68%,97%),  hsl(26,68%,91%));  }
.tint-home-daily-life   { background: linear-gradient(145deg, hsl(14,60%,97%),  hsl(14,60%,91%));  }
.tint-animals           { background: linear-gradient(145deg, hsl(32,55%,97%),  hsl(32,55%,91%));  }
.tint-history-traditions{ background: linear-gradient(145deg, hsl(44,58%,97%),  hsl(44,58%,91%));  }
.tint-design-elements   { background: linear-gradient(145deg, hsl(56,68%,97%),  hsl(56,68%,90%));  }
.tint-plants-nature     { background: linear-gradient(145deg, hsl(128,42%,97%), hsl(128,42%,91%)); }
.tint-science-technology{ background: linear-gradient(145deg, hsl(183,52%,97%), hsl(183,52%,91%)); }
.tint-school-learning   { background: linear-gradient(145deg, hsl(205,62%,97%), hsl(205,62%,91%)); }
.tint-current-events    { background: linear-gradient(145deg, hsl(213,30%,97%), hsl(213,30%,91%)); }
.tint-work-society      { background: linear-gradient(145deg, hsl(228,46%,97%), hsl(228,46%,91%)); }
.tint-arts-culture      { background: linear-gradient(145deg, hsl(268,52%,97%), hsl(268,52%,91%)); }
.tint-world-geography   { background: linear-gradient(145deg, hsl(168,48%,97%), hsl(168,48%,91%)); }
.tint-pop-culture       { background: linear-gradient(145deg, hsl(308,56%,97%), hsl(308,56%,91%)); }

/* ═══════════════════════════════════════════
   MODAL — HORIZONTAL (v2.0, #76 — replaced the flip card 2026-08-05)
   ═══════════════════════════════════════════
   Part names are Guidelines/14 → Anatomy → "Modal, horizontal", and the class
   names ARE those terms. The flip is gone: there is no second face, no
   perspective, no backface-visibility. The back face became the Details panel,
   which is a SIBLING of the words rather than the reverse of everything. */
.modal-overlay {
  display: none;
  position: fixed; inset: 0;
  background: rgba(0,0,0,0.55);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  z-index: 900;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
.modal-overlay.open { display: flex; }

/* THE MODAL ITSELF. 16:9 at size M — the shape the prototype settled on by
   measuring how much of the catalogue fits, not by taste — with the
   illustration taking 2/5 of the width and the words 3/5. Fixed rather than
   fluid: a modal that resizes to its content makes every illustration a
   different shape, and the set bar and the buttons move under the cursor
   between one illustration and the next. */
.modal-scene {
  --mw: min(1120px, 94vw);
  --mh: min(630px, 86vh);
  --leftw: calc(var(--mw) * 0.4);
  /* The shared height floor of the Set bar and the Action row. Each is the last
     thing in a full-height column, so equal heights put their two top rules on
     ONE line across the modal — by construction, not by tuning padding on each
     side until it happens to line up today. */
  /* 156, not the prototype's 128, and the difference is the app's own carousel:
     its items are 76px wide and CAPTIONED — the prototype's were bare 52px
     thumbnails. Measured rather than guessed: the set bar's natural height with
     real members is 153px, so a 128px floor left it 25px taller than the action
     row and the "one line across the modal" was two lines 25px apart. The floor
     has to clear the TALLER of the two, or it is not a floor. */
  --bottom-row: 156px;
  width: var(--mw);
  height: var(--mh);
  max-width: none;
  position: relative;
  display: flex;
  overflow: hidden;
  background: var(--surface);
  border-radius: 24px;
  box-shadow: 0 30px 80px rgba(0,0,0,0.3);
  animation: slideUp 0.25s cubic-bezier(0.34,1.56,0.64,1);
}
@keyframes slideUp {
  from { opacity: 0; transform: translateY(20px) scale(0.97); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) { .modal-scene { animation: none; } }

/* ── Header: the controls that change how the WHOLE modal reads ── */
.modal-head {
  position: absolute; top: 0; left: 0; right: 0; z-index: 5;
  height: 52px; display: flex; align-items: center; pointer-events: none;
}
.modal-head > * { pointer-events: auto; }
/* The illustration's own two corners: close at the left, backgrounds at the
   right. In flow as the two ends of a space-between row — an absolute offset
   would need re-tuning every time --leftw changed. */
.head-ill  { flex: 0 0 var(--leftw); display: flex; align-items: center;
             justify-content: space-between; padding: 0 14px 0 12px; }
.head-text { flex: 1; display: flex; align-items: center; gap: 8px; padding-left: 30px; }

.close-btn {
  width: 32px; height: 32px; border-radius: 50%; flex: 0 0 auto;
  background: rgba(255,255,255,0.9); border: 1px solid var(--border);
  color: var(--text-primary); cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font);
}
.close-btn:hover { border-color: var(--text-tertiary); }
.close-btn .ms { font-size: 18px; line-height: 1; }

/* Background switch — three swatches over the illustration. */
.bg-switch { display: flex; gap: 3px; background: var(--surface2, #f5f2ed);
             border: 1px solid var(--border); border-radius: 9px; padding: 2px; }
.bg-switch button { width: 26px; height: 22px; border: 1px solid transparent;
  border-radius: 6px; cursor: pointer; background: none; padding: 0;
  display: flex; align-items: center; justify-content: center; }
.bg-switch button[aria-checked="true"] { border-color: var(--text-tertiary); background: #fff; }
.bg-swatch { width: 14px; height: 14px; border-radius: 4px;
             border: 1px solid rgba(0,0,0,.18); display: block; }
.bg-swatch.bg-default { background: #f5f4f2; }
.bg-swatch.bg-dark    { background: hsl(35,6%,18%); }
.bg-swatch.bg-checker { background:
  linear-gradient(45deg,#ddd 25%,transparent 25%,transparent 75%,#ddd 75%) 0 0/8px 8px,
  linear-gradient(45deg,#ddd 25%,transparent 25%,transparent 75%,#ddd 75%) 4px 4px/8px 8px, #fff; }

/* The language control, same as the top bar's — no chrome, Irasutoya's own flag
   illustration. On the phone it moves to .ill-foot, the artwork's bottom right. */
.modal-lang-btn { width: 36px; height: 36px; border: none; background: none; padding: 0;
  line-height: 1; cursor: pointer; display: flex; align-items: center;
  justify-content: center; transition: transform .15s; }
.modal-lang-btn:hover  { transform: scale(1.08); }
.modal-lang-btn:active { transform: scale(0.96); }
.modal-lang-btn img { width: 30px; height: 30px; object-fit: contain; display: block; pointer-events: none; }
.ill-foot { position: absolute; right: 12px; bottom: 12px; z-index: 6;
            display: none; align-items: center; gap: 8px; }

/* ── The illustration column ── */
.ill-col { flex: 0 0 var(--leftw); min-width: 0; display: flex; flex-direction: column;
           border-right: 1px solid var(--border); background: var(--surface2, #f5f2ed); }
.artwork { flex: 1; min-height: 0; position: relative;
           display: flex; align-items: center; justify-content: center; padding: 26px 26px 0; }
.artwork[data-bg="default"] { background: #f5f4f2; }
.artwork[data-bg="dark"]    { background: hsl(35,6%,18%); }
.artwork[data-bg="checker"] { background:
  linear-gradient(45deg,hsl(35,8%,90%) 25%,transparent 25%,transparent 75%,hsl(35,8%,90%) 75%) 0 0/16px 16px,
  linear-gradient(45deg,hsl(35,8%,90%) 25%,transparent 25%,transparent 75%,hsl(35,8%,90%) 75%) 8px 8px/16px 16px, #fff; }

/* ── The text column ── */
.text-col { flex: 1; display: flex; flex-direction: column; min-width: 0; }
/* Everything the Details panel MAY cover. The action row is this element's
   SIBLING and so is unreachable by it, at any button-row height. */
.panel-bounds { flex: 1; position: relative; display: flex; flex-direction: column; min-height: 0; }
/* `safe` is load-bearing: plain `center` also centres content taller than the
   box, pushing the first line of a long description above the scroll origin
   where it can never be reached. The 56/8 padding puts a short block in the
   middle of the area BELOW the header rather than of the whole column. */
.reading { flex: 1; overflow-y: auto; padding: 56px 30px 8px;
           display: flex; flex-direction: column; justify-content: safe center; }
.reading > :last-child { margin-bottom: 0; }

/* max-height was 300px, sized for the flip card's fixed image band. In a
   448px column that capped the illustration at 300px however much room it had
   — the exact defect the high-resolution tier exists to remove, reintroduced
   one line lower. It fills the artwork now. */
.modal-img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  display: block;
}
/* THE HIGH-RESOLUTION SWAP (#76). The thumbnail paints first — it is already in
   cache from the card that was just clicked — and the master-resolution file
   replaces it once decoded.

   THE BLUR IS APPLIED TO THE NEW IMAGE, NOT TO THE THUMBNAIL. `.is-swapping` is
   added for one frame as the src changes and then removed, so the illustration
   that resolves is the high-resolution one. Blurring the thumbnail instead
   would mean deliberately degrading what a visitor is already looking at, and —
   worse during a rollout — it would sit blurred forever on every illustration
   that has no derivative. Those never enter this state at all.

   `filter`, not `opacity`: fading out shows the panel through the drawing
   mid-swap, and on flat-colour art that reads as the picture vanishing rather
   than sharpening. Both files are the same drawing, so nothing moves. */
.modal-img { transition: filter .25s ease-out; }
.modal-img.is-swapping { filter: blur(3px); transition: none; }
@media (prefers-reduced-motion: reduce) {
  .modal-img { transition: none; }
  .modal-img.is-swapping { filter: none; }
}
.modal-close {
  position: absolute;
  top: 12px; right: 12px;
  width: 32px; height: 32px;
  border-radius: 50%;
  background: rgba(0,0,0,0.26);
  border: none; cursor: pointer;
  color: white; font-size: 16px;
  display: flex; align-items: center; justify-content: center;
  transition: background 0.15s;
  font-family: var(--font); z-index: 2;
}
.modal-close:hover { background: rgba(0,0,0,0.5); }

/* .modal-scroll-body and .modal-body are GONE — the Reading area replaced both
   (see the shell above). Left-aligned now rather than centred: a centred
   paragraph is readable as one line and tiring as four, and the 3/5 column
   gives the description real width for the first time. */
.modal-title {
  font-size: calc(18px * var(--text-scale)); font-weight: 700;
  letter-spacing: -0.3px; line-height: 1.35; margin-bottom: 3px;
}
.modal-desc {
  font-size: calc(13.5px * var(--text-scale)); line-height: 1.65;
  color: var(--text-secondary); margin-bottom: 14px;
}
/* In こども (#69) the description matches the TITLE's size — 18px, not 13.5px.
   The usual hierarchy exists because an adult skims the title and reads the
   description only if they want it. A child reading kana is doing the opposite:
   the description is the longest continuous text on the screen and the hardest
   thing there, so it is the last place to shrink the type. Line-height opens
   too, because 分かち書き spacing already widens the line and cramped leading
   would undo it. Only applied when kanaHTML actually rendered — openModal sets
   the class from the same condition that fills the element. */
.modal-desc.is-kana { font-size: calc(18px * var(--text-scale)); line-height: 1.75; }

/* Metadata collapsible */
/* File details — gray bar below the action buttons */
.modal-meta { width: 100%; margin-top: 8px; }
.modal-details-flip-btn {
  display: flex; align-items: center; justify-content: center;
  color: var(--text-secondary); background: var(--surface2);
  border: 1px solid var(--border); font-family: var(--font);
  cursor: pointer; transition: background 0.15s;
}
.modal-details-flip-btn:hover { background: #e2e2e0; color: var(--text-primary); }
.modal-meta-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 10px 20px;
  margin-top: 10px;
  /* No left padding: the grid now sits flush with the "File Info" label above
     it, and the full width is available to a row that spans both columns. */
  padding: 12px 0 4px;
  text-align: left;
}
.modal-meta-col { display: flex; flex-direction: column; gap: 10px; }
.modal-meta-item { display: flex; flex-direction: column; gap: 2px; }
.modal-meta-label {
  font-size: calc(12px * var(--text-scale)); font-weight: 400;
  letter-spacing: 0.1px;
  color: var(--text-tertiary);
}
.modal-meta-value {
  font-size: calc(14px * var(--text-scale)); color: var(--text-secondary);
  word-break: break-all; line-height: 1.35;
}
/* A row that spans both columns. The derived link is a full illustration title
   and wraps awkwardly inside a half-width column; given the whole grid it fits
   on one line. break-word rather than break-all so a long title breaks between
   words instead of mid-word. */
.modal-meta-wide { grid-column: 1 / -1; }
.modal-meta-wide .modal-meta-value { word-break: break-word; }
/* Links in the metadata grid follow the encyclopedia's convention, the same one
   .info-panel-body uses: accent colour, no rule until hover. The browser default
   (blue + permanent underline) read as foreign next to everything around it. */
.modal-meta-value a {
  color: var(--accent);
  text-decoration: none;
  transition: color 0.15s;
}
.modal-meta-value a:hover { text-decoration: underline; }
/* THE TAG SHELF is fenced by two rules — one above it here, one on the action
   row below. Without the lower one the chips and the buttons read as a single
   field of rounded shapes, which is exactly what they are not: tags lead deeper
   INTO the encyclopedia, the buttons leave it. */
.tag-shelf { padding: 12px 30px 13px; border-top: 1px solid var(--border); }
.section-label { font-size: calc(11px * var(--text-scale)); font-weight: 700; letter-spacing: .05em;
  text-transform: uppercase; color: var(--text-tertiary); margin: 0 0 7px;
  display: flex; align-items: center; gap: 5px; justify-content: center; }
.section-label .ms { font-size: 14px; }
.panel-body .section-label { justify-content: flex-start; margin-top: 14px; }
.panel-body .section-label:first-child { margin-top: 0; }
.modal-tags { display: flex; flex-wrap: wrap; gap: 5px; justify-content: center; }
.modal-tag {
  font-size: calc(12px * var(--text-scale)); background: #f0f0f0; color: var(--text-secondary);
  border-radius: 8px; padding: 4px 10px; cursor: pointer;
  border: none; transition: background 0.15s; font-family: var(--font);
}
.modal-tag:hover { background: #e0e0e0; }

/* The action row: four buttons, visible in EVERY state — the one thing the flip
   could not do, because it hid everything to show anything. Shares
   --bottom-row with the set bar so their two rules meet across the modal. */
.action-row {
  padding: 13px 30px 15px; border-top: 1px solid var(--border);
  min-height: var(--bottom-row); box-sizing: border-box; align-content: center;
  background: var(--surface);
}
.modal-front-footer {
  display: flex; flex-direction: column; align-items: stretch; gap: 0;
  padding: 12px 14px 14px;
  border-top: 1px solid var(--border);
  flex-shrink: 0;
  background: var(--surface);
}
.modal-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 7px; }
.modal-action-btn {
  flex: unset;
  display: flex; align-items: center; justify-content: center; gap: 5px;
  padding: 8px 14px;
  background: #f5f5f3; border: 1px solid rgba(0,0,0,0.09);
  border-radius: 10px; font-size: 12px; font-weight: 500;
  color: var(--text-primary); cursor: pointer; text-decoration: none;
  transition: background 0.18s, color 0.18s, border-color 0.18s;
  font-family: var(--font); white-space: nowrap;
}
/* ── Copy — Blue (school-learning h:205 s:62) ── */
.modal-action-copy         { background: hsl(205,62%,91%) !important; color: hsl(205,67%,27%) !important; border-color: hsl(205,50%,83%) !important; }
.modal-action-copy:hover   { background: hsl(205,62%,86%) !important; }
.modal-btn--success        { background: hsl(205,62%,76%) !important; color: hsl(205,67%,27%) !important; border-color: hsl(205,55%,68%) !important; }
.modal-btn--error          { background: hsl(5,65%,82%)   !important; color: hsl(5,70%,30%)    !important; border-color: hsl(5,58%,72%)   !important; }

/* ── Download — Green (plants-nature h:128 s:42) ── */
.modal-action-download       { background: hsl(128,42%,91%) !important; color: hsl(128,47%,27%) !important; border-color: hsl(128,35%,83%) !important; }
.modal-action-download:hover { background: hsl(128,42%,86%) !important; }
.modal-btn--downloading      { background: hsl(128,42%,76%) !important; color: hsl(128,47%,27%) !important; border-color: hsl(128,35%,68%) !important; }

/* ── Link (Irasutoya) — Red (sports h:5 s:65) ── */
.modal-action-link       { background: hsl(5,65%,92%) !important; color: hsl(5,70%,27%) !important; border-color: hsl(5,58%,83%) !important; }
.modal-action-link:hover { background: hsl(5,65%,86%) !important; }
.modal-btn--active       { background: hsl(5,65%,76%) !important; color: hsl(5,70%,27%) !important; border-color: hsl(5,58%,68%) !important; }

/* ── Info (flip) — Yellow (design-elements h:56 s:68) ── */
.modal-details-flip-btn { background: hsl(56,68%,91%) !important; color: hsl(56,73%,24%) !important; border-color: hsl(56,58%,82%) !important; }
.modal-details-flip-btn:hover { background: hsl(56,68%,86%) !important; }

/* Back-face "Back" button */
.modal-back-flip-btn {
  width: 100%; border-radius: 10px; height: 36px;
  font-size: 12px; font-weight: 500; gap: 6px;
  background: #efefed; border: 1px solid rgba(0,0,0,0.1);
  cursor: pointer; display: flex; align-items: center; justify-content: center;
  color: var(--text-secondary); font-family: var(--font);
  transition: background 0.15s;
}
.modal-back-flip-btn:hover { background: #e2e2e0; color: var(--text-primary); }

/* ── Back face ── */
.modal-back-header {
  background: #f5f4f2;
  border-radius: 24px 24px 0 0;
  padding: 14px 52px 13px 16px; /* right padding gives space for the floating ✕ button */
  display: flex; align-items: center; gap: 11px;
  position: relative;
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}
.modal-back-thumb {
  width: 69px; height: 69px; object-fit: contain;
  border-radius: 8px; background: white;
  flex-shrink: 0; padding: 3px;
}
.modal-back-header-text { flex: 1; min-width: 0; }
.modal-back-title {
  font-size: 15px; font-weight: 400; line-height: 1.4;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
.modal-back-subtitle-text { display: none; }
.modal-back-scroll { flex: 1; overflow-y: auto; }
.modal-back-body { padding: 16px 22px 14px; }
.modal-back-section { margin-bottom: 20px; }
.modal-back-section-label {
  font-size: 13px; font-weight: 400;
  letter-spacing: 0.1px;
  color: var(--text-tertiary); margin-bottom: 10px;
}

/* Taxonomy list on back */
.modal-taxonomy { display: flex; flex-direction: column; gap: 6px; }
.modal-taxon {
  font-size: 15px; font-weight: 400;
  border-radius: 10px; padding: 9px 13px;
  cursor: pointer; border: 1.5px solid transparent;
  transition: filter 0.15s; line-height: 1.35;
}
.modal-taxon:hover { filter: brightness(0.93); }
.modal-taxon-path { display: flex; flex-wrap: wrap; align-items: center; gap: 4px; }
.modal-taxon-domain { font-weight: 400; }
.modal-taxon-sep { opacity: 0.45; font-size: 11px; }
.modal-back-tags { display: flex; flex-wrap: wrap; gap: 5px; }

/* Back: pinned footer — flip back button */
.modal-back-footer {
  display: flex; align-items: stretch;
  padding: 10px 14px 14px;
  border-top: 1px solid var(--border);
  flex-shrink: 0;
  background: var(--surface);
}

/* ═══════════════════════════════════════════
   COMMAND PALETTE (⌘K)
   ═══════════════════════════════════════════ */
/* Opened from ⌘K: the modal has to sit above the palette (z-index 1000), which
   stays open underneath so the search is still there when the modal closes. */
.modal-overlay.over-cmdk { z-index: 1100; }

.cmdk-overlay {
  display: none;
  position: fixed; inset: 0;
  background: rgba(0,0,0,0.4);
  backdrop-filter: blur(6px);
  z-index: 1000;
  align-items: flex-start;
  justify-content: center;
  padding-top: 12vh;
}
.cmdk-overlay.open { display: flex; }

.cmdk {
  background: var(--surface);
  border-radius: 18px;
  box-shadow: 0 24px 60px rgba(0,0,0,0.28);
  width: 580px;
  max-width: calc(100vw - 32px);
  overflow: hidden;
  animation: slideDown 0.2s cubic-bezier(0.34,1.56,0.64,1);
}

@keyframes slideDown {
  from { opacity: 0; transform: translateY(-12px) scale(0.98); }
  to   { opacity: 1; transform: none; }
}

.cmdk-search {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 15px 18px;
  border-bottom: 1px solid var(--border);
}
.cmdk-icon { font-size: 20px; color: var(--text-tertiary); line-height: 1; }
.cmdk-input {
  flex: 1; border: none; outline: none;
  font-size: 16px; font-family: var(--font);
  background: transparent; color: var(--text-primary);
  font-weight: 500;
}
.cmdk-input::placeholder { color: var(--text-tertiary); }

.cmdk-results { max-height: 400px; overflow-y: auto; padding: 8px; }

.cmdk-result {
  display: flex; align-items: center; gap: 11px;
  padding: 7px 9px; border-radius: 10px;
  cursor: pointer; transition: background 0.1s;
}
.cmdk-result:hover,
.cmdk-result.selected { background: var(--surface2); }
.cmdk-result.selected { background: #eef4ff; }

.cmdk-thumb {
  width: 46px; height: 46px;
  object-fit: contain; border-radius: 8px;
  background: #f5f4f2; flex-shrink: 0;
}
.cmdk-info { flex: 1; min-width: 0; }
.cmdk-name {
  font-size: 14px; font-weight: 500; color: var(--text-primary);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.cmdk-name-jp {
  font-size: 12px; color: var(--text-tertiary); margin-top: 1px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.cmdk-empty { padding: 30px; text-align: center; color: var(--text-tertiary); font-size: 14px; }

/* ── The facet bar (#35 half 2) ───────────────────────────────────────────
   Variant B: one row, the rest disclosed. Sits where the colour bar sits, and
   borrows its language — a label on the left, values on the right — because a
   second filter surface should not need a second visual grammar. */

.facet-bar { margin: 6px 0 2px; }
.facet-bar[hidden] { display: none; }

.facet-row { display: flex; align-items: flex-start; gap: 12px; padding: 3px 0; }
.facet-label {
  flex: 0 0 128px; padding-top: 5px;
  font-size: 13px; color: var(--text-tertiary);
}
.facet-chips { display: flex; flex-wrap: wrap; gap: 6px; flex: 1; min-width: 0; }

.facet-chip {
  display: inline-flex; align-items: baseline; gap: 5px;
  padding: 4px 11px; border-radius: 999px;
  border: 1px solid var(--border); background: var(--surface2);
  font-family: var(--font); font-size: 13px; color: var(--text-primary);
  cursor: pointer; white-space: nowrap;
  transition: background 0.12s, border-color 0.12s, color 0.12s;
}
.facet-chip:hover { border-color: var(--text-tertiary); }
.facet-chip.on { background: var(--text-primary); border-color: var(--text-primary); color: #fff; }
.facet-n { font-size: 11px; opacity: 0.55; font-variant-numeric: tabular-nums; }
.facet-chip.on .facet-n { opacity: 0.8; }

/* #45: the general value leads its row and is set in italic. NEVER labelled
   "(any)" — P(動物|犬) is 6.2%, so "any" would promise the whole facet and
   deliver a sixth of it. The rule border is the divider that decision asks for,
   drawn on the chip rather than as a separate element so it survives wrapping. */
.facet-chip.is-general {
  font-style: italic;
  margin-right: 7px;
  border-right-width: 1px;
  box-shadow: 7px 0 0 -6px var(--border);
}

.facet-more {
  display: inline-flex; align-items: center; gap: 4px;
  margin: 4px 0 0 140px; padding: 4px 2px;
  background: none; border: none; cursor: pointer;
  font-family: var(--font); font-size: 13px; color: var(--text-secondary);
}
.facet-more:hover { color: var(--text-primary); }
.facet-more .ms { font-size: 18px; }

.facet-rest { margin-top: 2px; padding-top: 4px; border-top: 1px dashed var(--border); }

.facet-clear {
  display: inline-flex; align-items: center; gap: 3px;
  margin: 6px 0 0 140px; padding: 3px 9px 3px 5px;
  border: 1px solid var(--border); border-radius: 999px;
  background: var(--surface); cursor: pointer;
  font-family: var(--font); font-size: 12px; color: var(--text-secondary);
}
.facet-clear:hover { color: var(--text-primary); border-color: var(--text-tertiary); }
.facet-clear .ms { font-size: 15px; }

@media (max-width: 700px) {
  /* The label goes above its chips — 128px of gutter is a third of the screen. */
  .facet-row { flex-direction: column; gap: 4px; }
  .facet-label { flex: none; padding-top: 0; }
  .facet-more, .facet-clear { margin-left: 0; }
}

/* ── Query chips (#63 item 3) ─────────────────────────────────────────────
   One chip per query token on the search results page. COLOUR ENCODES WHAT A
   CHIP DOES, not an arbitrary palette: a diagnostic filter behaves nothing like
   a search term — it answers a curation question and fails closed — and an OR
   group is the only chip meaning "any of these" rather than "this".

   Colour is never the ONLY signal. Excluded chips also carry a leading − and a
   line through the text, because the difference between "with" and "without" is
   the whole point of the control and must survive a colour-blind reader. */

.query-chips {
  display: flex; flex-wrap: wrap; gap: 7px;
  margin: 10px 0 2px;
}

.qchip {
  display: inline-flex; align-items: stretch;
  border-radius: 999px; overflow: hidden;
  border: 1px solid var(--border);
  background: var(--surface2);
  font-size: 13px; line-height: 1;
}
.qchip-body {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 6px 4px 6px 11px;
  border: none; background: transparent; cursor: pointer;
  font-family: var(--font); font-size: 13px; color: var(--text-primary);
}
.qchip-body:hover { background: rgba(0,0,0,0.04); }
.qchip-minus { font-weight: 700; opacity: 0.8; }
.qchip-x {
  border: none; background: transparent; cursor: pointer;
  padding: 0 9px 0 5px; font-size: 15px; line-height: 1;
  color: var(--text-tertiary); font-family: var(--font);
}
.qchip-x:hover { background: rgba(0,0,0,0.07); color: var(--text-primary); }

/* A plain word. Deliberately the quietest of the three — it is the common case
   and the row should read as a query, not as decoration. */
.qchip--term { background: var(--surface2); }

/* "any of these" */
.qchip--or {
  background: #eef4ff;
  border-color: #c9dcff;
}

/* A curation instrument, not a word someone searched for. */
.qchip--filter {
  background: #fff4e2;
  border-color: #f0d9b0;
  font-variant-numeric: tabular-nums;
}
.qchip--filter .qchip-text { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; }

/* Excluded: struck, muted, and prefixed with −. Three signals, one meaning. */
.qchip.is-excluded { background: var(--surface); border-style: dashed; }
.qchip.is-excluded .qchip-text { text-decoration: line-through; opacity: 0.62; }

/* ── Command palette: groups, meta, footer, egg (#63 item 2) ──────────────
   The palette went from one flat list of illustrations to five kinds of row
   under headings, so it needs a heading, a right-hand column and a footer that
   states the keyboard model where it is used. */

.cmdk-close {
  display: flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; flex-shrink: 0;
  border: none; border-radius: 8px; cursor: pointer;
  background: transparent; color: var(--text-tertiary);
  transition: background 0.1s, color 0.1s;
}
.cmdk-close:hover { background: var(--surface2); color: var(--text-primary); }
.cmdk-close .ms { font-size: 18px; }

.cmdk-group + .cmdk-group { margin-top: 6px; }
.cmdk-group-label {
  padding: 8px 9px 4px;
  font-size: 11px; font-weight: 600; letter-spacing: 0.04em;
  color: var(--text-tertiary); text-transform: uppercase;
}

/* Stands in for .cmdk-thumb on a row that is not an illustration, at the same
   46px, so icon rows and thumbnail rows share one baseline grid. */
.cmdk-glyph {
  width: 46px; height: 46px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  border-radius: 8px; background: var(--surface2);
  color: var(--text-secondary); font-size: 20px;
}
.cmdk-result.selected .cmdk-glyph { background: rgba(255,255,255,0.7); }

.cmdk-meta {
  display: flex; align-items: center; gap: 8px; flex-shrink: 0;
  font-size: 12px; color: var(--text-tertiary);
}
.cmdk-parent { white-space: nowrap; max-width: 140px; overflow: hidden; text-overflow: ellipsis; }
.cmdk-count {
  padding: 1px 7px; border-radius: 999px;
  background: var(--surface2); font-variant-numeric: tabular-nums;
}
.cmdk-result.selected .cmdk-count { background: rgba(255,255,255,0.75); }

.cmdk-foot {
  display: flex; align-items: center; gap: 16px;
  padding: 9px 16px;
  border-top: 1px solid var(--border);
  background: var(--surface2);
  font-size: 12px; color: var(--text-tertiary);
}
.cmdk-hint { display: flex; align-items: center; gap: 5px; }
.cmdk-foot kbd {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 20px; height: 20px; padding: 0 5px;
  border: 1px solid var(--border); border-bottom-width: 2px;
  border-radius: 5px; background: var(--surface);
  font-family: var(--font); font-size: 11px; line-height: 1;
  color: var(--text-secondary);
}
/* The arrows are literal ↑ ↓ characters, deliberately — see the comment in
   index.html. An icon-font <kbd class="ms"> loses the font-family contest to
   the rule above and renders the ligature NAME as text. */

/* The ↑↑↓↓←→←→BA panel. Replaces the result list rather than opening a dialog:
   the palette is already a dialog, and a second one over it would need its own
   focus and escape handling for a joke. */
.cmdk-egg { padding: 26px 20px 30px; text-align: center; }
.cmdk-egg-img { width: 96px; height: auto; margin-bottom: 10px; }
.cmdk-egg-title { font-size: 15px; font-weight: 600; color: var(--text-primary); }
.cmdk-egg-body { font-size: 13px; color: var(--text-tertiary); margin-top: 5px; }

@media (max-width: 560px) {
  /* The footer is a keyboard legend, and this viewport has no keyboard. */
  .cmdk-foot { display: none; }
  .cmdk-parent { display: none; }
}

/* ═══════════════════════════════════════════
   EMPTY STATE
   ═══════════════════════════════════════════ */
.empty-state {
  text-align: center; padding: 80px 20px; color: var(--text-tertiary);
}
.empty-state-icon { font-size: 48px; margin-bottom: 14px; }
.empty-state-text { font-size: 17px; font-weight: 500; color: var(--text-secondary); }
.empty-state-sub  { font-size: 14px; margin-top: 8px; line-height: 1.6; }
/* Irasutoya's own illustration instead of an emoji — same 200px WebP thumbnail
   the grid uses, so it costs nothing extra to serve. */
.empty-state-img {
  width: 120px; height: 120px;
  object-fit: contain;
  margin: 0 auto 14px;
  display: block;
}
.empty-state[hidden] { display: none; }

::-webkit-scrollbar { width: 5px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.13); border-radius: 3px; }

/* ═══════════════════════════════════════════
   TOP BAR NAV LINKS
   ═══════════════════════════════════════════ */
.top-bar-links {
  display: none; /* shown via min-width query below */
  align-items: center;
  gap: 2px;
  flex: 1;
  min-width: 0;
  overflow: visible;
}
@media (min-width: 1025px) {
  .top-bar-links { display: flex; }
}
.top-bar-link {
  background: none;
  border: none;
  font-family: var(--font);
  font-size: 13px;
  font-weight: 500;
  color: var(--text-tertiary);
  cursor: pointer;
  padding: 5px 8px;
  border-radius: 6px;
  white-space: nowrap;
  transition: background 0.12s, color 0.12s;
}
.top-bar-link:hover {
  background: rgba(0,0,0,0.06);
  color: var(--text-primary);
}
.top-bar-link--web {
  color: hsl(205,62%,40%) !important;
  font-weight: 500 !important;
}
.top-bar-link--web:hover {
  background: hsl(205,62%,93%) !important;
  color: hsl(205,67%,27%) !important;
}
.top-bar-link--nature {
  color: hsl(128,42%,32%) !important;
  font-weight: 500 !important;
}
.top-bar-link--nature:hover {
  background: hsl(128,42%,91%) !important;
  color: hsl(128,42%,22%) !important;
}
.top-bar-link--sports {
  color: hsl(5,65%,38%) !important;
  font-weight: 500 !important;
}
.top-bar-link--sports:hover {
  background: hsl(5,65%,93%) !important;
  color: hsl(5,65%,27%) !important;
}
/* iPad nav dropdown — base hidden, shown at 1025-1199px */
.ipad-nav-wrap {
  display: none;
  position: relative;
  flex-shrink: 0;
}
@media (max-width: 1199px) and (min-width: 1025px) {
  .ipad-nav-wrap { display: flex; align-items: center; }
}
.ipad-nav-btn {
  background: none;
  border: none;
  font-family: var(--font);
  font-size: 13px;
  font-weight: 500;
  color: var(--text-tertiary);
  cursor: pointer;
  padding: 5px 8px;
  border-radius: 6px;
  white-space: nowrap;
  transition: background 0.12s, color 0.12s;
  display: flex;
  align-items: center;
  gap: 4px;
}
.ipad-nav-btn:hover { background: rgba(0,0,0,0.06); color: var(--text-primary); }
.ipad-nav-arrow {
  display: inline-block;
  transition: transform 0.15s;
}
.ipad-nav-wrap.open .ipad-nav-arrow { transform: rotate(180deg); }
.ipad-nav-dropdown {
  display: none;
  position: fixed;
  min-width: 220px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.12);
  padding: 6px;
  z-index: 500;
}
.ipad-nav-dropdown.open { display: block; }

.top-bar-sep {
  color: var(--border);
  font-size: 14px;
  line-height: 1;
  user-select: none;
  flex-shrink: 0;
}
/* How-to-search illustrated examples */
.hts-example {
  background: var(--hover);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 14px 16px;
  margin: 14px 0;
}
.hts-path {
  display: flex;
  align-items: center;
  gap: 5px;
  flex-wrap: wrap;
  font-size: 12px;
  color: var(--text-tertiary);
  margin-bottom: 12px;
}
.hts-path strong { color: var(--text-primary); font-weight: 700; }
.hts-path-sep { color: var(--border); font-size: 14px; }
.hts-thumbs {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}
.hts-thumb-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
}
.hts-thumb {
  width: 76px;
  height: 76px;
  object-fit: contain;
  border-radius: 10px;
  background: var(--surface);
  border: 1px solid var(--border);
  padding: 5px;
}
.hts-thumb-label {
  font-size: 11px;
  color: var(--text-tertiary);
  text-align: center;
  max-width: 76px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.hts-search-demo {
  display: flex;
  align-items: center;
  gap: 10px;
  background: var(--surface);
  border: 1.5px solid var(--accent);
  border-radius: 10px;
  padding: 8px 13px;
  margin: 10px 0 12px;
  font-size: 13px;
  color: var(--text-secondary);
}
.hts-search-demo span {
  color: var(--text-primary);
  font-weight: 500;
}
.hts-search-results {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 4px;
}
.hts-search-result {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 10px;
  background: var(--hover);
  border: 1px solid var(--border);
  border-radius: 9px;
  font-size: 12px;
}
.hts-search-result img {
  width: 36px;
  height: 36px;
  object-fit: contain;
  border-radius: 6px;
  background: var(--surface);
  flex-shrink: 0;
}
.hts-search-result-title { color: var(--text-primary); font-weight: 500; }
.hts-search-result-path { color: var(--text-tertiary); font-size: 11px; margin-top: 1px; }

/* ── Three-levels table inside how-to-search ── */
/* Search-command reference at the foot of How to Search. Two columns so the
   command and what it finds read as a pair; collapses to stacked on narrow
   screens, where a two-column table would force a horizontal scroll. */
.hts-cmd {
  width: 100%;
  border-collapse: collapse;
  margin: 14px 0 18px;
}
.hts-cmd td {
  padding: 9px 12px;
  vertical-align: top;
  border-bottom: 1px solid var(--border);
}
.hts-cmd tr:last-child td { border-bottom: none; }
.hts-cmd td:first-child { width: 1%; white-space: nowrap; }
.hts-cmd code {
  background: var(--bg-secondary);
  border-radius: 6px;
  padding: 2px 7px;
  font-size: 0.92em;
  white-space: nowrap;
}
@media (max-width: 560px) {
  .hts-cmd, .hts-cmd tr, .hts-cmd td { display: block; width: auto; }
  .hts-cmd td { border-bottom: none; padding: 2px 0; }
  .hts-cmd td:first-child { width: auto; padding-top: 10px; }
  .hts-cmd tr { border-bottom: 1px solid var(--border); padding-bottom: 8px; }
}
.hts-levels {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 14px 0 18px;
}
.hts-level {
  display: flex;
  align-items: baseline;
  gap: 10px;
  background: var(--bg-secondary);
  border-radius: 8px;
  padding: 10px 14px;
}
.hts-level-name {
  font-weight: 700;
  font-size: 13px;
  color: var(--text-primary);
  min-width: 90px;
  flex-shrink: 0;
}
.hts-level-desc {
  font-size: 13px;
  color: var(--text-secondary);
  line-height: 1.4;
}

/* ── Web links grid inside info panel ── */
.web-links-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin: 16px 0 20px;
}
.web-link-card {
  position: relative;
  display: flex; align-items: center; gap: 12px;
  padding: 14px 16px;
  background: var(--hover);
  border-radius: 14px;
  border: 1px solid var(--border);
  color: inherit;
  transition: background 0.15s, box-shadow 0.15s, transform 0.15s;
}
.web-link-card:hover {
  background: var(--surface);
  box-shadow: 0 4px 16px rgba(0,0,0,0.10);
  transform: translateY(-2px);
}
/* Stretched invisible anchor — covers the whole card, no text = no underline */
.web-link-stretch {
  position: absolute; inset: 0;
  border-radius: 14px;
}
.web-link-card-icon {
  font-size: 28px; flex-shrink: 0;
  width: 48px; height: 48px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 10px;
  background: var(--surface);
}
.web-link-card-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.web-link-card-title { font-size: 13px; font-weight: 700; color: var(--text-primary); }
.web-link-card-desc { font-size: 11px; color: var(--text-tertiary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.web-cta {
  margin-top: 16px;
  font-size: 13px; line-height: 1.7;
  color: var(--text-tertiary);
}
.web-creator-row {
  display: flex; align-items: flex-start; gap: 16px;
  margin-bottom: 18px;
}
.web-creator-img {
  width: 144px; height: auto; flex-shrink: 0;
  border-radius: 10px;
}
@media (max-width: 600px) {
  /* いらすとやへ！ — image stacks above text, bigger since it has full width */
  .web-creator-row { flex-direction: column; align-items: center; gap: 12px; }
  .web-creator-img { width: 140px; }
  /* いらすとやへ！ — link cards in a single column */
  .web-links-grid { grid-template-columns: 1fr; }
  /* つかいかた — level rows stack title above description */
  .hts-level { flex-direction: column; gap: 4px; }
  .hts-level-name { min-width: unset; }
  /* このプロジェクトについて — avatar stays horizontal, image shrinks */
  .info-avatar-wrap { flex-wrap: wrap; gap: 12px; padding: 12px; }
  .info-avatar { width: 72px; border-radius: 8px; }
  .info-avatar-name-jp { font-size: 28px; }
  .info-avatar-name { font-size: 13px; }
  .info-avatar-email { font-size: 12px; margin-top: 4px; }
  .info-avatar-divider { width: 100%; height: 1px; align-self: unset; margin: 4px 0; }
  .info-avatar-links { width: 100%; }
  /* General — no horizontal scroll in info panels */
  .info-panel-body { overflow-x: hidden; }
}
.web-creator-text {
  font-size: 13.5px; line-height: 1.7;
  color: var(--text-secondary);
  padding-top: 2px;
}

/* ═══════════════════════════════════════════
   INFO OVERLAY PANEL
   ═══════════════════════════════════════════ */
.info-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.45);
  z-index: 300;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
.info-overlay.open { display: flex; }
.info-panel {
  background: var(--surface);
  border-radius: 16px;
  width: 100%;
  max-width: 680px;
  max-height: 88vh;
  display: flex;
  flex-direction: column;
  box-shadow: 0 20px 60px rgba(0,0,0,0.22);
  overflow: hidden;
}
.info-panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 20px 24px 16px;
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}
.info-panel-title {
  font-size: 17px;
  font-weight: 700;
  color: var(--text-primary);
  letter-spacing: -0.2px;
}
.info-close {
  width: 30px; height: 30px;
  border-radius: 50%;
  border: none;
  background: #e8e8e6;
  color: var(--text-secondary);
  cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  transition: background 0.15s;
  flex-shrink: 0;
}
.info-close .ms { font-size: 18px; line-height: 1; }
.info-close:hover { background: #d8d8d6; }
.info-panel-body {
  overflow-y: auto;
  padding: 24px 48px 48px;
  font-size: 14px;
  line-height: 1.75;
  color: var(--text-secondary);
}
@media (max-width: 600px) {
  .info-panel-body { padding: 20px 20px 36px; }
}
.info-panel-body h2 {
  font-size: 15px;
  font-weight: 700;
  color: var(--text-primary);
  margin: 20px 0 6px;
}
.info-panel-body h2:first-child { margin-top: 0; }
.info-panel-body p { margin: 0 0 12px; }
.info-panel-body ul {
  margin: 0 0 12px;
  padding-left: 20px;
}
.info-panel-body li { margin-bottom: 6px; }
.info-panel-body a {
  color: var(--accent);
  text-decoration: none;
}
.info-panel-body a:hover { text-decoration: underline; }
.info-panel-body hr {
  border: none;
  border-top: 1px solid var(--border);
  margin: 20px 0;
}

/* About / Contact avatar */
.info-avatar-wrap {
  display: flex;
  align-items: center;
  gap: 20px;
  margin-bottom: 20px;
  padding: 16px;
  background: var(--bg);
  border-radius: 12px;
}
.info-avatar {
  width: 140px;
  height: auto;
  border-radius: 12px;
  flex-shrink: 0;
}
.info-avatar-text {
  flex: 1;
  min-width: 0;
}
.info-avatar-name-jp {
  font-size: 42px;
  font-weight: 700;
  color: var(--text-primary);
  letter-spacing: -1px;
  line-height: 1.1;
}
.info-avatar-name {
  font-size: 14px;
  font-weight: 500;
  color: var(--text-secondary);
  margin-top: 4px;
  letter-spacing: 0;
}
.info-avatar-email {
  font-size: 13px;
  color: var(--accent);
  margin-top: 6px;
}
.info-avatar-divider {
  width: 1px;
  align-self: stretch;
  background: var(--border);
  flex-shrink: 0;
  margin: 0 4px;
}
.info-avatar-links {
  display: flex;
  flex-direction: column;
  gap: 6px;
  flex: 1;
}
.info-social-btn {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 13px;
  border-radius: 10px;
  background: var(--hover);
  border: 1px solid var(--border);
  cursor: pointer;
  transition: background 0.15s, box-shadow 0.15s, transform 0.15s;
}
.info-social-btn:hover {
  background: var(--surface);
  box-shadow: 0 3px 10px rgba(0,0,0,0.09);
  transform: translateY(-1px);
}
.info-social-icon {
  font-size: 18px;
  flex-shrink: 0;
  width: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-style: normal;
}
.info-social-name {
  font-size: 13px;
  font-weight: 700;
  color: var(--text-primary);
}
.info-social-dash {
  color: var(--text-tertiary);
  font-size: 12px;
}
.info-social-url {
  font-size: 12px;
  color: var(--text-tertiary);
}
.info-tag {
  display: inline-block;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 2px 8px;
  font-size: 12px;
  color: var(--text-tertiary);
  margin-right: 4px;
  margin-bottom: 4px;
}

/* ═══════════════════════════════════════════
   HUB BADGE (illustration grid cards)
   ═══════════════════════════════════════════ */
.hub-badge {
  position: absolute; top: 6px; right: 6px; z-index: 2;
  background: var(--hub); color: white;
  font-size: 9px; font-weight: 700;
  padding: 2px 7px; border-radius: 10px;
  letter-spacing: 0.03em;
  border: 1.5px solid white;
  /* no box-shadow — shadow is a painted property; badge border is sufficient */
  pointer-events: none;
}

/* ═══════════════════════════════════════════
   HUB CAROUSEL (modal)
   ═══════════════════════════════════════════ */
/* THE SET BAR. Was `.carousel-strip` under the illustration in a one-column
   card; now the bottom half of the illustration column, sharing --bottom-row
   with the action row so the rule above each lands on one line. */
.set-bar {
  border-top: 1px solid var(--border);
  background: var(--hub-light);
  flex-shrink: 0;
  min-height: var(--bottom-row); box-sizing: border-box;
  display: flex; flex-direction: column; justify-content: center;
  padding: 12px 14px 14px 22px;
}
.set-head { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
.set-pill {
  background: var(--hub); color: white;
  font-size: 9px; font-weight: 700;
  padding: 2px 8px; border-radius: 10px;
  letter-spacing: 0.04em; text-transform: uppercase;
}
.set-name { font-size: 12.5px; color: var(--hub); font-weight: 700; flex: 1;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* POSITION, not size — the bar already shows how many by showing them; what a
   scrolling strip hides is which one you are on. */
.set-count { margin-left: auto; font-size: 11.5px; color: var(--text-tertiary); }
.set-desc {
  font-size: 11.5px; color: var(--hub); line-height: 1.45;
  padding: 0 0 6px;
}

/* The gutter is the arrows' room, reserved whether or not they show. */
.set-viewport { position: relative; padding: 0 30px; }
.set-selector-wrap { overflow: hidden; }
.set-selector { display: flex; gap: 8px; transition: transform 0.25s ease; justify-content: flex-start; }

.carousel-item {
  flex-shrink: 0; width: 76px; cursor: pointer;
  border-radius: 10px; overflow: hidden;
  border: 2px solid transparent;
  transition: border-color 0.15s, transform 0.1s;
  background: var(--surface);
  box-shadow: var(--shadow-card);
}
.carousel-item:hover { transform: translateY(-1px); box-shadow: var(--shadow-card-hover); }
.carousel-item.active { border-color: var(--hub); }
.carousel-item img { width: 100%; aspect-ratio: 1; object-fit: contain; background: var(--hub-light); padding: 6px; display: block; }
.carousel-item-label {
  padding: 3px 4px 5px; font-size: 9px; font-weight: 500;
  text-align: center; color: var(--text-tertiary); background: var(--surface);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.carousel-item.active .carousel-item-label { color: var(--hub); }
/* SELECTION IS A BORDER, NEVER AN OUTLINE — an outline paints outside the
   border box and an overflow ancestor clips it. .carousel-item has always
   used a border; keep it that way. */

.set-arrow {
  position: absolute; top: 28px; transform: translateY(-50%);
  width: 24px; height: 24px; border-radius: 50%;
  background: var(--surface); border: 1px solid var(--border);
  box-shadow: 0 2px 6px rgba(0,0,0,0.1); cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  font-size: 13px; color: var(--text-secondary); transition: opacity 0.15s;
  font-family: var(--font);
}
.set-arrow:hover { background: #f0f0f0; }
/* Faded, never removed — the gutter must stay reserved or the strip jumps
   sideways the moment it reaches an end. */
.set-arrow.hidden { opacity: 0; pointer-events: none; }
.set-arrow.left { left: 0; }
.set-arrow.right { right: 0; }

/* ── Reading level (#69) ───────────────────────────────────────────────────
   Kasane (襲の色目) palette, from Nepô's reference sheet, deepened only as far
   as legibility as TEXT requires. Five rules, and each earns its place on
   frequency measured over every title and description:
     indigo  hiragana              34.4% of tokens · 藍色, added — Kasane has no blue
     purple  katakana              18.2% of tokens
     green   の                    46.4% of particles
     gold    every other particle  53.6% of particles
     red     kanji the reader KNOWS — the grade selector
   Punctuation stays ink. Latin has no colour: Roman letters in a kana line are
   already unmistakable.
   Red is the interesting one — the other four mark what a thing IS, red marks
   what the reader CAN DO. Moving up a grade turns more of the page red: a
   progress bar made of the text itself.
   (This paragraph said PURPLE until 2026-08-05, describing the arrangement the
   swap below replaced on 2026-08-02. It read as current for three days and a
   session nearly documented "purple is the progress bar" on the strength of it,
   catching the error only because the rendered ladder came out red. If the
   colours are ever swapped again, this paragraph is the one that goes stale.) */
:root{
  --kx-hira:#4a5da5; --kx-kata:#a868b8; --kx-no:#7fb84f;
  --kx-pt:#d9a038;   --kx-grade:#d93b2b;
  /* Swapped 2026-08-02: red went to the grade selector and purple to katakana.
     Red is the warmest thing on the line, and the kanji a reader HAS LEARNED is
     the one mark meant to be noticed — it is a reward, not a label. Katakana is
     18.2% of tokens and only needs to read as "different script", which purple
     does without competing. */
  --kx-gap:0.35em;
}
.kx-gap{margin-inline-end:var(--kx-gap)}
.kx-h{color:var(--kx-hira)}
.kx-k{color:var(--kx-kata)}
.kx-no{color:var(--kx-no)}
.kx-pt{color:var(--kx-pt)}
.kx-g{color:var(--kx-grade);font-weight:500}
/* ── The こども menu, in two surfaces ──────────────────────────────────────
   Settled 2026-08-02 in playground/reading-menu.html, which produced three
   surfaces rather than one winner. Two of them live here — illustrated cards
   at ≥601px, the compact strip below it — and the third (the ladder) is on the
   つかいかた page, where explaining is the job.
   ONE ORDER on all three: ひらがな → 1–6年生 → 漢字 LAST, because 漢字 is the
   level containing every kanji and belongs above 6年生 rather than beside
   ひらがな. */
.read-level{position:relative;display:inline-block}
/* The button reads こども — the name of the feature, not the current state, and
   not a difficulty setting: it is a way of reading the encyclopedia.
   It no longer needs a FIXED width. It did while the label changed with the
   state (かな / 1年 / 漢字), because a Latin digit is narrower than a full-width
   kanji and picking a year resized the button and dragged the top bar; the
   width was pinned to stop that. A constant label cannot jump, so the pin is
   gone with the reason for it. If the label is ever made variable again, put
   the width back. */
.read-level-btn{font:inherit;font-size:13px;font-weight:700;text-align:center;
  padding:5px 10px;border-radius:9px;border:1px solid var(--border,#e5e0d8);
  background:var(--card,#fff);color:var(--text,#3a3632);cursor:pointer;line-height:1.2}
.read-level-btn.is-on{background:var(--kx-grade);border-color:var(--kx-grade);color:#fff}
.read-level-menu{display:none;position:absolute;top:calc(100% + 6px);right:0;z-index:60;
  background:var(--card,#fff);border:1px solid var(--border,#e5e0d8);border-radius:14px;
  padding:10px;box-shadow:0 12px 32px rgba(0,0,0,.14)}
.read-level-menu.open{display:block}

/* ── cards — desktop and tablet ─────────────────────────────────────────── */
.read-cards{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;width:304px}
/* Selection LIFTS the card, it does not repaint it — this file's own idiom
   (.big-card, .subcat-card). A checked rule that set a background would beat
   .rc-kana's tint on specificity and flatten the one card meant to stand out
   at the moment it is chosen; height is a channel no background rule can win. */
.rc{border:2px solid transparent;border-radius:12px;background:var(--hover,#f7f4ef);
  padding:8px 6px;cursor:pointer;text-align:center;font:inherit;color:inherit;
  box-shadow:var(--shadow-card);
  transition:border-color .12s,background .15s,box-shadow .15s,transform .15s}
.rc:hover{transform:translateY(-1px);box-shadow:var(--shadow-card-hover)}
.rc:focus-visible{outline:2px solid var(--kx-grade);outline-offset:2px}
.rc[aria-checked="true"]{border-color:var(--kx-grade);transform:translateY(-2px);
  box-shadow:var(--shadow-card-hover)}
/* LAST on purpose: :hover and [aria-checked] are both class-plus-pseudo/attr,
   so among equals the later rule wins. Put the press above either and a
   selected card stops responding to being pressed. */
.rc:active{transform:scale(.99)}
/* MAX-WIDTH IS LOAD-BEARING. gakunen1–6 are 200×86 BANNERS, not squares —
   capped by height alone one renders far wider than its column and bursts the
   panel. Height caps the tall art, width caps the wide art. */
.rc-art{display:flex;align-items:center;justify-content:center;overflow:hidden;min-height:38px}
.rc-art img{max-width:100%;max-height:56px;object-fit:contain}
.rc-mode{grid-column:1 / -1;display:flex;flex-direction:column;align-items:center;
  justify-content:center;gap:1px;padding:11px 10px}
.rc-name{font-size:15px;font-weight:800;line-height:1.35}
.rc-sub{font-size:11.5px;font-weight:600;color:var(--muted,#8a8078);line-height:1.4}
/* ひらがな is where a child who cannot read kanji starts, so it is not one of
   eight equal boxes. The tint is its OWN level colour at low alpha — standing
   out costs no new colour. */
.rc-kana{background:rgba(255,204,128,.30)}
.rc-kana:hover{background:rgba(255,204,128,.46)}
.rc-kana .rc-sub{color:#a8763a}

/* ── strip — phones ─────────────────────────────────────────────────────── */
.read-strip{display:flex;flex-direction:column;gap:2px}
.read-strip button{border:0;background:none;cursor:pointer;font:inherit;font-size:13px;
  font-weight:700;padding:8px 14px;border-radius:9px;color:var(--text,#3a3632);
  min-width:8.5em;text-align:center;white-space:nowrap}
.read-strip button:hover{background:var(--hover,#f4f1ec)}
/* selected background/colour are set inline from LEVEL_COLOR — no colour here,
   or this rule would fight the inline one on every deselect */
.read-strip button[aria-checked="true"]{font-weight:800}
.read-strip .read-sep{height:1px;background:var(--border,#e8e2da);margin:3px 5px}

/* Which surface shows. 600px is this file's existing phone boundary. */
.read-level-menu .read-strip{display:none}
@media (max-width:600px){
  .read-level-menu .read-cards{display:none}
  .read-level-menu .read-strip{display:flex}
}

/* ── ladder — the つかいかた page ────────────────────────────────────────────
   Six years as a climb, which is how Japanese learning books lay the years out.
   Rungs at or below the chosen level fill in their own colour; those above stay
   grey — the years still to come. */
.read-ladder{display:flex;align-items:flex-end;gap:5px;overflow-x:auto;
  padding:4px 0 6px;margin:6px 0 18px}
/* THE RUNGS MUST SHRINK, and min-width was stopping them. The panel is
   max-width:680px with 48px of padding each side — 584px of content — while
   eight rungs at min-width:70px plus seven 6px gaps wanted 602px. It overflowed
   by 18px, which cropped 漢字: the one rung the whole climb builds towards.
   flex:1 has basis 0 already, so removing the floor is the entire fix — 584px
   then gives about 68px a rung, and ひらがな, the longest label, measures roughly
   44px at 11px. overflow-x:auto stays for phones, where the media query below
   restores a floor and scrolling is the honest answer. */
.read-ladder .rung{flex:1;min-width:0;border:0;background:none;cursor:pointer;
  padding:0;text-align:center;transition:transform .15s}
.read-ladder .rung img{width:100%;height:44px;object-fit:contain;margin-bottom:5px}
/* ひらがな's rung gets the column's full width. study_nihongo is 191×200 where
   gakunen1–6 are 200×86, so inside a 44px box the near-square drawing renders
   about 42px wide against their 100+, and the first rung of the climb looked
   like an afterthought. */
.read-ladder .rung-kana img{height:auto;max-height:none}
.read-ladder .rung-bar{display:block;border-radius:10px 10px 0 0;
  transition:background .15s,height .15s,opacity .15s}
.read-ladder .rung small{display:block;font-size:11px;color:var(--muted,#8a8078);
  margin-top:5px;white-space:nowrap}
/* Phones cannot fit eight rungs — the body padding drops to 20px but the
   viewport drops much further, so here the floor comes back and the ladder
   scrolls on purpose rather than crushing ひらがな into two lines. */
@media (max-width:600px){ .read-ladder{gap:4px} .read-ladder .rung{min-width:52px} }


/* ═══════════════════════════════════════════
   MODAL — THE DETAILS PANEL (#76)
   ═══════════════════════════════════════════
   The back face's successor, and a SIBLING rather than a reverse: it slides
   over the words and stops at the column rule, so the illustration, the set bar
   and the four buttons stay visible while you read the metadata. */
.details-panel {
  position: absolute; inset: 0;
  /* ABOVE the header (z 5): at a lower z-index the language flag floats on top
     of the panel's own heading — the one control the panel is not showing. */
  z-index: 7;
  background: var(--surface);
  border-left: 1px solid var(--border);
  display: flex; flex-direction: column;
  transform: translateX(100%);
  transition: transform 0.34s cubic-bezier(0.22,0.61,0.36,1);
  will-change: transform;
  box-shadow: -16px 0 34px rgba(0,0,0,0.09);
}
.modal-overlay.details-open .details-panel { transform: none; }
@media (prefers-reduced-motion: reduce) { .details-panel { transition: none; } }
.panel-head {
  display: flex; align-items: center; gap: 8px;
  padding: 12px 30px; border-bottom: 1px solid var(--border);
}
.panel-head b { font-size: 13px; flex: 1; }
/* A WORD, NOT A GLYPH — the modal's own close is already visible in the other
   corner, and two identical marks would mean two different things. Black
   because every other button carries a domain hue that means something, and
   leaving a panel is not a domain. */
.modal-action-btn.act-close { background: var(--text-primary) !important; color: #fff !important;
  border-color: var(--text-primary) !important; flex: 0 0 auto; }
.panel-body { flex: 1; overflow-y: auto; padding: 16px 30px 20px; }
.info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 18px; }

/* ═══════════════════════════════════════════
   MODAL — PHONE (#76)
   ═══════════════════════════════════════════
   Below ~760px there is not room for two columns of real width; forcing it
   gives a 150px illustration beside 150px of text. Stacked instead. */
@media (max-width: 760px) {
  .modal-scene {
    --mw: 100%; --mh: auto;
    flex-direction: column; max-height: 86vh;
    /* ONE SCROLLER, AND IT IS THE MODAL. Two columns scrolling independently is
       right side by side; stacked it reads as the illustration being stuck
       while a small window of text moves under it. overflow-x stays hidden so
       the set selector can scroll sideways without the sheet following it. */
    overflow-y: auto; overflow-x: hidden;
  }
  .ill-col  { flex: none; border-right: none; border-bottom: 1px solid var(--border); }
  .text-col { overflow: visible; }
  .artwork  { min-height: 240px; }
  .panel-bounds { min-height: auto; }
  /* Not a scroll box here — the 56px of top padding clears a header that, on
     the phone, sits over the ILLUSTRATION rather than over the words, and it
     was pushing the title out of an 85px window from the inside. */
  .reading { flex: none; overflow: visible; justify-content: flex-start; padding: 20px 24px 14px; }
  /* The picture's three corners: close ↖, backgrounds ↗, language ↘. */
  .head-ill  { flex: 1; }
  .head-text { display: none; }
  .ill-foot  { display: flex; }
  .details-panel { width: 100%; }
}


/* ═══════════════════════════════════════════
   MODAL — FILE INFO ON THE FRONT, FACETS IN THE PANEL (#76)
   ═══════════════════════════════════════════ */
/* One line, directly above the tags. Quiet on purpose: these four facts are a
   footnote to the illustration, and anything louder would compete with the
   description above it. Wraps rather than truncates — a long filename is the
   one fact here that can be long, and a clipped filename is useless. */
.file-strip {
  display: flex; flex-wrap: wrap; gap: 4px 14px;
  justify-content: center;
  padding: 10px 30px 0;
  font-size: calc(11.5px * var(--text-scale)); color: var(--text-tertiary);
}
.file-fact { display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; }
.file-fact .ms { font-size: 14px; }
/* The filename may be long; it alone may wrap inside itself. */
.file-fact:first-child { white-space: normal; word-break: break-all; }

/* The "+N" chip that stands in for the tags beyond the seventh. Outlined
   rather than filled, so it reads as a control and not as one more tag. */
.modal-tag--more {
  background: transparent !important;
  border: 1px dashed var(--border) !important;
  color: var(--text-tertiary) !important;
}
.modal-tag--more:hover { border-color: var(--text-secondary) !important; }

/* Facets in the panel — a facet name, then the tags that put this illustration
   in it. The name is a LABEL, not a chip: only the tags are clickable, because
   only the tags are searchable. */
.modal-facets { display: flex; flex-direction: column; gap: 8px; }
.facet-row { display: flex; flex-wrap: wrap; align-items: center; gap: 5px; }
.facet-name {
  font-size: 10.5px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--text-tertiary);
  margin-right: 2px;
}
