/* Japanese Language Schools — directory stylesheet.
   Ported from the website-minimal design exploration (see
   ../../../../website-minimal/DESIGN.md): paper white, sumi ink, aizome-indigo
   accent, Mincho serif for Japanese names, hairline "dictionary entry" rows.
   No webfont requests (system font stacks only), one small file. Every page
   renders and works identically with JavaScript disabled; the site's five scripts (theme,
   search-shortcut, map-loader, courses, filters-collapse) are progressive enhancement that
   never block content or touch cookies.

   Theme: light by default, dark via OS preference (prefers-color-scheme) or a
   manual [data-theme] set by the toggle. */

:root {
  color-scheme: light;
  --paper: #ffffff;
  --ink: #1b1c1e;
  --muted: #5c5e63;   /* ink-2 */
  --faint: #6f7176;   /* ink-3 — WCAG AA (TASK-141): 4.88:1 on paper, 4.51:1 on wash */
  --line: #e4e3df;    /* hairline */
  --control-border: #85878c; /* form-control boundary (TASK-895, WCAG 1.4.11): 3.59:1 on paper */
  --accent: #14538c;  /* 藍 aizome indigo */
  --accent-ink: #ffffff;
  --visited: #7b2d9e; /* visited link (TASK-783): 7.75:1 on paper, 7.16:1 on wash */
  --wash: #f6f6f4;
  --ok: #2c6e49;
  --closed: #9a3b3b;
  --radius: 8px;
  --serif: "Hiragino Mincho ProN", "Yu Mincho", "Noto Serif JP", serif;
  --sans: -apple-system, BlinkMacSystemFont, "Hiragino Kaku Gothic ProN",
          "Yu Gothic", "Noto Sans JP", "Segoe UI", Roboto, Meiryo, sans-serif;
}

:root[data-theme="dark"] {
  color-scheme: dark;
  --paper: #141518;
  --ink: #e9e8e4;
  --muted: #a7a9ad;
  --faint: #83858b;   /* ink-3 — WCAG AA (TASK-141): 4.95:1 on paper, 4.52:1 on wash */
  --line: #2b2d31;
  --control-border: #6b6e75; /* TASK-895: 3.58:1 on paper */
  --accent: #6ea8d8;
  --accent-ink: #10161c;
  --visited: #c08cf0; /* visited link (TASK-783): 7.17:1 on paper, 6.56:1 on wash */
  --wash: #1c1e22;
  --ok: #7dc49a;
  --closed: #d08a8a;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --paper: #141518;
    --ink: #e9e8e4;
    --muted: #a7a9ad;
    --faint: #83858b;   /* ink-3 — WCAG AA (TASK-141): 4.95:1 on paper, 4.52:1 on wash */
    --line: #2b2d31;
  --control-border: #6b6e75; /* TASK-895: 3.58:1 on paper */
    --accent: #6ea8d8;
    --accent-ink: #10161c;
    --visited: #c08cf0; /* visited link (TASK-783): 7.17:1 on paper, 6.56:1 on wash */
    --wash: #1c1e22;
    --ok: #7dc49a;
    --closed: #d08a8a;
  }
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }

/* Strict kinsoku for Japanese text (TASK-782). The default `line-break: auto`
   lets a wrapped line start with the prolonged sound mark or a small kana
   (measured at 375px: about 1.5% of wrapped /ja lines). `strict` forbids
   those breaks. Keyed on :lang(ja) rather than the /ja pages so it also covers
   lang="ja" runs (official school names) on /en pages and leaves English text
   alone. */
:lang(ja) { line-break: strict; }

body {
  margin: 0;
  color: var(--ink);
  background: var(--paper);
  font-family: var(--sans);
  font-size: 0.9375rem;
  line-height: 1.6;
}

main {
  max-width: 64rem;
  margin: 0 auto;
  padding: 1.5rem 1.5rem 4rem;
}

a { color: var(--accent); text-decoration: none; }
/* Visited links (TASK-783). The directory's main journey is opening many
   schools from paginated lists, so an opened link reads in --visited. Browsers
   limit :visited to colour properties, so colour is the whole cue and nothing
   can shift. This rule covers every link that takes the default accent colour
   (prose, fact lists, tables, facet toggles); component links that set their
   own colour opt in next to their rule (school rows, prefecture and region
   links, pagination). Site chrome (masthead, breadcrumb, footer) and
   button-like links (skip link, map button, filter reset) keep their colour:
   they are controls, not places a visitor ticks off. */
a:visited { color: var(--visited); }
a:hover { text-decoration: underline; }

/* WCAG 1.4.1 Use of Color (TASK-141): a link sitting inside running text must
   be distinguishable without relying on color, so links embedded in prose
   paragraphs are underlined. Standalone component links (nav, dictionary rows,
   facet toggles, breadcrumb, pagination) are not "in a text block" and keep
   the clean underline-on-hover treatment. */
p a, dd a { text-decoration: underline; }

h1 { font-size: 1.7rem; line-height: 1.3; margin: 0.5rem 0; }
h2 { font-size: 1.25rem; margin: 2rem 0 0.75rem; }
h3 { font-size: 1.05rem; margin: 1rem 0 0.5rem; }

:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 2px; }

/* "(opens in a new tab)" cue inside a target=_blank link (TASK-774). */
.new-tab-hint { font-size: 0.85em; white-space: nowrap; }

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Skip link (TASK-141): first focusable element on every page. Visually
   hidden until it receives keyboard focus, then anchored top-left over the
   masthead so a keyboard/SR user can jump straight to the main content. */
.skip-link {
  position: absolute;
  left: 0.5rem;
  top: -3rem;
  z-index: 100;
  padding: 0.5rem 1rem;
  background: var(--accent);
  color: var(--accent-ink);
  border-radius: var(--radius);
  font-size: 0.9rem;
  transition: none;
}
.skip-link:visited { color: var(--accent-ink); }
.skip-link:focus { top: 0.5rem; text-decoration: none; }
main:focus { outline: none; }
/* BUG-UI-009: the "Skip to results" target is focused programmatically only. */
#results:focus { outline: none; }

.caption, .not-listed { color: var(--muted); font-size: 0.9rem; }

/* ---------- Masthead (header) & footer ---------- */

.site-header {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.5rem;
  align-items: center;
  justify-content: space-between;
  max-width: 64rem;
  margin: 0 auto;
  padding: 1.5rem 1.5rem 0.9rem;
}

/* flex-wrap so the primary nav's links drop below the wordmark on very narrow
   viewports instead of forcing horizontal page overflow (TASK-141 overflow
   sweep: the wordmark + links were ~411px wide and could not shrink under
   320/390px). */
.site-header nav { display: flex; align-items: center; gap: 0.5rem 1.25rem; flex-wrap: wrap; }

