:root {
  --bg: #f6f1e8;
  --paper: #fffdf8;
  --ink: #1f2a2a;
  --muted: #63706f;
  --line: rgba(31, 42, 42, 0.12);
  --brand: #1e76c7;
  --brand-soft: #d6efea;
  --accent: #e29f2d;
  --danger: #a6472f;
  --shadow: 0 18px 40px rgba(24, 37, 37, 0.08);
  --radius: 18px;
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  color: var(--ink);
  font-family: "Hiragino Sans", "Yu Gothic", "Noto Sans JP", sans-serif;
  background:
    radial-gradient(circle at top left, rgba(226, 159, 45, 0.18), transparent 28%),
    linear-gradient(180deg, #fff7ea 0%, var(--bg) 100%);
}

a {
  color: var(--brand);
  text-decoration: none;
}

.page-shell {
  max-width: 1200px;
  margin: 0 auto;
  padding: 28px 20px 48px;
  min-height: 100vh;
}

.hero {
  display: flex;
  justify-content: space-between;
  gap: 24px;
  padding: 28px;
  border: 1px solid var(--line);
  border-radius: 28px;
  background: linear-gradient(135deg, rgba(255, 255, 255, 0.92), rgba(214, 239, 234, 0.88));
  box-shadow: var(--shadow);
}

.hero h1,
.section-header h2,
.card h3 {
  margin: 0;
  font-family: Georgia, "Times New Roman", serif;
  letter-spacing: 0.01em;
}

/* Review round 1 (2026-09-10) E4: .card h3 shared the hero/section-header
   margin: 0 above, so the first child right after it (.form-grid's first
   label, .role-notice, .detail-list, .stack -- none of which carry their
   own margin-top) sat flush against the heading (render/1024/
   tenant_policies.png). Give .card h3 its own margin-bottom without
   touching .hero h1 / .section-header h2, which stay margin: 0.
   Codex cycle 1 (2026-09-10) W1: the bare .card h3 selector applied to
   every .card on every screen -- it doubled the gap under .form-card's
   own grid gap (tenant_apps.html / setup.html headings) and, since
   ".card h3" outranks the single-class ".summary-card__title" by
   specificity, silently overrode that rule's own spacing on
   setup_summary.html. Scope to a modifier class present only on the
   tenant_detail.html / tenant_policies.html cards this was written for. */
.card--heading-gap h3 {
  margin-bottom: 16px;
}

/* Review round 2 (2026-09-10) F3: .section-header h2 (specifically
   tenant_policies.html's policy_console_title, "{tenant_name} の
   ポリシー") broke mid-word / put a trailing long-vowel mark 「ー」 at
   the start of the next line at 390px and 1024px, same CJK word-break
   reason as .role-notice above. Scoped to a .section-header--keep-all
   modifier (see setup_summary.html's existing --success modifier for
   the same pattern) rather than the bare .section-header h2 selector.
   Review round 4 (2026-09-10) W2/I2 (Opus): the round-2 comment above
   named "applying keep-all globally" as the cause of tenant_choose.html's
   tenant_choose_title overflow without pinning which overflow-wrap value
   was in effect -- this rule pairs word-break:keep-all with
   overflow-wrap:break-word, and that specific pairing does NOT overflow
   tenant_choose_title at 390px. Re-measured directly with
   render_con_tenant_screens.py --with-policies against two global (bare
   .section-header h2, no modifier class) variants: (a) word-break:
   keep-all + overflow-wrap:normal -- overflows tenant_choose at 390px
   (document.documentElement.scrollWidth=481 > 390); (b) word-break:
   keep-all + overflow-wrap:break-word (this rule's actual pairing) --
   all 45 screens (15 x 3 widths) render clean, no overflow. The
   .section-header--keep-all scoping below is still correct (it keeps
   this opinionated word-break off every other .section-header h2 that
   hasn't been checked against it), but the modifier is not covering for
   an overflow-wrap:break-word failure -- there isn't one. */
.section-header--keep-all h2 {
  word-break: keep-all;
  overflow-wrap: break-word;
}

.hero-copy,
.section-copy,
.subtle,
.meta-label,
.runtime-detail {
  color: var(--muted);
}

.hero-meta {
  display: grid;
  gap: 12px;
  min-width: 240px;
}

.meta-card,
.runtime-banner,
.flash,
.card {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--paper);
}

.meta-card {
  padding: 16px;
}

.meta-label,
.eyebrow {
  display: block;
  margin-bottom: 6px;
  font-size: 0.78rem;
}

/* review W2: uppercase + letter-spacing is a Latin-script convention --
   applying it to Japanese/Chinese text (the common case here) distorts
   the glyphs. Scope to :lang(en), same treatment as .data-table th
   below. */
:lang(en) .meta-label,
:lang(en) .eyebrow {
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.eyebrow {
  color: var(--brand);
}

.top-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 18px 0;
}

.top-nav a,
.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 42px;
  padding: 0 16px;
  border-radius: 999px;
  background: var(--brand);
  color: #fff;
  font-weight: 700;
}

.button--ghost,
.top-nav a {
  background: rgba(15, 118, 110, 0.08);
  color: var(--brand);
}

.inline-form {
  display: inline-flex;
  margin: 0;
}

button.button {
  border: 0;
  cursor: pointer;
  font: inherit;
}

.runtime-banner,
.flash {
  margin-bottom: 16px;
  padding: 14px 16px;
}

.runtime-banner--warning {
  background: rgba(226, 159, 45, 0.12);
}