.site-header ul {
  display: flex;
  gap: 1rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

.site-header a { text-decoration: none; }

/* BUG-UI-038: language switcher + theme toggle grouped at the right edge. The
   toggle precedes the switcher in the markup, so revealing it (theme.js) grows
   the group leftwards and the switcher never moves. Above 720px the group is
   pushed to the right edge (margin-left: auto) so that, even when the header
   wraps and the group sits alone on its row, the switcher stays put. At
   <=720px the group takes its own full row with the switcher at the start and
   the toggle (ordered last) at the end, as the flat header laid them out. */
.header-controls { display: flex; align-items: center; gap: 0.5rem 1.5rem; margin-left: auto; }
@media (max-width: 720px) {
  .header-controls { flex: 1 1 100%; justify-content: space-between; margin-left: 0; }
  .header-controls .theme-toggle { order: 1; }
}

/* Header search (TASK-704): compact one-line GET form; the submit button is
   visually hidden-free but small so the header stays on one row on desktop
   and wraps below the nav on narrow viewports. */
.header-search { display: flex; align-items: center; gap: 0.4rem; flex: 1 1 12rem; min-width: 0; max-width: 20rem; margin: 0; }
.header-search input[type="search"] { flex: 1 1 auto; min-width: 0; font: inherit; font-size: 0.95rem; padding: 0.35rem 0.6rem; }
.header-search input[type="submit"] { font: inherit; font-size: 0.9rem; padding: 0.35rem 0.8rem; }

.site-header .brand {
  font-family: var(--serif);
  font-size: 1.7rem;
  letter-spacing: 0.05em;
  color: var(--ink);
  font-weight: 500;
  /* TASK-894: at a 24px browser default the wordmark is wider than a 320px
     viewport; let it break rather than overflow the page. */
  overflow-wrap: anywhere;
}
.site-header .brand:hover { text-decoration: none; }

.site-header nav ul a { color: var(--muted); font-size: 0.95rem; }
.site-header nav ul a:hover { color: var(--ink); }

/* Current section / current locale (TASK-692): the layout sets aria-current
   on the matching primary-nav link ("page" on the index, "true" below it) and
   on the current-locale link, the same convention as breadcrumbs, pagination
   and facet items. Ink colour + an underline marks it without relying on
   colour alone; neither changes the link box, so the mobile tap-target
   sizing below (BUG-UI-006/TASK-591) is untouched. (The old
   `.locale-switch a` colour/size rule that sat here was dead: it was always
   outranked by `.site-header nav ul a` above, (0,1,3) vs. (0,1,1).) */
.site-header nav ul a[aria-current] {
  color: var(--ink);
  text-decoration: underline;
  text-decoration-thickness: 2px;
  text-underline-offset: 0.35em;
}

/* Dark-mode toggle (progressive enhancement — see app/assets/javascripts/theme.js). Invisible by
   default; theme.js makes it visible only when JS runs, so no-JS users never see a
   dead control. It keeps its box (visibility, not display:none; BUG-UI-061) so the
   wrapped header row does not grow when the script reveals it. */
.theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  visibility: hidden;
  font: inherit;
  font-size: 1.05rem;
  line-height: 1;
  color: var(--muted);
  background: none;
  border: 1px solid var(--line);
  border-radius: 999px;
  width: 2rem;
  height: 2rem;
  /* BUG-UI-037: the generic button padding must not widen the 2rem circle. */
  padding: 0;
  cursor: pointer;
}
.theme-toggle:hover { color: var(--ink); border-color: var(--faint); }
/* Outline control with no fill for a filter to darken: press fills it (TASK-783). */
.theme-toggle:active { background: var(--wash); filter: none; }
.theme-toggle.is-ready { visibility: visible; }

.site-footer {
  border-top: 1px solid var(--line);
  margin-top: 2rem;
  padding: 1.75rem 1.5rem;
  color: var(--faint);
  font-size: 0.8rem;
  line-height: 1.7;
  text-align: center;
}

.footer-links ul {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  justify-content: center;
  list-style: none;
  margin: 0.75rem 0;
  padding: 0;
}

.footer-links a, .footer-disclaimer a { color: var(--muted); }
/* "/" shortcut off switch (BUG-UI-012/TASK-691) -- search-shortcut.js
   unhides it; no display rule here so the [hidden] attribute keeps working. */
.shortcut-pref { color: var(--muted); font-size: 0.9rem; }
.shortcut-pref input { margin: 0 0.4rem 0 0; vertical-align: middle; }
/* BUG-UI-049/TASK-1017: a keyboard-shortcut preference is meaningless when
   the primary pointer is a finger (phones, tablets), so the control is not
   shown there even after search-shortcut.js unhides it. Hybrid devices whose
   primary pointer is a mouse/trackpad still get it. */
@media (pointer: coarse) {
  .shortcut-pref { display: none; }
}

/* ---------- Home hero & search ---------- */

.hero { padding: 1.5rem 0 0.5rem; }
.hero h1 { font-family: var(--serif); font-weight: 500; font-size: 1.9rem; }
.hero p { color: var(--muted); margin: 0.25rem 0 1.25rem; }

.search-form {
  display: flex;
  gap: 0.75rem;
  align-items: flex-end;
  margin: 1rem 0 1.5rem;
}

.search-form input[type="search"] {
  flex: 1 1 auto;
  min-width: 0; /* TASK-894: shrink below the intrinsic width at large default fonts */
  font: inherit;
  font-size: 1.1rem;
  color: var(--ink);
  background: var(--paper);
  border: none;
  border-bottom: 2px solid var(--ink);
  border-radius: 0;
  padding: 0.6rem 0.15rem;
}
.search-form input[type="search"]::placeholder { color: var(--faint); }
.search-form input[type="search"]:focus { border-bottom-color: var(--accent); }
/* TASK-895: no `outline: none` above -- keyboard focus keeps the global ring. */

button, input[type="submit"] {
  padding: 0.5rem 1.1rem;
  border: 1px solid var(--accent);
  border-radius: 999px;
  background: var(--accent);
  color: var(--accent-ink);
  font: inherit;
  font-size: 0.9rem;
  cursor: pointer;
}
button:hover, input[type="submit"]:hover { filter: brightness(1.08); }
/* Pressed feedback (TASK-783): hover lightens, a press darkens. Declared after
   :hover so it wins while the pointer is still over the control. */
button:active, input[type="submit"]:active { filter: brightness(0.88); }

.search-hint { color: var(--muted); }
/* TASK-699: `.hint` (compare page intro, admin/corrections form hints) had no
   rule and rendered as plain body text; muted like .search-hint. */
.hint { color: var(--muted); }

/* ---------- Browse layout (filters sidebar + results) ---------- */

.browse-layout {
  display: grid;
  grid-template-columns: 15rem minmax(0, 1fr);
  gap: 2.25rem;
  align-items: start;
  margin-top: 1.25rem;
}

/* ---------- Region / prefecture browse ---------- */

.region { margin-bottom: 1rem; }
.region h2, .region h3 { font-size: 1.05rem; margin: 1.25rem 0 0.6rem; }
.region a { color: var(--ink); }
.region a:visited { color: var(--visited); }

.prefecture-list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

.prefecture-list a {
  display: inline-block;
  padding: 0.3rem 0.75rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--wash);
  text-decoration: none;
  font-size: 0.92rem;
  color: var(--ink);
}
.prefecture-list a:visited { color: var(--visited); }
.prefecture-list a:hover { border-color: var(--accent); text-decoration: none; }
.prefecture-list .count { color: var(--faint); font-size: 0.85em; }

/* TASK-140: choropleth prefecture map (ported from website-minimal
   DESIGN.md §5.3; see README.md for the Geolonia SVG's MIT attribution).
   Colors are all CSS custom properties, so light/dark theming is automatic —
   no separate dark-mode rules needed here. The container is aria-hidden
   (see app/views/prefectures/index.html.erb); shapes are tabindex="-1"
   links, clickable/tappable but never keyboard-focusable, so the region
   list below remains the sole keyboard/screen-reader path. */
/* TASK-287: block margins only — the INLINE margins are the `.center-block`
   utility's business (a `margin` shorthand here would reset its `auto`). */
.pref-map { margin-block: 0.5rem 1.5rem; max-width: 32rem; }
.pref-map svg { width: 100%; height: auto; display: block; }
.pref-map .prefecture {
  fill: var(--wash);
  stroke: var(--line);
  stroke-width: 0.8;
}
.pref-map a { cursor: pointer; }
.pref-map a:hover .prefecture, .pref-map a:focus .prefecture { fill: var(--accent); fill-opacity: 0.7; }
/* Choropleth intensity buckets (0 = fewest schools .. 4 = most), sqrt-scaled
   server-side (PrefectureMapHelper) so Tokyo's ~500 doesn't flatten every
   other prefecture to near-white. color-mix keeps every step themed off the
   same custom properties instead of five hardcoded hex pairs.
   TASK-687 / BUG-UI-008: with the live counts ~26 of 47 prefectures land in
   chor-0, which used to be the bare page wash + --line hairline (1.08:1 fill,
   1.28:1 outline) — half the map looked missing. Every step is now tinted,
   and each shape is kept >= 3:1 against --paper in both themes by its fill
   OR its outline:
     - chor-0/1 (light fills, 1.3-2.0:1) get a mid-grey outline (faint 85%
       over paper: 3.63:1 light / 3.91:1 dark) that also sets them off from
       same-bucket neighbours (2.0-2.9:1 against their fills);
     - chor-2..4 fills are themselves >= 3:1 (3.07/4.55/6.6 light,
       3.4/4.78/6.3 dark), so they keep a paper-coloured gap line, which
       separates same-bucket neighbours better than grey would on a dark fill.
   Steps stay monotonic (adjacent-step contrast 1.3-1.7:1). */
.pref-map .prefecture.chor-0,
.pref-map .prefecture.chor-1 { stroke: color-mix(in srgb, var(--faint) 85%, var(--paper)); }
.pref-map .prefecture.chor-2,
.pref-map .prefecture.chor-3,
.pref-map .prefecture.chor-4 { stroke: var(--paper); }
.pref-map .prefecture.chor-0 { fill: color-mix(in srgb, var(--accent) 12%, var(--wash)); }
.pref-map .prefecture.chor-1 { fill: color-mix(in srgb, var(--accent) 32%, var(--wash)); }
.pref-map .prefecture.chor-2 { fill: color-mix(in srgb, var(--accent) 58%, var(--wash)); }
.pref-map .prefecture.chor-3 { fill: color-mix(in srgb, var(--accent) 76%, var(--wash)); }
.pref-map .prefecture.chor-4 { fill: color-mix(in srgb, var(--accent) 92%, var(--wash)); }
.pref-map .boundary-line { stroke: var(--line); stroke-width: 1; fill: none; }

/* ---------- Filters ---------- */

.filter-field { display: flex; flex-direction: column; gap: 0.25rem; font-size: 0.85rem; }
.filter-field label { color: var(--muted); }
/* TASK-811: Rails wraps invalid label/control in div.field_with_errors; keep
   the controls flex items of .filter-field so a 422 re-render keeps its width. */
.filter-field .field_with_errors { display: contents; }

/* The textarea (correction form "Details") is listed explicitly so it no
   longer falls back to the UA default size (~13.3px) -- BUG-UI-017/TASK-697. */
.filter-field select, .filter-field input, .filter-field textarea {
  padding: 0.45rem 0.5rem;
  border: 1px solid var(--control-border);
  border-radius: var(--radius);
  background: var(--paper);
  color: var(--ink);
  font-size: 0.95rem;
}

/* ---------- Faceted sidebar (TASK-131), ported from website-minimal ---------- */

/* Disclosure wrapper: on desktop the sidebar is always expanded and the
   summary is hidden; on mobile (<=720px) the summary becomes a tappable
   header so the whole facet list collapses into a <details>. Zero-JS. */
.filters-box { border: none; padding: 0; margin: 0; }
.filters-box > summary {
  display: none;
  font-size: 0.9rem;
  color: var(--accent);
  cursor: pointer;
  padding: 0.5rem 0;
  list-style: none;
}
.filters-box > summary::-webkit-details-marker { display: none; }

/* Disclosure state cue + decorative legend mark (BUG-UI-022/TASK-776).
   The three zero-JS <details> summaries drop the native marker (above and
   in the two legend blocks below), so they get a replacement chevron that
   flips when the <details> is open -- otherwise the accent-coloured summary
   reads like a link with no open/closed state. The chevron is drawn with
   borders on an empty-content box (currentColor, so it follows both themes
   and forced-colors) and therefore adds nothing to the accessible name.
   The legends' "ⓘ" (TASK-141) is an aria-hidden <span class="legend-mark">
   in the markup, NOT `summary::before` generated content: CSS generated
   text is part of accessible-name computation and leaked "ⓘ" into the
   summary's name. */
.filters-box > summary::after,
.signal-legend > summary::after,
.badge-legend > summary::after {
  content: "";
  display: inline-block;
  inline-size: 0.4em;
  block-size: 0.4em;
  margin-inline-start: 0.5em;
  border-inline-end: 0.12em solid currentColor;
  border-block-end: 0.12em solid currentColor;
  transform: translateY(-0.2em) rotate(45deg);
}
.filters-box[open] > summary::after,
.signal-legend[open] > summary::after,
.badge-legend[open] > summary::after {
  transform: translateY(0.1em) rotate(-135deg);
}
.legend-mark { margin-inline-end: 0.25em; }

.fgroup { border-top: 1px solid var(--line); padding: 0.75rem 0 0.85rem; }
/* Closing rule: the last .fgroup among its siblings, whatever its element type
   and whether or not a trailing .reset-wrap follows (BUG-UI-045). */
.fgroup:not(:has(~ .fgroup)) { border-bottom: 1px solid var(--line); }
/* TASK-780: facet links carry their group's #facet-* fragment, so the reload
   scrolls to the group; keep its heading clear of the viewport edge. */
.fgroup[id] { scroll-margin-top: 0.75rem; }
.fgroup h2 {
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--faint);
  margin: 0 0 0.4rem;
}

/* Each facet option is a full-width toggle link with an aligned count. */
.side-item {
  display: flex;
  width: 100%;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem;
  text-align: left;
  font-size: 0.85rem;
  color: var(--muted);
  padding: 0.2rem 0.4rem;
  border-radius: 4px;
}
.side-item:hover { color: var(--ink); background: var(--wash); text-decoration: none; }
/* TASK-783: an unselected facet toggle the visitor has already followed takes
   --visited from the base `a:visited` rule; the selected one keeps its
   accent-ink-on-accent pairing, since this selector outranks `a:visited`. */
.side-item[aria-current="true"] { color: var(--accent-ink); background: var(--accent); }
.side-item .cnt { font-size: 0.72rem; color: var(--faint); font-variant-numeric: tabular-nums; }
.side-item[aria-current="true"] .cnt { color: inherit; opacity: 0.75; }
/* Selection-aware zero-count options: de-emphasized, but still
   clickable/focusable. TASK-141: recede them with the (AA-compliant) --faint
   token instead of an opacity fade — opacity below ~0.85 pushes the small
   count text under WCAG AA, whereas --faint (4.88:1 light / 4.95:1 dark on
   paper) keeps every glyph legible while still reading clearly lighter than
   the --muted active options.
   TASK-1025 (BUG-UI-055): the dim colours apply to unselected options only.
   A selected option whose count is zero (e.g. the default listing status on a
   search with no results) keeps the accent-ink-on-accent pairing; --faint on
   --accent is 1.63:1 light / 1.45:1 dark. */
.side-item.dim:not([aria-current="true"]) { color: var(--faint); }
.side-item.dim:not([aria-current="true"]) .cnt { color: var(--faint); }
.side-item.dim:not([aria-current="true"]):hover { color: var(--muted); }
/* TASK-694 (BUG-UI-014): under forced colors (Windows High Contrast) the
   selected option's accent colour and background are replaced by system
   colours, leaving it identical to its siblings. Add a non-colour cue that
   survives the override: an inset outline in the Highlight system colour plus
   a heavier weight. Scoped to forced-colors, so normal light/dark styling is
   unchanged. */
@media (forced-colors: active) {
  .side-item[aria-current="true"] {
    outline: 2px solid Highlight;
    outline-offset: -2px;
    font-weight: 700;
  }
}