.flash--success {
  background: var(--brand-soft);
}

/* #1435: only .flash--success had a tint, so every failure and warning fell
   back to the bare .flash rule and rendered on plain --paper -- visually
   weaker than a success. #1419 surfaces send failures through
   flash(..., "danger") specifically, so its whole point was lost on screen.
   The palette already carried --danger, and .runtime-banner--warning already
   set the house treatment for a soft 12% tint; both are reused here rather
   than inventing new colours. The paired selector also means the codebase's
   interchangeable "error"/"danger" categories look identical to a reader,
   which is the part that matters here -- collapsing them in Python is
   separate hygiene. */
.flash--danger,
.flash--error {
  background: rgba(166, 71, 47, 0.12);
}

.flash--warning {
  background: rgba(226, 159, 45, 0.12);
}

.content {
  display: grid;
  /* B2 follow-up (caught by render_con_tenant_screens.py's new scrollWidth
     assertion): with no grid-template-columns, this single implicit
     column defaults to auto min-sizing -- the max-content width of
     whatever it holds. tenant_list.html's new .table-wrap has
     overflow-x: auto specifically so a wide table scrolls internally
     instead of pushing the page wider, but that only works if something
     upstream can shrink below the table's intrinsic width first.
     minmax(0, 1fr) is the same fix .grid--one already applies to itself
     below; .content is always effectively single-column (multi-column
     layouts use nested .grid--two/.grid--three), so applying it here
     unconditionally is safe. */
  grid-template-columns: minmax(0, 1fr);
  gap: 20px;
}

.section-header,
.grid {
  display: grid;
  gap: 20px;
}

.grid--one {
  grid-template-columns: minmax(0, 1fr);
  max-width: 480px;
  margin: 0;
}

/* Round-2 residual (UI Review round 2): .grid--one's own "margin: 0 auto"
   shrink-wrapped it to its card's intrinsic width and then centred that
   narrower box inside .content -- a second, independent centring axis on
   top of .content:has(> .grid--one) below, so a short card narrower than
   480px landed at a different x/width than the header above it even
   though both rules capped max-width at 480px. Dropping the auto margin
   lets .grid--one simply fill the 480px column .content already
   constrains and centres, so header and card share one axis (measured:
   /login at 1280 -- header x=400 w=480, .grid--one x=400 w=480). */
.content:has(> .grid--one) {
  max-width: 480px;
  margin: 0 auto;
}

.section-header {
  grid-template-columns: 1fr auto;
  align-items: end;
}