.side-subnote { font-size: 0.72rem; color: var(--faint); line-height: 1.5; margin: 0 0 0.5rem; }
/* No overscroll-behavior: contain here (BUG-UI-015/TASK-695): at phone widths
   this in-flow list spans ~87% of the viewport, so trapping scroll chaining
   left a thumb resting on it unable to scroll the page once the list hit its
   end. Default chaining hands the rest of the gesture to the page. Browser
   check: script/pref_list_scroll_chain.mjs (see doc/accessibility.md). */
.pref-list { max-height: 21rem; overflow-y: auto; }
/* TASK-785 (BUG-UI-025): a facet link spans the full width of this scroll box,
   so the site-wide ring (drawn 2px OUTSIDE the element) lost its left and
   right sides to the box's clipping, and its top or bottom too on the first
   and last link. Draw it inside the link instead: no ancestor can clip it and
   nothing moves (an outline takes no space). On the selected link the fill is
   --accent itself, so the ring switches to --accent-ink there (the same pair
   as the label: 7.8:1 light / 7.2:1 dark) rather than accent on accent.
   Under forced colors the UA replaces both colours with system ones.
   Browser check: script/focus_ring_clip.mjs (see doc/accessibility.md). */
.pref-list .side-item:focus-visible { outline-offset: -2px; }
.pref-list .side-item[aria-current="true"]:focus-visible { outline-color: var(--accent-ink); }

.facet-search { display: flex; flex-direction: column; gap: 0.4rem; border-top: none; padding-top: 0; }
.facet-search label {
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--faint);
}
.facet-search input[type="search"] {
  padding: 0.45rem 0.5rem;
  border: 1px solid var(--control-border);
  border-radius: var(--radius);
  background: var(--paper);
  color: var(--ink);
  font-size: 0.95rem;
}
.facet-search input[type="submit"] { align-self: stretch; }

/* BUG-UI-017 (TASK-697): iOS Safari zooms the viewport when focusing an
   editable control whose computed font-size is under 16px. The facet search
   (input#q), the compare page's add-school input and the correction form's
   select/url/email/textarea fields all computed 13.3-15.2px at mobile widths.
   Bump them to 1rem (16px) on narrow screens only; desktop keeps 0.95rem.
   Same selectors as the base rules above, so equal specificity + later
   source order wins. */
@media (max-width: 720px) {
  .filter-field select, .filter-field input, .filter-field textarea,
  .facet-search input[type="search"] {
    font-size: 1rem;
  }
}

.reset-wrap { padding: 0.75rem 0 0; }
.reset { font-size: 0.85rem; color: var(--accent); padding: 0.15rem 0.4rem; }
.reset:visited { color: var(--accent); }

/* Collapsible signal legend (TASK-141): the AT-reachable, zero-JS equivalent
   of the detail-page badge title= tooltips. Quiet by default; the ⓘ marker
   and small print keep it out of the way of the facet toggles above. */
.signal-legend { margin: 0.5rem 0 0.15rem; }
.signal-legend > summary {
  font-size: 0.78rem;
  color: var(--accent);
  cursor: pointer;
  padding: 0.15rem 0.4rem;
  list-style: none;
}
.signal-legend > summary::-webkit-details-marker { display: none; }
.signal-legend dl { margin: 0.35rem 0 0; padding: 0 0.4rem; }
.signal-legend dt { font-size: 0.78rem; font-weight: 600; color: var(--muted); margin-top: 0.5rem; }
.signal-legend dd { font-size: 0.75rem; color: var(--muted); line-height: 1.5; margin: 0.1rem 0 0; }

@media (max-width: 720px) {
  .filters-box > summary { display: block; }
  /* BUG-UI-064: theme.js sets .filters-narrow before first paint; the panel
     body is hidden until filters-collapse.js drops `open` and the class
     together (no layout shift). No JS: class never set, panel stays open. */
  .filters-narrow .filters-box[open] > :not(summary) { display: none; }
  .pref-list { max-height: 15rem; }
}

/* WCAG 2.2 SC 2.5.8 Target Size (Minimum) (BUG-UI-006/TASK-591): at mobile
   widths the primary nav, language switcher, footer links, region-heading
   jump links, breadcrumb links, prefecture-grid links, the filters "Clear
   all filters" link and the school-search label were 15-21px tall — under
   the 24x24 minimum and, for several of them, too close to a neighbouring
   target to qualify for the spacing exception either. inline-flex + vertical
   padding grows each tap target without changing its visible text size or
   wrapping behaviour; desktop (>720px, fine pointer) is untouched.

   TASK-698: the same sizing also applies to touch devices wider than 720px
   (tablets, landscape phones) via `(pointer: coarse)`; a mouse-driven
   desktop keeps the compact sizes.

   `.locale-switch` sits inside `.site-header nav`, so its links are already
   matched (and, at (0,1,3) vs. (0,1,1), outranked) by `.site-header nav ul a`
   below — there is no separate `.locale-switch a` sizing rule here because
   one would be dead code. The language switcher gets the same ~44px primary
   -nav target as a result, which is fine (arguably better) for a top-level
   control. */
@media (max-width: 720px), (pointer: coarse) {
  .site-header nav ul a {
    display: inline-flex;
    align-items: center;
    min-block-size: 2.75rem; /* ~44px, covers both the primary nav and (via specificity) the locale switch */
    padding-block: 0.4rem;
  }
  .footer-links a {
    display: inline-flex;
    align-items: center;
    min-block-size: 1.5rem; /* 24px */
    padding-block: 0.35rem;
  }
  /* BUG-UI-049/TASK-1017: the in-sentence "Glossary" link in the footer
     disclaimer was missed by the rule above and stayed 15px tall. */
  .footer-disclaimer a {
    display: inline-flex;
    align-items: center;
    min-block-size: 1.5rem; /* 24px */
  }
  .prefecture-list a {
    min-block-size: 1.5rem; /* 24px */
    padding-block: 0.45rem;
  }
  /* Region heading jump links ("Hokkaido", "Tohoku", …) were ~20px tall with
     under 24px of clearance to the first prefecture link below them. */
  .region h2 a, .region h3 a {
    display: inline-flex;
    align-items: center;
    min-block-size: 1.5rem; /* 24px */
    padding-block: 0.2rem;
  }
  .region h2, .region h3 { margin-block-end: 0.85rem; }
  /* Breadcrumb links were 16px tall. flex-wrap on .breadcrumb ol already
     handles long school names wrapping onto their own line at narrow
     widths, so inline-flex + padding here doesn't change that. */
  /* BUG-UI-023: the padded links make every <li> ~29px tall, but the
     current-page <li> holds plain text, so under the default
     align-items: stretch it (and its "›") stayed at the top of the row,
     4px above the linked crumbs. Centre the items on the cross axis. */
  .breadcrumb ol { align-items: center; }
  .breadcrumb a {
    display: inline-flex;
    align-items: center;
    min-block-size: 1.5rem; /* 24px */
    padding-block: 0.25rem;
  }
  .reset { min-block-size: 1.5rem; padding-block: 0.4rem; } /* 24px, was ~21px */
  /* The visible "Search schools" label in the filters sidebar sat flush
     against `.filters-box > summary` above it (0px gap) — closer to that
     than to its own input below. Restoring facet-search's normal top
     padding (zeroed by .facet-search for the desktop two-column layout,
     where the summary isn't part of the flow) gives it breathing room on
     both sides instead of growing the label itself. */
  .facet-search { padding-block-start: 0.75rem; }
  /* Correction-form field labels ("Details", "Your email", …) sat flush
     against the previous field's control (0px gap between adjacent
     .filter-field groups) -- same fix as .facet-search above: restore a
     little breathing room between groups instead of growing each label. */
  .filter-field { margin-block-start: 0.35rem; }
}

/* ---------- School list: dictionary rows ---------- */

.school-list { list-style: none; margin: 0.5rem 0 0; padding: 0; }
.school-list li { border-top: 1px solid var(--line); }
.school-list li:last-child { border-bottom: 1px solid var(--line); }

.school-row {
  display: flex;
  align-items: baseline;
  gap: 1rem;
  padding: 0.85rem 0.15rem;
  color: inherit;
}
/* TASK-783: the row is ink, not accent, so the visited cue is put on the
   school name alone; the secondary line and the meta column keep their own
   colours. --visited holds AA on --wash, the row's hover background. */
.school-row:visited { color: inherit; }
.school-row:visited .row-primary { color: var(--visited); }
.school-row:hover { background: var(--wash); text-decoration: none; }

.row-names { flex: 1 1 auto; min-width: 0; }
.row-primary {
  display: block;
  font-family: var(--serif);
  font-size: 1.1rem;
  line-height: 1.4;
  color: var(--ink);
  overflow-wrap: anywhere;
}
.row-secondary {
  display: block;
  font-size: 0.82rem;
  color: var(--muted);
  white-space: normal;
  overflow-wrap: anywhere;
}
.row-secondary-translated { font-style: italic; }
/* TASK-899 (BUG-UI-046): the secondary name wraps at every width (no ellipsis):
   its campus/department suffix is what tells sibling schools apart, and WCAG
   1.4.12 text-spacing overrides must not clip it. Browser check: script/a11y_audit.mjs. */
/* BUG-UI-020: the ward/city (or registry ID) that tells two same-name schools in one prefecture apart. */
.row-locator { display: block; font-size: 0.76rem; color: var(--muted); }
.row-alias { display: block; font-size: 0.76rem; color: var(--muted); }
.row-meta {
  flex: 0 0 auto;
  text-align: right;
  font-size: 0.82rem;
  color: var(--muted);
  white-space: nowrap;
}
.row-badges { margin-top: 0.2rem; display: flex; gap: 0.35rem; justify-content: flex-end; flex-wrap: wrap; }
/* TASK-177: subtle note under the prefecture when a row is reached via a branch. */
.row-branch-pref { display: block; margin-top: 0.15rem; font-size: 0.72rem; color: var(--muted); font-style: italic; white-space: normal; }
/* BUG-UI-053: the tuition figure the list is ordered by, shown only while the tuition sort is active. */
.row-sort-key { display: block; margin-top: 0.15rem; font-size: 0.78rem; color: var(--ink, inherit); }
.row-sort-key-none { color: var(--muted); }

.empty { color: var(--muted); padding: 2rem 0; }
/* BUG-WEB-170: relax-filters hint under an empty filtered list (WEB-FUNC-007). */
.empty:has(+ .zero-hint) { padding-bottom: 0.75rem; }
.zero-hint { padding-bottom: 2rem; overflow-wrap: anywhere; }
.zero-hint ul { margin: 0.5rem 0; padding-left: 1.25rem; }
.zero-hint li { margin: 0.3rem 0; }
.zero-hint .cnt { color: var(--muted); }

/* ---------- Badges (mark exceptions only; outline + currentColor) ---------- */

.badge {
  display: inline-block;
  font-size: 0.7rem;
  letter-spacing: 0.04em;
  line-height: 1.7;
  border-radius: 3px;
  padding: 0 0.5rem;
  border: 1px solid currentColor;
  color: var(--muted);
  background: none;
  font-weight: 600;
}
.badge.accredited { color: var(--ok); }
.badge.closed { color: var(--closed); }
.badge.unconfirmed { color: var(--faint); border-style: dashed; }

/* Detail-page header signal badges (TASK-139): 適正校 / MHLW / association,
   side by side above the compare link -- reuses .badge (outline style),
   never a new visual language. */
.detail-badges { display: flex; gap: 0.35rem; flex-wrap: wrap; margin: 0.35rem 0; }

/* Collapsible badge legend (TASK-141): visible, AT-reachable, zero-JS
   equivalent of the header badges' title= tooltips. */
.badge-legend { margin: 0 0 0.5rem; }
.badge-legend > summary {
  font-size: 0.82rem;
  color: var(--accent);
  cursor: pointer;
  list-style: none;
}
.badge-legend > summary::-webkit-details-marker { display: none; }
.badge-legend dl { margin: 0.4rem 0 0; }
.badge-legend dt { font-weight: 600; color: var(--muted); font-size: 0.85rem; margin-top: 0.5rem; }
.badge-legend dd { color: var(--muted); font-size: 0.85rem; line-height: 1.5; margin: 0.1rem 0 0; }

/* Per-registry freshness line (TASK-139), under the sources note -- muted,
   matches .caption. */
.freshness { list-style: none; margin: 0.4rem 0 0; padding: 0; }
.freshness li { color: var(--muted); font-size: 0.85rem; }

/* ---------- Detail page ---------- */

.school-detail h1 {
  font-family: var(--serif);
  font-weight: 500;
  font-size: 1.9rem;
  line-height: 1.35;
  overflow-wrap: anywhere;
}
.school-detail .school-locator {
  color: var(--muted);
  font-size: 0.95rem;
  margin: 0.2rem 0 0.5rem;
}
.school-detail .secondary-name {
  color: var(--muted);
  font-size: 0.95rem;
  margin: 0.2rem 0 0.5rem;
}
.secondary-name-label {
  font-style: italic;
}
.school-detail h2 {
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--faint);
  margin: 2.25rem 0 0.6rem;
}

.compare-link, .compare-add-form + p, .correction-link { font-size: 0.9rem; margin: 0.75rem 0 0; }

/* Status / notice banners */
.status-banner, .flash {
  margin: 0 0 1.25rem;
  padding: 0.75rem 1rem;
  border-radius: var(--radius);
  border: 1px solid transparent;
  font-size: 0.95rem;
}
/* BUG-UI-013 / TASK-693: the school page's banner is the <header>'s next
   sibling, and neither side had a margin at that shared edge (.compare-link is
   top-only, the banner bottom-only), so the two touched. Scoped to the
   adjacent case so .flash and banner-free pages keep their spacing. */