.grid--two {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.card {
  padding: 22px;
  box-shadow: var(--shadow);
}

.card--center {
  text-align: center;
  padding: 48px 24px;
}

.admin-change-card {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
}

.data-table {
  width: 100%;
  border-collapse: collapse;
}

.data-table th,
.data-table td {
  padding: 12px 10px;
  border-bottom: 1px solid var(--line);
  text-align: left;
  vertical-align: top;
}

.data-table th {
  color: var(--muted);
  font-size: 0.88rem;
}

/* review W1: same Latin-only convention as .eyebrow/.meta-label above --
   scope to :lang(en) instead of removing outright. The #2055 exemption
   below (.data-table--nos-tenants th) predates this and stays as-is;
   it now overrides a rule that no longer fires for ja anyway. */
:lang(en) .data-table th {
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

/* #2055: /admin/nos-tenants — Japanese headers should not carry the Latin
   uppercase/letter-spacing treatment above; scoped to this table only so
   other .data-table pages (tenant_list.html, tenant_detail.html) keep
   their current look. */
.table-wrap {
  overflow-x: auto;
}

/* #2063 review 1 / R2 finding 5: column min-widths must sum to <= the
   .page-shell content box (1200 max-width - 40 padding = 1160px at
   >=1240px viewports), or the table always scrolls even when there is
   room. 130+190+150+170+130+70+180 = 1020. The per-column 12px 10px
   cell padding is not added on top: box-sizing: border-box (see the
   global reset above) means each column's min-width already includes
   its own padding, so the real column sum is 1020px, comfortably
   under the 1160px budget. */
.data-table--nos-tenants {
  min-width: 1160px;
}

.data-table--nos-tenants th {
  text-transform: none;
  letter-spacing: normal;
}

.data-table--nos-tenants .col-applied-at { white-space: nowrap; min-width: 130px; }
.data-table--nos-tenants .col-company { min-width: 190px; }
.data-table--nos-tenants .col-company__sub {
  display: block;
  margin-top: 2px;
  font-size: 0.78rem;
  color: var(--muted);
}
.data-table--nos-tenants .col-domain {
  overflow-wrap: anywhere;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.85rem;
  min-width: 150px;
}
.data-table--nos-tenants .col-admin-email {
  overflow-wrap: anywhere;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.85rem;
  min-width: 170px;
}
.data-table--nos-tenants .col-status { white-space: nowrap; min-width: 130px; }
.data-table--nos-tenants .col-ai-verify { white-space: nowrap; min-width: 70px; }
.data-table--nos-tenants .col-action { min-width: 180px; }

/* #3259: below a 1240px viewport the 1160px table min-width above pushed
   the action column past the right edge of .table-wrap, so the buttons
   were reachable only by an inner scroll the page does not announce.
   Let the table shrink, and pin the action column to the right edge
   (opaque, with a left shadow) so the buttons stay in view while the
   other columns scroll under it. The pin covers the whole range: the
   rendered width is not the 1020px min-width sum (nowrap timestamps,
   badges and English headers measured 1063px ja / 1149px en), so a
   narrower breakpoint left a band that overflowed with nothing pinned.
   The change-admin-email modals sit outside the table (a sticky td is
   its own stacking context; nos_tenant_requests.html). */
@media (max-width: 1239px) {
  .data-table--nos-tenants {
    min-width: 0;
  }

  .data-table--nos-tenants .col-action {
    position: sticky;
    right: 0;
    z-index: 1;
    background: var(--paper);
    box-shadow: -8px 0 8px -8px rgba(24, 37, 37, 0.25);
  }
}

.tag-row,
.button-row,
.stack {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

/* #2063 review R2 finding 2: the previous rewrite of the shared
   .button-row rule above changed 13 other templates at <=860px.
   Scope the tighter, baseline-aligned layout to the nos-tenants
   action column only; the shared rule above is untouched. */
.data-table--nos-tenants .button-row {
  align-items: center;
  gap: 6px 8px;
}

.stack--vertical {
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
  margin-top: 6px;
}

.tag,
.status-pill {
  display: inline-flex;
  align-items: center;
  min-height: 28px;
  padding: 0 10px;
  border-radius: 999px;
  background: rgba(15, 118, 110, 0.1);
  color: var(--brand);
  font-size: 0.9rem;
}

.status-pill {
  background: rgba(226, 159, 45, 0.14);
  color: #8a5c09;
}

.detail-list {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px 18px;
  margin: 0;
}

.detail-list div {
  padding-bottom: 10px;
  border-bottom: 1px solid var(--line);
}

.detail-list dt {
  color: var(--muted);
  font-size: 0.84rem;
}

.detail-list dd {
  margin: 4px 0 0;
  font-weight: 700;
}

.form-card,
.form-grid {
  display: grid;
  gap: 16px;
}

.form-grid {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.form-group {
  display: grid;
  gap: 8px;
}

.form-group label {
  font-weight: 700;
}

input[type="text"],
input[type="email"],
input[type="tel"],
input[type="password"],
select,
textarea {
  width: 100%;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: #fff;
  font: inherit;
  color: inherit;
}

input[type="text"]:focus,
input[type="email"]:focus,
input[type="tel"]:focus,
input[type="password"]:focus,
select:focus,
textarea:focus {
  outline: none;
  border-color: var(--brand);
  box-shadow: 0 0 0 3px rgba(15, 118, 110, 0.15);
}

/* apply ページ専用レイアウト */
.apply-shell {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 32px 16px 48px;
}

.apply-card {
  width: 100%;
  max-width: 560px;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 24px;
  padding: 40px 36px;
  box-shadow: var(--shadow);
}

.apply-brand {
  text-align: center;
  margin-bottom: 28px;
}

/* #2066 PR-2: apply.html renders this wrapper around one or more .flash
   messages (role="alert" for a11y), but it previously had no CSS rule of its
   own -- each .flash's own margin-bottom happened to create some visual
   spacing, but the group had no deliberate separation from the brand mark
   above it. */
.apply-card__errors {
  margin-bottom: 20px;
}

.apply-brand__img {
  max-height: 80px;
  display: block;
  margin: 0 auto;
}
.apply-brand__logo {
  font-family: Georgia, "Times New Roman", serif;
  font-size: 1.6rem;
  font-weight: 700;
  color: var(--brand);
  letter-spacing: 0.04em;
  display: block;
}

.apply-title {
  font-size: 1.25rem;
  font-weight: 700;
  margin: 0 0 8px;
  text-align: center;
}

.apply-desc {
  font-size: 0.9rem;
  color: var(--muted);
  text-align: center;
  margin: 0 0 28px;
}

/* #1423: success.html の完了メッセージ用。.apply-desc は申込/同意フォームの
   短いタグライン向けに中央揃えだが、完了メッセージは文章として読ませるため
   左揃えにする。.apply-desc 自体の中央揃えは変えない（他画面に影響させない）。 */
.success-message {
  text-align: left;
}

/* #2066 PR-2: apply.html's field .form-group elements had no gap rule of
   their own (only the 8px gap *inside* each .form-group), so consecutive
   labels sat flush against the previous input. Scoped to the apply.html
   form specifically (not the shared .apply-form class, which consent.html
   also uses with its own deliberate manual spacing between its two
   checkboxes) to avoid affecting consent.html's layout. */
.apply-form--fields {
  display: grid;
  gap: 20px;
}

.apply-form .field-hint {
  display: block;
  font-size: 0.8rem;
  color: var(--muted);
  margin: 2px 0 4px;
}

.apply-form .field-error {
  display: none;
  font-size: 0.8rem;
  color: var(--danger);
  margin-top: 4px;
}

/* #2066 PR-2: highlight a field that failed HTML5 constraint validation on
   a POST re-render (see apply.html's request.method == 'POST' script). */
.field-invalid {
  border-color: var(--danger);
  box-shadow: 0 0 0 3px rgba(166, 71, 47, 0.15);
}

.service-select {
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 16px;
  margin-bottom: 16px;
}
.service-select legend {
  font-weight: 600;
  font-size: 0.95rem;
  padding: 0 6px;
  margin-left: 4px;
}
.service-option {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 10px 0;
  border-bottom: 1px solid var(--line);
}
.service-option:last-of-type {
  border-bottom: none;
}
.service-option input[type="checkbox"] {
  margin-top: 3px;
}
.service-option label {
  display: flex;
  flex-direction: column;
  gap: 2px;
  cursor: pointer;
}
.service-desc {
  font-size: 0.8rem;
  color: var(--muted);
}
.service-option--disabled {
  opacity: .62;
}
.service-option--disabled label {
  cursor: not-allowed;
}
.service-badge {
  display: inline-flex;
  width: max-content;
  margin-top: 4px;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: .72rem;
  font-weight: 600;
}
.service-badge--unavailable {
  color: #7a4b00;
  background: #fff3cd;
  border: 1px solid #f0d98c;
}

.privacy-row {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 14px 16px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.7);
  margin-bottom: 24px;
}

.privacy-row input[type="checkbox"] {
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  margin-top: 2px;
  accent-color: var(--brand);
}

.privacy-row__label {
  font-size: 0.9rem;
  line-height: 1.5;
}

.submit-wrap {
  margin-top: 4px;
}

/* Issue #31 Phase 2-Con-pub: Cloudflare Turnstile widget spacing */
.turnstile-widget {
  margin-top: 16px;
}

.submit-wrap .btn--apply {
  width: 100%;
  padding: 14px;
  font-size: 1rem;
  font-weight: 700;
  border-radius: 14px;
  border: none;
  background: var(--brand);
  color: #fff;
  cursor: pointer;
  transition: background 0.15s, transform 0.1s;
}

.submit-wrap .btn--apply:hover {
  background: #0d6460;
  transform: translateY(-1px);
}

.submit-wrap .btn--apply:active {
  transform: translateY(0);
}

.apply-success {
  text-align: center;
  padding: 16px 0;
}

.apply-success .apply-desc {
  white-space: pre-line;
}

.apply-success__icon {
  margin-bottom: 12px;
  display: block;
  color: var(--brand);
}

.apply-success__icon svg {
  width: 56px;
  height: 56px;
  margin: 0 auto;
  display: block;
}

/* #2066 PR-2: consent_tos_body's "こちら"/"here" is now the anchor text
   itself, inline within the sentence (see i18n.py consent_tos_link_text and
   consent.html) rather than a separate duplicate link below the paragraph. */
.consent-tos-link {
  text-decoration: underline;
  font-weight: 600;
}

/* 公開ページ用言語切替 */
.lang-switcher {
  display: flex;
  justify-content: center;
  gap: 8px;
  margin-bottom: 24px;
  flex-wrap: wrap;
}

.lang-btn {
  padding: 4px 12px;
  font-size: 0.8rem;
  border-radius: 20px;
  border: 1px solid var(--line);
  background: transparent;
  color: var(--muted);
  text-decoration: none;
  transition: all 0.15s;
}

.lang-btn:hover {
  background: var(--brand-soft);
  color: var(--brand);
  border-color: var(--brand);
}

.lang-btn--active {
  background: var(--brand);
  color: #fff;
  border-color: var(--brand);
}

/* 管理画面ナビ用言語切替 */
.nav-lang-switcher {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  align-items: center;
}

.toggle-card {
  display: flex;
  gap: 14px;
  padding: 14px 16px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.7);
}

.toggle-card input {
  width: 20px;
  height: 20px;
  margin-top: 2px;
}

.toggle-card small {
  display: block;
  margin-top: 4px;
  color: var(--muted);
}

.check-list {
  margin: 0;
  padding-left: 18px;
  color: var(--muted);
}

/* #2066 PR-3: tenant_choose used a bare <ul> with no rule of its own, so
   the browser default disc marker rendered next to each pill (UI Review
   finding "「•」記号が pill 横に残る" -- grep for .tenant-choice-list in
   this file returned 0 hits before this block). */
.tenant-choice-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* #2066 PR-3: setup.html's right-hand "セットアップについて" card is far
   shorter than the left form card, but .grid--two's default stretch
   (grid items fill the row's height) made the card box stretch to match
   the form anyway, leaving a large empty area inside the card below its
   own content (UI Review finding "右カード下半分空白"). align-self:start
   scopes the fix to this one card rather than changing .grid--two, which
   tenant_apps.html / tenant_detail.html / tenant_policies.html /
   store_tenants/csv_import.html also use and is PR-4's territory.
   Round-2 residual: align-self:start alone still left ~800px of blank
   viewport below the short card while the tall left form kept scrolling
   at 1280x1024 (UI Review finding). position:sticky follows the scroll
   instead; there is no fixed/sticky header on this page to clear, so
   top:24px just echoes .page-shell's own 28px top padding / the 24px
   spacing rhythm used elsewhere in this file (e.g. .button-row above). */
.setup-about-card {
  align-self: start;
  position: sticky;
  top: 24px;
}

/* --- ユーザーバッジ --- */
.top-nav {
  justify-content: space-between;
  align-items: center;
}
.top-nav__links {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}
.user-badge {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
}
.user-badge__email {
  color: var(--muted);
  max-width: 320px;
  overflow-wrap: anywhere;
}
.user-badge__role {
  padding: 2px 10px;
  border-radius: 20px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.02em;
}
.user-badge__role--superadmin {
  background: var(--accent);
  color: #fff;
}
.user-badge__role--tenant_admin {
  background: var(--brand-soft);
  color: var(--brand);
}
.user-badge__role--readonly {
  background: var(--line);
  color: var(--muted);
}
.user-badge__logout {
  font-size: 12px;
  color: var(--muted);
  text-decoration: none;
  padding: 2px 8px;
  border: 1px solid var(--line);
  border-radius: 4px;
}
.user-badge__logout:hover {
  color: var(--text);
  border-color: var(--muted);
}

/* --- RBAC ガード --- */
/* Review round 2 (2026-09-10) F3: .role-notice text (e.g. policy_readonly_notice_body)
   broke mid-word and put a trailing long-vowel mark 「ー」 at the start of
   the next line at 390px and 1024px (render/390 and render/1024 of
   tenant_policies.png), same CJK word-break reason as the W3/E1 fixes
   above. Applied keep-all at all widths, not just the <=480px block,
   since this reproduced at 1024px too.
   Round-2 CHK re-run (2026-09-10) F7: that keep-all pairing regressed
   multi-sentence prose in .role-notice -- at 390px lines broke
   mid-phrase (rerun-2142 screenshots 25/390/policies.png) and at
   1280px lines averaged ~40% empty (25/1280/policies.png), because
   keep-all treats a whole unspaced CJK run as one unbreakable "word"
   and only allows a break at the rare punctuation boundary.
   .role-notice holds prose, not a short heading/label, so drop
   keep-all here and rely on the UA's normal CJK line-breaking (which
   already respects kinsoku -- it will not start a line with 「ー」);
   overflow-wrap: break-word stays as the fallback for a genuinely
   unbreakable long token. keep-all remains on .section-header--keep-all
   h2 (short titles) where it was introduced for the same 「ー」
   line-start bug -- that selector is unaffected.
   F11: word-break: normal alone was not enough -- the UA's default
   line-break mode (line-break: auto) does not guarantee kinsoku
   (Japanese line-breaking rules) is enforced, so a 390px line could
   still start with 「ー」. line-break: strict explicitly requests the
   stricter kinsoku ruleset (no line may start with a prolonged sound
   mark, small kana, etc.), independent of word-break. */
.role-notice {
  padding: 12px 16px;
  border-radius: 10px;
  font-size: 14px;
  margin-bottom: 16px;
  word-break: normal;
  overflow-wrap: break-word;
  line-break: strict;
}
.role-notice--info {
  background: var(--brand-soft);
  color: var(--brand);
  border: 1px solid var(--brand);
}
.toggle-card--readonly {
  opacity: 0.6;
  pointer-events: none;
}

/* --- セットアップフォーム --- */
.form-label--required::after {
  content: " *";
  color: var(--danger);
}
.setup-defaults-notice {
  padding: 12px 16px;
  background: var(--brand-soft);
  border-radius: 10px;
  font-size: 13px;
  margin-top: 8px;
}
.defaults-list {
  margin: 6px 0 0 16px;
  color: var(--muted);
}

/* --- 確認モーダル --- */
.modal {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
}
.modal[hidden] {
  display: none;
}
.modal__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.4);
}
.modal__panel {
  position: relative;
  background: var(--paper);
  border-radius: 18px;
  padding: 32px;
  max-width: 480px;
  width: 90%;
  box-shadow: 0 8px 32px rgba(0,0,0,0.18);
}
.modal__title {
  margin: 0 0 8px;
  font-size: 18px;
}
.modal__body {
  color: var(--muted);
  margin: 0 0 16px;
}
.modal__actions {
  display: flex;
  gap: 10px;
  margin-top: 20px;
}
.confirm-list {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 4px 16px;
  font-size: 14px;
}
.confirm-list dt {
  color: var(--muted);
  font-weight: 500;
}

/* --- サマリー画面 --- */
/* #2066/#2069 R2 D6: the "✓ "/"√" text glyph rendered as a fallback
   system-font check mark on some platforms (Windows Chrome). Replaced with
   the same inline SVG check used by success.html's .apply-success__icon,
   scaled down and set inline before the heading text instead of via
   ::before content (SVG can't be injected through a CSS pseudo-element). */
.section-header--success h2 {
  display: flex;
  align-items: center;
  gap: 8px;
}
.section-header--success__icon {
  display: inline-flex;
  color: var(--brand);
  flex-shrink: 0;
}
.section-header--success__icon svg {
  width: 26px;
  height: 26px;
  display: block;
}
.grid--three {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 20px;
  margin-bottom: 24px;
}
.summary-card__title {
  font-size: 15px;
  font-weight: 700;
  margin: 0 0 14px;
  color: var(--brand);
  border-bottom: 1px solid var(--line);
  padding-bottom: 8px;
}
.summary-dl {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 4px 12px;
  font-size: 14px;
}
.summary-dl dt {
  color: var(--muted);
  font-weight: 500;
}
.app-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.app-list__item {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.app-badge {
  padding: 3px 12px;
  border-radius: 20px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.04em;
}
.app-badge--toriteki { background: #dbeafe; color: #1d4ed8; }
.app-badge--mitsumori { background: #fef3c7; color: #92400e; }
.app-badge--core { background: #ede9fe; color: #5b21b6; }
/* #2066 PR-4: tenant_list.html's company-name cell had no dedicated rule,
   so at 390px it wrapped a long 法人名 into a tall stack of one/two
   characters per line. .table-wrap (see #2063) already scrolls
   horizontally, so a fixed min-width + nowrap is safe here. */
.tenant-list-table .col-company { white-space: nowrap; min-width: 160px; }
/* #2066 PR-4: tenant_apps.html's composition/ownership selects and
   tenant_policies.html's 14 selects + notes textarea render `disabled`
   for the con-tenant (customer-facing) service, but the browser default
   disabled style is too subtle to read as read-only at a glance. */
select:disabled,
textarea:disabled {
  background: #f3f4f6;
  /* #2098 round 2: #6b7280 on #f3f4f6 measured 4.393:1 (below WCAG AA
     4.5:1). #4b5563 on #f3f4f6 measures 6.867:1 (python: relative
     luminance formula, WCAG 2.x contrast ratio). */
  color: #4b5563;
  cursor: not-allowed;
}
/* #2055: base .status-badge had no rule at all (only the modifiers
   below), so the badge span rendered with no display/white-space of its
   own and could wrap one character per line inside a squeezed table
   cell. This gives every status badge a consistent nowrap pill shape. */
.status-badge {
  display: inline-flex;
  align-items: center;
  white-space: nowrap;
  padding: 3px 10px;
  border-radius: 999px;
  background: rgba(15, 118, 110, 0.1);
  color: var(--brand);
  font-size: 0.82rem;
  font-weight: 600;
}

/* #1437 review item 5 (Opus r3 INFO 10, r4 INFO): delivery-status
   modifiers for the status-badge used in nos_tenant_requests.html. */
.status-badge--delivered { background: #dcfce7; color: #15803d; }
.status-badge--bounced { background: #fee2e2; color: #b91c1c; }
.status-badge--complained { background: #fee2e2; color: #b91c1c; }
.status-badge--delayed { background: #fef3c7; color: #92400e; }

/* #2063 review 2: nos_tenant_requests.html derives
   status-badge--{{ req.status | lower | replace('_','-') }} but only
   failure/delivery modifiers existed above, so every request status
   (applied/rejected/expired/consented/password-pending/...) fell back to
   the base teal .status-badge. Add modifiers for the statuses the
   template actually renders distinct treatment for. */
.status-badge--rejected { background: #fee2e2; color: #b91c1c; }
.status-badge--expired { background: #e5e7eb; color: #4b5563; }
.status-badge--consented { background: #dcfce7; color: #15803d; }
.status-badge--password-pending { background: #fef3c7; color: #92400e; }
/* .status-badge--applied intentionally omitted: it only restated the
   base .status-badge rule above (same background/color), so the
   fallback already renders it correctly. */

/* #2063 review R2 finding 3: 5 of the 10 NOS_STATUS_* values in
   app.py had no modifier and fell back to the base teal badge. */
.status-badge--active { background: #bbf7d0; color: #166534; }
.status-badge--approved { background: #dcfce7; color: #15803d; }
.status-badge--email-verified { background: #dbeafe; color: #1d4ed8; }
.status-badge--ai-verified { background: #dbeafe; color: #1d4ed8; }
.status-badge--consent-pending { background: #fef3c7; color: #92400e; }
.button--sm {
  padding: 3px 12px;
  font-size: 12px;
  border-radius: 8px;
}

/* #2063 review 8/9: nos_tenant_requests.html's action column and modal
   use the .btn family (distinct from the older .button family used
   elsewhere in the app), but it had no base rules at all and rendered
   as bare browser-default buttons. This gives it the same visual
   language as .top-nav a / .button above (inline-flex, pill radius,
   font: inherit, weight 600+) using the existing design tokens. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: 36px;
  padding: 0 14px;
  border: 1px solid transparent;
  border-radius: 999px;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color 120ms ease, border-color 120ms ease, color 120ms ease;
}
.btn:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}
.btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
.btn--sm {
  min-height: 32px;
  padding: 0 12px;
  font-size: 12.5px;
}
.btn--primary {
  background: var(--brand);
  color: #fff;
}
.btn--primary:hover:not(:disabled) {
  filter: brightness(0.92);
}
.btn--success {
  background: var(--brand-soft);
  color: var(--ink);
  border-color: rgba(30, 118, 199, 0.4);
}
.btn--success:hover:not(:disabled) {
  filter: brightness(0.96);
}
.btn--secondary {
  background: rgba(15, 118, 110, 0.08);
  color: var(--brand);
}
.btn--secondary:hover:not(:disabled) {
  background: rgba(15, 118, 110, 0.16);
}
.btn--danger {
  background: transparent;
  color: var(--danger);
  border-color: var(--danger);
}
.btn--danger:hover:not(:disabled) {
  background: var(--danger);
  color: #fff;
}
/* #2063 review R2 finding 4: minimal .btn--ghost for low-emphasis
   actions in the .btn family. */
.btn--ghost {
  background: transparent;
  color: var(--brand);
  border-color: var(--line);
}
.btn--ghost:hover:not(:disabled) {
  background: rgba(15, 118, 110, 0.16);
}
.next-steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.next-steps__link {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px 14px;
  border: 1px solid var(--line);
  border-radius: 10px;
  font-size: 14px;
  text-decoration: none;
  color: var(--ink);
  transition: background 0.15s, border-color 0.15s;
}
.next-steps__link:hover {
  background: var(--brand-soft);
  border-color: var(--brand);
}
.next-steps__arrow {
  color: var(--muted);
  font-size: 16px;
}
.card--warning {
  border: 1px solid var(--accent);
  background: #fffbeb;
}
.card--warning h3 {
  color: var(--accent);
}
.blocker-list {
  padding-left: 18px;
  color: var(--ink);
}
.page-actions {
  display: flex;
  gap: 12px;
  margin-top: 24px;
  flex-wrap: wrap;
}
.button--primary {
  background: var(--brand);
  color: #fff;
}
.empty-state {
  color: var(--muted);
  font-size: 14px;
}

@media (max-width: 860px) {
  .hero,
  .section-header,
  .grid--two,
  .grid--three,
  .detail-list,
  .form-grid {
    /* B1/Opus-W5: a bare `1fr` track keeps its default auto min-size
       (the content's max-content width), so a wide child (e.g.
       .section-header's .button-row) still forced the grid wider than
       the viewport at 390px. minmax(0, 1fr) is the same fix .grid--one
       already uses above. */
    grid-template-columns: minmax(0, 1fr);
  }

  .hero {
    padding: 22px;
  }

  .top-nav,
  .button-row,
  .stack {
    flex-direction: column;
  }

  /* #2066 PR-3 round-2: .grid--two collapses to a single column above,
     so setup.html's form and about-card stack instead of sitting
     side-by-side. Sticky positioning exists only to track the tall
     left column while both sit in the same row; stacked, the about
     card would just pin itself mid-scroll under whatever precedes it,
     so drop back to normal flow here. */
  .setup-about-card {
    position: static;
  }
}

/* W3: at very narrow widths, long button/pill labels (e.g.
   「パスワード変更」) could break mid-character wherever the default
   line-breaking algorithm chose, which reads badly for Japanese CJK
   text with no natural word boundaries. keep-all avoids breaking within
   a run of same-script characters; overflow-wrap: anywhere is the
   fallback for a single unbreakable token (e.g. a long email address)
   so nothing overflows the viewport either way.
   Round-2 residual (UI Review round 2): .user-badge__logout (「パスワード
   変更」/「キャンセル」 in base.html's user badge) had the same mid-word
   break at 390px and was missed by the original W3 selector list. */
@media (max-width: 480px) {
  .button,
  .btn,
  .lang-btn,
  .user-badge__logout {
    word-break: keep-all;
    overflow-wrap: anywhere;
  }

  /* #2066/#2069 R2 D7: tenant_detail.html's 責務サマリ .data-table td cells
     (e.g. 「アプリ個別」→「アプリ個/別」, 「発注・納品・検収」→「発注・納/品・
     検収」) and .detail-list dd values broke mid-word at 390px, same W3
     CJK reason as above.
     Round-1 review (2026-09-10) E1: pairing overflow-wrap: anywhere with
     word-break: keep-all here still let the UA break inside a CJK run
     (「仕入先見積依/頼」「発注業務フ/ロー」「アプリ/個別」 in
     render/390/tenant_detail.png) — overflow-wrap: anywhere permits a
     break at any character, which wins over keep-all's "no break in a
     CJK/no-space run" rule. Drop overflow-wrap: anywhere and rely on
     word-break: keep-all alone; long unbreakable tokens (long email/URL
     values) can overflow their cell, but tenant_detail.html now wraps
     its .data-table in .table-wrap (overflow-x: auto, see #2063 review
     1) so the table scrolls instead of forcing a mid-word break.
     Review round 2 (2026-09-10) F2: the bare .data-table td selector
     applied this rule to every .data-table on the site, but most
     templates (uribiki/*.html, store_tenants/list.html,
     store_tenants/import_result.html, issuer_profiles/list.html) don't
     wrap their table in .table-wrap -- keep-all there changed their
     previous mid-word wrapping behaviour with no scroll fallback to
     compensate. Scope this rule to .table-wrap .data-table td so only
     the tables that actually get the scroll fallback change.
     Codex cycle 1 (2026-09-10) W2: .table-wrap .data-table td still hit
     every .table-wrap'd table, including tenant_list.html and
     nos_tenant_requests.html, which were never part of this 責務サマリ
     fix and have their own prior wrapping behaviour. Scope to
     .data-table--responsibility (tenant_detail.html's own table, see
     the F9 rule two blocks below) so only that table changes.
     Codex cycle 1 (2026-09-10) W3: .detail-list dd has no .table-wrap /
     overflow-x fallback like the table above, so a long unbroken token
     (long domain, email) with no CJK break opportunity could still
     overflow its cell at 390px. Add overflow-wrap: break-word, the same
     pairing already proven safe with word-break: keep-all by the W2/I2
     .section-header--keep-all re-measurement above (unlike
     overflow-wrap: anywhere, break-word only breaks when there is no
     other break opportunity, so it doesn't reintroduce the E1 CJK
     mid-word split). */
  .table-wrap .data-table--responsibility td {
    word-break: keep-all;
  }

  .detail-list dd {
    word-break: keep-all;
    overflow-wrap: break-word;
  }

  /* Round-2 CHK re-run (2026-09-10) F9: tenant_detail.html's 責務サマリ
     table 備考 column inherited the keep-all above too -- for a short
     but unspaced note value (e.g. 「情報源: 個別アプリ」) that left the
     cell with no break opportunity, so the text ran past the column and
     read as clipped at 390px with no visible scroll cue (rerun-2142
     screenshots 25/390/tenant_detail.png), even though .table-wrap
     technically scrolls. This column is a short note, not a heading, so
     let it wrap normally like ordinary CJK text and give it a floor
     width so the object/owner columns can't squeeze it to near zero. */
  .table-wrap .data-table--responsibility td.col-note {
    word-break: normal;
    min-width: 88px;
  }

  /* #2066 PM report 2026-09-09 16:4x JST residual: at 390px .hero kept
     the brand h1 and .nav-lang-switcher side by side with no wrap, so
     the 4 language pills stacked into a narrow vertical column instead
     of a row under the brand. .user-badge (email / role / パスワード変更
     / キャンセル) squeezed the same way into 3-line wraps. */
  .hero {
    flex-wrap: wrap;
  }

  .nav-lang-switcher {
    flex-basis: 100%;
  }

  .user-badge {
    flex-wrap: wrap;
  }

  .user-badge__logout {
    white-space: nowrap;
  }

  .user-badge__email {
    min-width: 0;
    overflow-wrap: anywhere;
  }
}

/* Round-2 residual (UI Review round 2): .data-table th headers (e.g.
   「ステータス」/「ドリフト」 in tenant_list.html) broke mid-word at 390px
   for the same CJK reason as W3 above. Every .data-table lives inside a
   .table-wrap (overflow-x: auto, see #2063 review 1), so forcing headers
   to stay on one line and letting the wrapper scroll horizontally is
   simpler than keep-all/overflow-wrap here and matches how the table
   body already handles overflow.
   Review round 1 (2026-09-10) E1: this comment was stale when written --
   tenant_detail.html's 責務サマリ table was not yet inside a .table-wrap,
   so its th cells still relied on keep-all/overflow-wrap (see the block
   above) instead of this rule. tenant_detail.html now wraps that table
   in .table-wrap too.
   Review round 2 (2026-09-10) F2: "all three .data-table usages" was
   still wrong -- only tenant_list.html, nos_tenant_requests.html and
   tenant_detail.html wrap their table in .table-wrap. Several other
   templates (uribiki/*.html, store_tenants/list.html,
   store_tenants/import_result.html, issuer_profiles/list.html) render a
   .data-table with no .table-wrap at all, and uribiki/exceptions.html
   wraps its table in an unrelated, undefined .table-wrapper class. This
   selector's .table-wrap scoping means it (and the keep-all rule above)
   only ever applies to the three templates that are actually wrapped;
   the others are untouched by either rule. */
.table-wrap .data-table th {
  white-space: nowrap;
}

.apply-footer {
  text-align: center;
  padding: 24px 16px;
  margin-top: 32px;
  font-size: 0.8rem;
  color: var(--muted);
}
.apply-footer__links {
  display: flex;
  justify-content: center;
  gap: 16px;
  margin-bottom: 8px;
}
.apply-footer__links a {
  color: var(--muted);
  text-decoration: underline;
}
.apply-footer__copy {
  margin: 0;
}

/* Issue #3098 -------------------------------------------------------------
   A (CJK wrapping): body copy broke mid-phrase in Japanese at 390px
   (「くださ/い。」) and left one or two characters alone on the last line
   (「ん。」). word-break: auto-phrase (Japanese only) keeps phrases
   together and fixed both in a local Chromium render; line-break: strict
   keeps kinsoku; text-wrap: pretty is kept as a hint but did not move the
   short Chinese last line (「打开。」, still open, see the PR).
   overflow-wrap: break-word still breaks a single long token (an email);
   unlike anywhere it does not shrink min-content, so .section-copy inside
   admin flex / grid items keeps its width (UI Review round 2). */
.section-copy,
.apply-desc,
.card--center p,
.legal-copy {
  line-break: strict;
  overflow-wrap: break-word;
  text-wrap: pretty;
}

/* #3164: the legal card holds a single .legal-copy paragraph; its UA
   margin made the card's inner space taller than wide. */
.legal-copy {
  margin: 0;
}

/* #3164: .legal-copy (legal_privacy / legal_tos) is left-aligned, so it
   takes auto-phrase but not the balance rule below. */
.section-copy:lang(ja),
.apply-desc:lang(ja),
.card--center p:lang(ja),
.legal-copy:lang(ja) {
  word-break: auto-phrase;
}

/* Measured (local Chromium render + UI Review, #3098): text-wrap: pretty did
   not move the 2-3 character last line at 390px. balance does, but only
   Japanese gets it: with auto-phrase it breaks between phrases, while in
   Chinese (no phrase segmentation) it moved the break into a word
   (「邀/请」), and on left-aligned card copy it left half the card empty.
   So: centred Japanese copy only. */
.apply-desc:lang(ja),
.card--center p:lang(ja) {
  text-wrap: balance;
}

/* B (header gap): public pages hide .top-nav (base.html), whose 18px
   margin was the only space between the header and the content. This
   margin collapses with .top-nav's on the other pages, so they keep 18px. */
.hero {
  margin-bottom: 18px;
}

/* #3164: the error page heading (error.html) was outside the Georgia group
   at the top, so en did not get it. Font only: the group's margin: 0 would
   undo the h2 margin that balances the centred card. */
.card--center h2 {
  font-family: Georgia, "Times New Roman", serif;
  letter-spacing: 0.01em;
}

/* C: Georgia and Latin letter-spacing do not suit Japanese / Chinese
   headings; keep the body font. .hero h1 (the Latin brand) keeps Georgia. */
.section-header h2:lang(ja),
.section-header h2:lang(zh),
.card h3:lang(ja),
.card h3:lang(zh),
.card--center h2:lang(ja),
.card--center h2:lang(zh) {
  font-family: inherit;
  letter-spacing: normal;
}

/* D (#3086 item 7): a card that opens or closes with body copy kept the UA
   paragraph margin, so its inner space was uneven. This also applies to
   admin cards that start or end with .section-copy (uribiki, master
   pages, issuer / store lists): intended. Centred cards
   (error.html) open with an h2 whose margin balances the last paragraph's,
   so they keep it. */
.card > .section-copy:first-child {
  margin-top: 0;
}

.card:not(.card--center) > .section-copy:last-child {
  margin-bottom: 0;
}