.school-detail > header + .status-banner { margin-top: 1.25rem; }
.status-banner--unconfirmed { background: color-mix(in srgb, #e8b649 16%, var(--paper)); border-color: #c69626; color: var(--ink); }
.status-banner--closed { background: color-mix(in srgb, var(--closed) 14%, var(--paper)); border-color: var(--closed); color: var(--ink); }
.status-banner--excluded { background: var(--wash); border-color: var(--faint); color: var(--ink); }
.flash--notice { background: color-mix(in srgb, var(--ok) 14%, var(--paper)); border-color: var(--ok); color: var(--ink); }
.flash--alert { background: color-mix(in srgb, var(--closed) 14%, var(--paper)); border-color: var(--closed); color: var(--ink); }

/* Accreditation-transition status. `revoked` stays the loudest of the four. */
.accreditation-badge {
  display: inline-block;
  padding: 0.15rem 0.6rem;
  border-radius: 999px;
  font-size: 0.85rem;
  font-weight: 600;
  border: 1px solid transparent;
}
.accreditation-badge--accredited { background: color-mix(in srgb, var(--ok) 16%, var(--paper)); color: var(--ok); }
.accreditation-badge--notified-only { background: var(--wash); color: var(--muted); }
.accreditation-badge--not-applicable { background: var(--wash); color: var(--faint); }
.accreditation-badge--revoked { background: color-mix(in srgb, var(--closed) 16%, var(--paper)); color: var(--closed); border-color: var(--closed); }
.accreditation-transition-note { font-size: 0.9rem; color: var(--muted); }

/* Fact lists (dl) */
.school-facts { margin: 0; border-top: 1px solid var(--line); }
.school-facts div {
  display: flex;
  gap: 1rem;
  padding: 0.6rem 0.15rem;
  border-bottom: 1px solid var(--line);
}
.school-facts dt { flex: 0 0 8.5rem; color: var(--muted); font-size: 0.85rem; padding-top: 0.1rem; margin: 0; }
.school-facts dd { margin: 0; overflow-wrap: anywhere; }

.locations { list-style: none; margin: 0; padding: 0; }
.locations li { border-bottom: 1px solid var(--line); padding: 0.75rem 0; }
.locations li:last-child { border-bottom: none; }

.quality-signals, .sources, .course-name-list { padding-left: 1.2rem; }
.quality-signals li, .sources li, .course-name-list li { margin: 0.3rem 0; }
.sources code { background: var(--wash); padding: 0.1rem 0.35rem; border-radius: 4px; }
/* BUG-UI-035 (TASK-887): attribution strings hold unbroken URLs; let them wrap
   so the page never scrolls sideways below ~750px (WCAG 1.4.10). */
.sources code { overflow-wrap: anywhere; }
.confidence { color: var(--faint); font-size: 0.85em; }
.capacity-note { display: block; color: var(--muted); font-size: 0.8rem; }

/* TASK-599: the university-bekka course description paragraph + its 2019
   disclosure-vintage source note (course-description-source), and the
   EN "published in Japanese only" pointer (same shape as .dormitory-ja-only). */
.course-description { display: block; margin: 0.35rem 0 0; font-size: 0.9rem; }
.course-description-source { display: block; color: var(--muted); font-size: 0.8rem; margin-top: 0.15rem; }

/* TASK-138: the courses-table JLPT cell's shape heading, N5-baseline note
   and start-level fact -- all secondary to the raw label text itself. */
.jlpt-heading, .jlpt-note, .jlpt-start-level { display: block; color: var(--muted); font-size: 0.8rem; }

/* Keyless Google Maps embed (TASK-129, public map iframe), now click-to-load
   only (TASK-143). */
.map-embed { margin: 0.75rem 0 0.25rem; }
.map-embed iframe {
  width: 100%;
  height: 340px;
  border: 0;
  border-radius: var(--radius);
  display: block;
  transition: filter 0.2s ease;
}
/* Static placeholder shown before the click (TASK-143): address + a
   pill-styled "Show map" link, matching the site's generic button look
   (border/background/radius mirror the `button, input[type="submit"]` rule
   above) so it reads as part of the design, not a bare fallback. */
.map-placeholder {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.6rem;
  padding: 1rem 1.1rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--wash);
}
.map-placeholder-address { margin: 0; color: var(--muted); font-size: 0.9rem; }
.map-load-button {
  display: inline-block;
  padding: 0.5rem 1.1rem;
  border: 1px solid var(--accent);
  border-radius: 999px;
  background: var(--accent);
  color: var(--accent-ink);
  font-size: 0.9rem;
  text-decoration: none;
}
.map-load-button:visited { color: var(--accent-ink); }
.map-load-button:hover, .map-load-button:focus-visible { filter: brightness(1.08); }
.map-load-button:active { filter: brightness(0.88); }
/* The keyless Google Maps iframe has no dark mode of its own (only the JS Maps
   API does), so approximate one in dark theme by inverting the light map and
   rotating hues ~180deg back — water/greenery keep plausible colours; text
   labels invert too, so this is an approximation, not a true dark style.
   Follows the site theme exactly: manual [data-theme="dark"], or OS preference
   unless the toggle forced light (same selectors as the palette above). */
:root[data-theme="dark"] .map-embed iframe {
  filter: invert(0.92) hue-rotate(180deg) brightness(0.95) contrast(0.9);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .map-embed iframe {
    filter: invert(0.92) hue-rotate(180deg) brightness(0.95) contrast(0.9);
  }
}

/* ---------- Admin corrections status filter (TASK-735) ---------- */
.admin-status-filter ul { display: flex; flex-wrap: wrap; gap: 0.75rem; list-style: none; margin: 1rem 0 0.5rem; padding: 0; }
.admin-status-filter span[aria-current] { font-weight: 600; }

/* ---------- Tables (courses, compare, admin) ---------- */

table { border-collapse: collapse; width: 100%; margin: 0.5rem 0 1rem; font-size: 0.95rem; }
th, td {
  text-align: left;
  padding: 0.5rem 0.75rem 0.5rem 0.15rem;
  border-bottom: 1px solid var(--line);
  vertical-align: top;
  /* TASK-686 (BUG-UI-007): `break-word`, never `anywhere`. Both break an
     over-long token rather than overflow, but `anywhere` also shrinks every
     cell's min-content width to ~1 character, so a wide table never exceeds
     its `.table-scroll` box (it never scrolls) and columns collapse into
     mid-word breaks. `break-word` leaves min-content at the longest word, so
     words wrap at spaces and a table that doesn't fit scrolls instead. */
  overflow-wrap: break-word;
}
th {
  color: var(--faint);
  font-weight: 600;
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  border-bottom: 1px solid var(--faint);
}

/* Wide tables scroll within their own box; the page body never scrolls x. */
.table-scroll {
  overflow-x: auto;
  /* TASK-1045 (BUG-UI-056): `position: relative` makes the box the containing
     block of any absolutely positioned descendant (visually-hidden text in a
     cell). Without it such an element is positioned against the viewport and
     is NOT clipped by this overflow box, so the whole page gained a
     horizontal scrollbar at 320-375px. */
  position: relative;
  margin: 0.5rem 0 1rem;
  /* TASK-1018 (BUG-UI-050): scroll affordance, CSS only. Two edge shadows are
     painted on the scroll box itself (`scroll` attachment: they stay put) and
     two paper-coloured covers travel with the content (`local` attachment).
     At either end of the scroll range a cover sits on its shadow and hides
     it, so a table that fits shows nothing and an overflowing one shows a
     shadow on every side that still has columns to reveal.
     Two things keep the cue visible where a reader first meets the table:
     the shadows are LINEAR and full height (a radial one fades to nothing at
     the top and bottom rows of a tall table), and a cover is exactly as wide
     as its shadow and no wider, so an overflow as small as 0.625rem (10px)
     already uncovers the whole shadow -- a wider, feathered cover still
     masks it on the 14px overflow this was reported on. The first
     declaration is the fallback for browsers without color-mix(). */
  background:
    linear-gradient(var(--paper), var(--paper)) left center,
    linear-gradient(var(--paper), var(--paper)) right center,
    linear-gradient(to right, rgba(0, 0, 0, 0.3), transparent) left center,
    linear-gradient(to left, rgba(0, 0, 0, 0.3), transparent) right center;
  background:
    linear-gradient(var(--paper), var(--paper)) left center,
    linear-gradient(var(--paper), var(--paper)) right center,
    linear-gradient(to right, color-mix(in srgb, var(--ink) 30%, transparent), transparent) left center,
    linear-gradient(to left, color-mix(in srgb, var(--ink) 30%, transparent), transparent) right center;
  background-repeat: no-repeat;
  background-size: 0.625rem 100%, 0.625rem 100%, 0.625rem 100%, 0.625rem 100%;
  background-attachment: local, local, scroll, scroll;
}
.table-scroll table { margin: 0; }
/* The wrapper is a tab stop (tabindex="0" in the views) so the arrow keys can
   scroll it; the ring is drawn inside the box so no ancestor can clip it. */
.table-scroll:focus-visible { outline-offset: -2px; }

/* TASK-1019 (BUG-UI-051): the compare table's label column. It is pinned to
   the left edge of the `.table-scroll` box so the row labels stay readable
   while the school columns scroll under it (390px: 3 schools never fit), and
   it has a floor width: Japanese text may break between any two characters,
   so the auto table layout otherwise squeezes this column to one character
   per line. The cells need an opaque background (the columns pass beneath
   them) and draw their rules as inset shadows -- a collapsed border belongs
   to the table and scrolls away from a sticky cell. */
.compare-table > thead > tr > :first-child,
.compare-table > tbody > tr > th[scope="row"] {
  position: sticky;
  left: 0;
  z-index: 1;
  min-width: 7rem;
  background: var(--paper);
  border-bottom-color: transparent;
  box-shadow: inset 0 -1px 0 var(--faint), inset -1px 0 0 var(--line);
}
.compare-table > tbody > tr > th[scope="row"] + td,
.compare-table > thead > tr > :first-child + th { padding-left: 0.6rem; }
@media (min-width: 721px) {
  .compare-table > thead > tr > :first-child,
  .compare-table > tbody > tr > th[scope="row"] { min-width: 9.5rem; }
}

/* Course focus (progressive enhancement — courses.js). The panel and the
   row-button affordances are revealed by JS; with JS off the full table shows
   and none of this applies. */
.course-select {
  display: flex;
  gap: 0.75rem;
  align-items: baseline;
  flex-wrap: wrap;
  font-size: 0.85rem;
  color: var(--muted);
  margin: 0 0 0.5rem;
}
/* TASK-965: `display: flex` above (and the pill's own display) would override
   the `hidden` attribute these elements ship with; with JS off nothing hides
   the hint again, so it showed an inert "click a row" prompt. */
.course-select[hidden],
.course-reset[hidden] { display: none; }
.course-reset {
  font: inherit;
  font-size: 0.8rem;
  color: var(--accent);
  background: none;
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 0.15rem 0.7rem;
  cursor: pointer;
}
.course-reset:hover { border-color: var(--accent); filter: none; }
.course-reset:active { background: var(--wash); filter: none; }
/* TASK-885: the select affordance is a real <button class="course-pick"> in
   the row-header cell; the row itself keeps native table semantics. */
.course-pick {
  font: inherit;
  color: inherit;
  text-align: inherit;
  background: none;
  border: 0;
  padding: 0;
  margin: 0;
  cursor: pointer;
  text-decoration: underline dotted;
  text-underline-offset: 0.2em;
}
.course-pick:hover { color: var(--accent); filter: none; }
[data-course-selector] tbody [data-course-row]:hover :is(th, td) { background: var(--wash); }
[data-course-row].is-selected :is(th, td) { background: var(--wash); }
[data-course-row].is-selected > :first-child { box-shadow: inset 3px 0 0 var(--accent); }
/* TASK-785 (BUG-UI-025): a focusable course row is as wide as its
   `.table-scroll` box, which clipped the left/right sides of the outside ring
   (and the bottom one on the last row). Inset, as on the wrapper itself. */
.course-pick:focus-visible { outline-offset: 2px; }

/* Search-match highlight (TASK-139, DESIGN.md §6.1): same 3px accent bar +
   wash treatment as .is-selected, applied by courses.js to rows satisfying
   every active course-level filter carried over from a filtered schools
   list (never on a direct link). Composes with .is-selected -- a matched row
   that is also the selected one just gets both classes, same look. */
.course-match-note { color: var(--muted); font-size: 0.85rem; margin: 0 0 0.5rem; }
[data-course-row].is-match :is(th, td) { background: var(--wash); }
[data-course-row].is-match > :first-child { box-shadow: inset 3px 0 0 var(--accent); }

/* BUG-UI-018/TASK-700: the course-name body cell is a row header
   (<th scope="row">) for assistive tech but must look exactly like the data
   cells beside it, so it undoes the column-header styling of the base `th`. */
th.course-name-cell {
  color: inherit;
  font-weight: normal;
  font-size: inherit;
  letter-spacing: normal;
  text-transform: none;
  border-bottom: 1px solid var(--line);
}

/* ---------- Compare builder ---------- */

.compare-builder {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--wash);
  padding: 1rem;
  margin: 1rem 0;
}
.compare-chips { display: flex; flex-wrap: wrap; gap: 0.5rem; list-style: none; margin: 0.5rem 0; padding: 0; }
.compare-chip {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 0.3rem 0.75rem;
  background: var(--paper);
}
.compare-add-form { display: flex; flex-wrap: wrap; align-items: end; gap: 0.75rem; margin: 0.75rem 0; }
.compare-add-form .filter-field { min-width: 0; max-width: 100%; }
.compare-add-form input[type="search"] { max-width: 100%; box-sizing: border-box; }
.compare-add-results { list-style: none; margin: 0.5rem 0 0; padding: 0; }
.compare-add-results li { padding: 0.3rem 0; border-bottom: 1px solid var(--line); }
.compare-add-results li:last-child { border-bottom: none; }
.compare-add-meta { color: var(--muted); font-size: 0.85rem; margin-inline-start: 0.5rem; }
.compare-cap-notice { color: var(--muted); font-size: 0.9rem; }
/* TASK-812 (BUG-UI-030): WCAG 2.5.8 -- a real 24px+ box rather than leaning on
   the line box (24.3px at 0.95rem) or the spacing exception. */
.compare-remove {
  font-size: 0.85rem;
  display: inline-flex;
  align-items: center;
  min-block-size: 1.75rem;
  min-inline-size: 1.75rem;
  justify-content: center;
}
/* TASK-696 (BUG-UI-016): the school-name link heading each compare column is
   content, not a chrome label -- without its own rule it inherited the
   generic `th` eyebrow style (0.7rem, uppercase, letter-spaced, --faint), so
   school names rendered as 11.2px small caps. TASK-255: uppercase is for
   labels, never school names. Reset to the table body size (0.95rem) and
   normal case; the row-label `th` cells keep the label look. */
th .compare-col-name {
  display: flex;
  align-items: center;
  min-block-size: 1.75rem;
  font-size: 0.95rem;
  letter-spacing: normal;
  text-transform: none;
}

/* ---------- Sources page ---------- */

.source-registry { padding-left: 0; list-style: none; }
.source-registry li {
  margin: 0 0 1.5rem;
  padding-bottom: 1rem;
  border-bottom: 1px solid var(--line);
}
.source-registry li:last-child { border-bottom: none; }
.source-registry h3 { margin: 0 0 0.35rem; }

/* ---------- Breadcrumb ---------- */

.breadcrumb ol {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  list-style: none;
  margin: 0;
  padding: 0;
  font-size: 0.82rem;
  color: var(--faint);
}
.breadcrumb li + li::before { content: "›"; margin: 0 0.35rem; color: var(--faint); }
.breadcrumb a { color: var(--muted); }

/* ---------- Pagination ---------- */

.pagination ul {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  justify-content: center;
  list-style: none;
  margin: 2rem 0 0;
  padding: 0;
}
.pagination a, .pagination span {
  display: inline-block;
  min-width: 2.1rem;
  padding: 0.3rem 0.6rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  text-align: center;
  text-decoration: none;
  color: var(--muted);
}
.pagination a:visited { color: var(--visited); }
.pagination a:hover { border-color: var(--accent); color: var(--accent); text-decoration: none; }
.pagination span[aria-current="page"] {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}
.pagination .gap { border: none; }
.show-all { text-align: center; margin: 0.6rem 0 0; font-size: 0.85rem; }

/* ---------- Responsive ---------- */

@media (max-width: 720px) {
  .browse-layout { grid-template-columns: minmax(0, 1fr); gap: 0.5rem; }
  .site-header .brand { font-size: 1.4rem; }
  .school-row { flex-direction: column; align-items: stretch; gap: 0.2rem; }
  .row-meta { text-align: left; white-space: normal; }
  .row-badges { justify-content: flex-start; }
  .school-facts div { flex-direction: column; gap: 0.2rem; }
  .school-facts dt { flex-basis: auto; }
}

@media (prefers-reduced-motion: no-preference) {
  .school-row, .prefecture-list a, .pagination a { transition: background-color 120ms ease, border-color 120ms ease; }
}

/* ---------- itemized fees (TASK-136) ---------- */
.fees { margin-bottom: 1.75rem; }
.fees-subhead { font-family: var(--serif); font-size: 1.05rem; font-weight: 600; margin: 0 0 0.5rem; }
.fee-from { margin: 0 0 0.2rem; font-weight: 600; }
.fee-bucket-note { margin-top: 0; }
.fee-block { margin: 0 0 1.25rem; max-width: 34rem; }
/* BUG-WEB-042: a school with >1 fee block must never read as one continuous
   list — every block after the first gets its own rule + top padding so the
   boundary is visible even when a block has no header (the unnamed
   catch-all) or when its header text happens to match a neighbor's. */
.fee-block + .fee-block { padding-top: 1rem; border-top: 1px solid var(--line); }
.fee-block .fee-name { font-family: var(--serif); font-size: 0.95rem; font-weight: 600; margin: 0.9rem 0 0.25rem; }
/* BUG-WEB-119: the stored qualifier that tells apart sibling blocks sharing
   one heading (e.g. four intake-month schedules), shown as a light suffix
   rather than bold like the heading itself. */
.fee-name .fee-heading-qualifier { display: block; font-weight: 400; margin-top: 0.1rem; }
.fee-table { width: 100%; border-collapse: collapse; }
.fee-table th, .fee-table td { padding: 0.35rem 0; border-bottom: 1px solid var(--line); vertical-align: baseline; }
/* BUG-UI-018/TASK-700: row labels are <th scope="row"> for assistive tech;
   undo the base `th` column-header look so they render as the former <td>. */
.fee-table th {
  color: inherit;
  font-weight: normal;
  font-size: inherit;
  letter-spacing: normal;
  text-transform: none;
}
/* The fee table has no `.table-scroll` wrapper (two columns: label + a nowrap
   amount), so its label cells keep the break-anywhere policy that stops an
   unbroken label from widening the page (TASK-686). */
.fee-table th, .fee-table td:not(.num) { overflow-wrap: anywhere; }
.fee-table td.num { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }
.fee-table .fee-sub th, .fee-table .fee-sub td { color: var(--muted); font-size: 0.85rem; }
.fee-table .year-break th, .fee-table .year-break td { border-top: 1px solid var(--line); padding-top: 0.6rem; }
.fee-table .fee-total th, .fee-table .fee-total td { font-weight: 600; border-bottom: none; }
.correction-message { white-space: pre-line; }

.fee-text {
  white-space: pre-line;
  font-size: 0.9rem;
  color: var(--muted);
  background: var(--wash);
  border-radius: 6px;
  padding: 0.65rem 0.9rem;
  margin: 0.5rem 0 0;
}
.fee-note { margin-top: 0.6rem; }
.mini { font-size: 0.8rem; color: var(--muted); }

/* ---------- Subsidized courses (MHLW) + Scholarships (TASK-137) ---------- */
/* Unverified self-claim badge: dashed outline (same signal as the
   .unconfirmed listing badge) so it reads as "claimed, not confirmed". */
.badge--unverified { border-style: dashed; }

.mhlw-eligibility-warning {
  font-size: 0.9rem;
  color: var(--ink);
  background: color-mix(in srgb, #e8b649 16%, var(--paper));
  border: 1px solid #c69626;
  border-radius: 6px;
  padding: 0.6rem 0.9rem;
  margin: 0.4rem 0 0.8rem;
}
.mhlw-course-list { list-style: none; margin: 0; padding: 0; }
.mhlw-course-list li { padding: 0.35rem 0; border-bottom: 1px solid var(--line); }
.mhlw-course-list li:last-child { border-bottom: none; }
.mhlw-course-name { font-weight: 600; }
.mhlw-category, .mhlw-period { display: block; font-size: 0.85rem; color: var(--muted); }

.scholarship-text {
  white-space: pre-line;
  font-size: 0.9rem;
  color: var(--muted);
  background: var(--wash);
  border-radius: 6px;
  padding: 0.65rem 0.9rem;
  margin: 0.5rem 0 0;
}

/* ---------- Print (TASK-630, BUG-WEB-141) ---------- */
/* WEBSITE_REQUIREMENTS.md UC-03: "compare view printable". A printed page is
   the content only: site chrome (masthead, locale switch, theme toggle,
   footer, skip link) and interactive controls (compare builder form, Remove
   links, correction link, JS course controls) are dropped, and the light
   palette is forced even when the visitor toggled dark (data-theme="dark"
   survives into print; the OS preference does not), so paper always gets
   dark ink on white rather than Chromium's lightened-grey substitute. */
@media print {
  :root,
  :root[data-theme="dark"],
  :root:not([data-theme="light"]) {
    --paper: #ffffff;
    --ink: #1b1c1e;
    --muted: #5c5e63;
    --faint: #6f7176;
    --line: #e4e3df;
    --accent: #14538c;
    --accent-ink: #ffffff;
    --visited: #7b2d9e;
    --wash: #f6f6f4;
    --ok: #2c6e49;
    --closed: #9a3b3b;
  }
  :root[data-theme="dark"] .map-embed iframe,
  :root:not([data-theme="light"]) .map-embed iframe { filter: none; }

  .skip-link,
  .site-header,
  .locale-switch,
  .theme-toggle,
  .site-footer,
  .compare-add-form,
  .compare-add-results,
  .compare-cap-notice,
  .compare-remove,
  .compare-chip > a + a,
  .compare-builder:has(~ .table-scroll),
  .compare-link,
  .correction-link,
  .course-select,
  .course-reset { display: none !important; }

  /* The screen table scrolls in its own box; paper cannot scroll, so the
     table must fit the page width. Headers keep whole words (an
     overflow-wrap:anywhere header lets the auto table layout squeeze the
     row-header column until "PREFECTURE" splits mid-word); data cells switch
     from the screen's `break-word` to `anywhere` so a long URL can still
     shrink and break instead of pushing the table past the page edge, where
     print would clip it (TASK-686). `keep-all` keeps the Japanese labels
     (都道府県, 公式サイト) whole, which otherwise break between any two
     characters; they still wrap at spaces and hyphens. */
  .table-scroll { overflow: visible; background: none; }
  th { overflow-wrap: normal; word-break: keep-all; }
  /* TASK-1019: nothing scrolls on paper, so the label column is not pinned
     and gives its floor width back to the page. */
  .compare-table > thead > tr > :first-child,
  .compare-table > tbody > tr > th[scope="row"] { position: static; min-width: 0; box-shadow: none; border-bottom-color: var(--faint); }
  td { overflow-wrap: anywhere; }
  /* BUG-UI-018/TASK-700: these row headers carry data-cell text (course
     names, fee labels), so they print like the data cells they used to be. */
  th.course-name-cell, .fee-table th { overflow-wrap: anywhere; word-break: normal; }
  tr { break-inside: avoid; }
  thead { display: table-header-group; }
}

/* ---------- Centring utilities (TASK-282, TASK-287) ---------- */
/* One vocabulary for "centre this", so the homepage headings and the Japan
   map cannot drift apart: `.center-text` centres the LINES inside a block
   (headings, and a heading + its subtitle centred as a unit by putting it on
   both), `.center-block` centres a width-constrained BLOCK in its container
   (an inline SVG's wrapper, where text-align has no effect on the box).
   MUST STAY LAST in this file: both rules are single-class specificity, so
   they win over a component's own margin/alignment only by source order. */
/* `text-wrap: balance` evens out the line lengths of a centred heading or its
   subtitle when they wrap (a centred two-line subtitle with a one-word second
   line reads badly at 320/390px, and CJK breaks mid-word). Browsers without it
   ignore the declaration and simply wrap as before. */
.center-text { text-align: center; text-wrap: balance; }
.center-block { margin-inline: auto; }
