@charset "utf-8";

/* =============================================================================
   Design-system primitives — ported from public/components-preview.html so the
   live app can use the planned tokens/components instead of one-off markup.
   Tokens are declared ONCE here; every component reads them via var() (no
   hardcoded design values at call sites). Loaded after styles.css in
   layouts.app, so these rules win on specificity ties.

   RESPONSIVE: mobile-first. Base styles target ≈375px; @media enhances upward.
   Canonical breakpoints (the ONLY three — see docs/responsive-conventions.md):
     max-width: 600px   → phone
     max-width: 1024px  → tablet / portal drawer
     min-width: 1025px  → desktop / portal icon-rail collapse
   ========================================================================== */
:root {
  /* ── Brand palette — 和紙・墨・朱 (washi paper · sumi ink · vermilion).  The
       portal reads as warm paper and ink; the official 日本寿司協会 Hinomaru
       #E60012 is used like a 落款 (artist's seal): small, intense, rare — the
       active-nav tick, the primary action, the brand mark. Never as large fills.
       Gold + green SURVIVE ONLY as semantic state tints (success / warning
       notices + status badges) — never as chrome. ───────────────────────────── */
  --ds-red-50:#FBE9E5; --ds-red-100:#F3CDC6; --ds-red-500:#E60012; --ds-red-700:#A40009;
  --ds-sun-rgb:230,0,18;                 /* Hinomaru disc — consumed via rgba(var(--ds-sun-rgb),α) */

  /* state-only tints (meaning, not chrome) */
  --ds-gold-50:#F9F2DF; --ds-gold-100:#F0E3BD; --ds-gold-500:#C9A24A; --ds-gold-700:#8F6F1E;
  --ds-green-50:#EFF2E6; --ds-green-100:#DDE4C8; --ds-green-500:#7F8F49; --ds-green-700:#57652B;

  /* semantic aliases */
  --ds-primary:var(--ds-red-500);
  --ds-primary-dark:var(--ds-red-700);
  --ds-success:var(--ds-green-500);
  --ds-warning:var(--ds-gold-500);
  --ds-app-bg:#F5F2EA;                   /* 生成り washi — the warm paper ground */
  --ds-surface:#FFFEFA;                  /* cards / menus — a sheet laid on the paper */
  --ds-cream:#EFEAE0;                    /* washi wash — hover + tag surfaces (was a pink tint) */
  --ds-ink:#211D17;                      /* 墨 sumi — warm near-black text */
  --ds-muted:#8C8273;                    /* faded-ink grey-brown */
  --ds-line:#E4DCCC;                     /* hairline 罫線 borders */

  /* typography — Noto Sans JP (the standard gothic) everywhere; display text is
     differentiated by weight + letter-spacing, not by face. The two tokens stay
     separate so a display face can be swapped back in one line later. */
  --ds-font-display:"Noto Sans JP","Hiragino Kaku Gothic ProN",-apple-system,sans-serif;
  --ds-font-body:"Noto Sans JP","Hiragino Kaku Gothic ProN",-apple-system,sans-serif;
}
/* Per-language faces (2026-09-16). The Japanese faces lack Simplified Chinese and
   Hangul glyphs, so zh/ko text fell through to the OS fallback one character at a
   time — mixed weights, and on the public site mixed serif/sans (styles.css says
   `body { font-family:"M PLUS 1p", serif }`). Each language gets the sibling of
   Noto Sans JP (partials/locale-fonts loads it for that locale only), and the
   public body face is swapped too, not just the tokens. */
html:lang(zh){
  --ds-font-display:"Noto Sans SC","PingFang SC","Hiragino Sans GB","Microsoft YaHei",sans-serif;
  --ds-font-body:"Noto Sans SC","PingFang SC","Hiragino Sans GB","Microsoft YaHei",sans-serif;
}
html:lang(ko){
  --ds-font-display:"Noto Sans KR","Apple SD Gothic Neo","Malgun Gothic",sans-serif;
  --ds-font-body:"Noto Sans KR","Apple SD Gothic Neo","Malgun Gothic",sans-serif;
}
html:lang(zh) body, html:lang(ko) body{ font-family:var(--ds-font-body); }
:root{

  --ds-shadow-soft:0 2px 14px -6px rgba(33,29,23,.12),0 1px 4px -1px rgba(33,29,23,.05);

  /* ── 8pt grid (THE spacing law) — every size, padding, margin, gap and
       line-height in this file is a multiple of 8px, drawn from this scale.
       Heights come in three control sizes; text line-heights snap to 16/24/32.
       1px hairline borders are the single exemption (industry-standard). ──── */
  --ds-space-1:8px;  --ds-space-2:16px; --ds-space-3:24px;
  --ds-space-4:32px; --ds-space-5:40px; --ds-space-6:48px;
  --ds-control-h:48px;     /* inputs, selects, textareas — the FIELD height (never a button) */
  --ds-control-h-md:40px;  /* THE button height — every action button in a card/page action row
                              (.btn3, .btn-ghost, .btn-danger, .btn-toggle, .star-btn, .notice__action)
                              is this, so any two buttons side by side always line up. Also avatars/icon boxes. */
  --ds-control-h-sm:32px;  /* compact toolbar controls + in-table actions (dense-table exemption) */
  --ds-chip-h:24px;        /* status chips, tags, badges (display-only, not touch targets) */
  --ds-row-h:48px;         /* minimum data-table row height */
  --ds-topbar-h:72px;      /* fixed topbar height — sticky table headers pin below it */
  --ds-radius:8px;
  --ds-radius-xl2:16px;

  /* fixed-header clearance — the <header> in styles.css is position:fixed and
     ~96px tall; anything that rides above page content must clear it. */
  --ds-header-h:96px;

  /* ── Responsive breakpoints — the SINGLE SOURCE OF TRUTH (docs/responsive-
     conventions.md). Phone ≤600, tablet/portal-drawer ≤1024, desktop ≥1025.
     IMPORTANT: CSS `@media` conditions CANNOT read custom properties, so every
     media query must repeat these literals — `@media (max-width:600px)` for the
     phone tier, etc. Keep those literals in sync with these values; do not invent
     new breakpoints. (The vars stay usable in JS / container contexts / inline
     calc where custom properties ARE allowed.) ─────────────────────────────── */
  --ds-bp-mobile:600px;    /* ≤ this = phone tier */
  --ds-bp-tablet:1024px;   /* ≤ this = tablet / portal off-canvas drawer */
}

/* ── Global notice band ─────────────────────────────────────────────────────
   Flash messages + the verify-email prompt. Sits in normal flow but pads past
   the fixed header so it never slides underneath it again. Only rendered when
   it actually has children (blade guards the wrapper). */
.site-notices {
  max-width:880px;
  margin:0 auto;
  padding:calc(var(--ds-header-h) + var(--ds-space-3)) 5% 0;
  display:flex;
  flex-direction:column;
  gap:var(--ds-space-2);
}

.notice {
  display:flex;
  align-items:flex-start;
  gap:var(--ds-space-2);
  padding:var(--ds-space-2) var(--ds-space-3);
  border-radius:var(--ds-radius-xl2);
  border:1px solid var(--ds-line);
  background:#fff;
  box-shadow:var(--ds-shadow-soft);
  color:var(--ds-ink);
  font-size:14px;
  line-height:24px;
  transition:opacity .4s ease, transform .4s ease;
}
.notice > p { margin:0; flex:1 1 auto; }
/* vertically centre the icon against the body (single-line action notices) */
.notice--center { align-items:center; }
/* separate an in-flow notice from the block that follows (card rhythm) */
.notice--spaced { margin-bottom:var(--ds-space-3); }

/* auto-dismiss fade-out (success flash only; see partials/notices) */
.notice.is-dismissing { opacity:0; transform:translateY(-6px); }

.notice__icon {
  flex:none;
  width:24px; height:24px;
  margin-top:0;
  border-radius:9999px;
  display:grid; place-items:center;
  font-size:13px; font-weight:700; line-height:1;
  color:#fff;
}

/* success — matcha (抹茶) */
.notice--success { background:var(--ds-green-50); border-color:var(--ds-green-100); }
.notice--success .notice__icon { background:var(--ds-success); }

/* warning / action-needed — gold */
.notice--warning { background:var(--ds-gold-50); border-color:var(--ds-gold-100); }
.notice--warning .notice__icon { background:var(--ds-warning); color:var(--ds-ink); }

/* a notice that carries its own action (e.g. resend button) */
.notice__body { flex:1 1 auto; display:flex; flex-wrap:wrap; align-items:center; gap:var(--ds-space-1) var(--ds-space-2); }
.notice__body p { margin:0; }

.notice__action {
  flex:none;
  display:inline-flex; align-items:center;
  height:var(--ds-control-h-md); padding:0 var(--ds-space-2);
  border:0; border-radius:9999px;
  background:var(--ds-primary); color:#fff;
  font-size:13px; font-weight:700; line-height:16px;
  text-decoration:none; cursor:pointer;
  transition:background .15s;
}
.notice__action:hover { background:var(--ds-primary-dark); }
.notice__action:focus-visible { outline:2px solid var(--ds-primary-dark); outline-offset:2px; }

@media (max-width:600px) {
  .site-notices { padding-top:calc(var(--ds-header-h) + var(--ds-space-1)); }
  .notice { flex-direction:row; }
  .notice__action { width:100%; justify-content:center; margin-top:var(--ds-space-1); }
}

/* =============================================================================
   Authenticated app shell (portal) — the post-login UI for chef/admin/store.
   Distinct from the marketing layout (layouts.app + the fixed .header_top):
   visitors get the marketing chrome, signed-in users get this dedicated shell
   (sidebar + sticky topbar + content cards). Ported by hand from the sidebar/
   topbar design in public/components-preview.html — no Tailwind/Alpine runtime,
   just these classes reading the tokens above.
   ========================================================================== */
.portal-body {
  margin:0;
  min-height:100vh;
  font-family:var(--ds-font-body);
  color:var(--ds-ink);
  /* Hinomaru — a faint red disc fixed at the centre of the viewport; the
     content scrolls over it. On the warm washi ground it is barely-there —
     an elegant watermark, never competing with the ink. */
  background-color:var(--ds-app-bg);
  background-image:
    radial-gradient(circle at 50% 42%,
      rgba(var(--ds-sun-rgb),.05) 0,
      rgba(var(--ds-sun-rgb),.035) 18vmin,
      rgba(var(--ds-sun-rgb),.015) 26vmin,
      transparent 34vmin);
  background-repeat:no-repeat;
  background-position:center;
  background-attachment:fixed;
  -webkit-font-smoothing:antialiased;
}
.portal { display:flex; min-height:100vh; }

/* ── sidebar ─────────────────────────────────────────────────────────────── */
.portal__sidebar {
  position:fixed; top:0; left:0; bottom:0; z-index:40;
  width:264px; box-sizing:border-box;
  display:flex; flex-direction:column;
  background:var(--ds-surface); border-right:1px solid var(--ds-line);
  padding:var(--ds-space-3) var(--ds-space-2);
  transition:transform .25s ease;
}
/* The nav groups scroll; brand (top) + collapse (bottom) stay put, so a tall staff
   menu reaches its last item on a short viewport in every mode (#144). */
.portal__scroll { flex:1 1 auto; min-height:0; overflow-y:auto; overflow-x:hidden; }
.portal__brand, .portal__collapse { flex:none; }
/* Padding matches .portal__link, so the mark's left edge lines up with the nav
   icons (マイページ等) when expanded. Collapsing animates the left padding down so
   the mark re-centres in the rail without snapping. */
.portal__brand { display:flex; align-items:center; padding:var(--ds-space-1) var(--ds-space-2); margin-bottom:var(--ds-space-2); text-decoration:none; transition:padding .25s ease; }
/* The text owns its own left gap (matching the link's 12px), not a flex `gap`, so
   the rail can animate it to zero in lockstep with the sidebar width — the mark
   never shifts, the text just fades and slides away. */
.portal__brand-text {
  display:flex; flex-direction:column; min-width:0;
  margin-left:var(--ds-space-2); max-width:200px; overflow:hidden; white-space:nowrap;
  transition:opacity .18s ease, max-width .25s ease, margin-left .25s ease;
}
/* The association logo — a square tile that carries the brand red itself, so it
   needs no seal behind it (it replaced a drawn 判子 circle). Left square on
   purpose: the artwork's red blocks run to the edge, so a rounded box would clip
   their corners. `contain` keeps the tile intact whatever the source file's
   aspect ratio; the box stays 40px square so the icon rail's alignment is
   unchanged. */
.portal__brand-mark {
  height:40px; width:40px; flex:none;
  object-fit:contain;
}
.portal__brand-name { font-family:var(--ds-font-display); font-weight:600; font-size:18px; letter-spacing:.08em; line-height:24px; color:var(--ds-ink); }
.portal__brand-sub  { font-size:10.5px; letter-spacing:.04em; color:var(--ds-muted); margin-top:0; line-height:16px; }

.portal__navhead {
  padding:0 var(--ds-space-2); margin:0 0 var(--ds-space-1);
  font-size:10px; font-weight:700; letter-spacing:.16em; line-height:16px; text-transform:uppercase; color:var(--ds-muted);
  white-space:nowrap; overflow:hidden;
}
.portal__nav { list-style:none; margin:0 0 var(--ds-space-3); padding:0; display:flex; flex-direction:column; gap:0; }
.portal__nav--bottom { margin-top:0; margin-bottom:0; }
.portal__link {
  position:relative;
  display:flex; align-items:center; width:100%; box-sizing:border-box;
  padding:var(--ds-space-1) var(--ds-space-2); border:0; border-radius:var(--ds-radius); background:none;
  color:rgba(33,29,23,.66); font-size:14px; font-weight:500; line-height:24px;
  text-decoration:none; cursor:pointer;
  transition:background .15s, color .15s;
}
.portal__link:hover { background:var(--ds-cream); color:var(--ds-ink); }
/* Active = ink text on a washi wash with a vermilion 朱線 tick at the left edge —
   the seal-red stays a fine accent instead of a heavy filled pill. */
.portal__link.is-active { background:var(--ds-cream); color:var(--ds-ink); font-weight:700; }
.portal__link.is-active::before {
  content:""; position:absolute; left:0; top:24%; bottom:24%; width:3px;
  border-radius:9999px; background:var(--ds-primary);
}
.portal__link.is-active svg { color:var(--ds-primary); }
.portal__link svg { height:24px; width:24px; flex:none; }
/* nowrap + own overflow so the label NEVER wraps while the rail width animates
   (wrapping in the narrow rail is what made the row grow and the icon jump). Its
   left margin (= the old flex gap) and width animate to zero on collapse. */
/* text-overflow so a label wider than the 264px sidebar (long ja compounds like
   スカウトメッセージ管理, and the longer en wordings — "Public visibility settings")
   degrades to an ellipsis instead of being cut mid-glyph. The full label stays
   reachable: every link carries `title`, and the rail has its hover tooltip. */
.portal__link-text {
  margin-left:var(--ds-space-2); max-width:200px; overflow:hidden; white-space:nowrap;
  text-overflow:ellipsis;
  transition:opacity .18s ease, max-width .25s ease, margin-left .25s ease;
}
.portal__link--disabled { color:rgba(43,36,34,.34); cursor:default; }
.portal__link--disabled:hover { background:none; color:rgba(43,36,34,.34); }
.portal__badge {
  margin-left:auto; box-sizing:border-box; overflow:hidden; white-space:nowrap;
  font-size:10px; font-weight:700; line-height:16px; color:var(--ds-gold-700);
  background:var(--ds-gold-50); padding:0 var(--ds-space-1); border-radius:9999px;
  transition:opacity .18s ease, max-width .25s ease;
}

/* Label available to screen readers only — the badge's bare number needs to say
   what it counts. Standard clip pattern; no design values involved. */
.sr-only {
  position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap; border:0;
}

/* ── body column ─────────────────────────────────────────────────────────── */
.portal__body { flex:1 1 auto; min-width:0; margin-left:264px; display:flex; flex-direction:column; transition:margin-left .25s ease; }

/* ── topbar (a <div>, NOT <header>, to dodge styles.css `header{position:fixed}`) */
.portal__topbar {
  position:sticky; top:0; z-index:30;
  display:flex; align-items:center; gap:var(--ds-space-2);
  box-sizing:border-box; height:var(--ds-topbar-h); padding:0 var(--ds-space-3);
  background:rgba(245,242,234,.82); backdrop-filter:blur(8px); -webkit-backdrop-filter:blur(8px);
  border-bottom:1px solid var(--ds-line);
}
.portal__title { font-family:var(--ds-font-display); font-size:21px; font-weight:600; letter-spacing:.06em; line-height:32px; margin:0; color:var(--ds-ink); }
.portal__topbar-spacer { flex:1 1 auto; }
.portal__avatar {
  height:var(--ds-control-h-md); width:var(--ds-control-h-md); flex:none; border-radius:9999px;
  background:var(--ds-ink); color:var(--ds-surface);
  display:grid; place-items:center;
  font-family:var(--ds-font-display); font-weight:600; font-size:15px; line-height:1;
}
.portal__user-meta { line-height:16px; text-align:left; }
.portal__user-name { display:block; font-size:14px; font-weight:700; color:var(--ds-ink); }
.portal__user-role { display:block; font-size:11px; color:var(--ds-muted); }

/* ── account menu (name → language / profile / logout disclosure) ─────────── */
.portal__usermenu { position:relative; }
.portal__usermenu > summary {
  list-style:none; cursor:pointer;
  display:inline-flex; align-items:center; gap:var(--ds-space-1);
  padding:0 var(--ds-space-1); border:1px solid var(--ds-line); border-radius:var(--ds-radius);
  background:var(--ds-surface); transition:background .15s, border-color .15s;
}
.portal__usermenu > summary::-webkit-details-marker { display:none; }
.portal__usermenu > summary:hover { background:var(--ds-cream); border-color:var(--ds-red-100); }
.portal__usermenu-caret { height:16px; width:16px; color:var(--ds-muted); transition:transform .15s; }
.portal__usermenu[open] > summary .portal__usermenu-caret { transform:rotate(180deg); }

.portal__usermenu-panel {
  position:absolute; right:0; top:calc(100% + var(--ds-space-1)); min-width:216px; max-width:280px; z-index:50;
  padding:var(--ds-space-1); background:var(--ds-surface);
  border:1px solid var(--ds-line); border-radius:var(--ds-radius-xl2); box-shadow:var(--ds-shadow-soft);
}
/* Which account am I on? name + email at the top of the menu (#154). */
.portal__usermenu-identity {
  margin:var(--ds-space-1) var(--ds-space-1) var(--ds-space-2);
  padding:0 var(--ds-space-1) var(--ds-space-2); border-bottom:1px solid var(--ds-line);
}
.portal__usermenu-identity-label {
  display:block; font-size:11px; font-weight:700; line-height:16px; letter-spacing:.04em;
  text-transform:uppercase; color:var(--ds-muted);
}
.portal__usermenu-identity-name { display:block; font-size:14px; font-weight:700; color:var(--ds-ink); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.portal__usermenu-identity-email { display:block; font-size:12px; color:var(--ds-muted); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.portal__usermenu-head {
  margin:var(--ds-space-1) var(--ds-space-1) 0; font-size:11px; font-weight:700; line-height:16px; letter-spacing:.04em;
  text-transform:uppercase; color:var(--ds-muted);
}
.portal__usermenu-item {
  display:flex; align-items:center; gap:var(--ds-space-1); width:100%; box-sizing:border-box;
  padding:var(--ds-space-1); border:0; border-radius:var(--ds-radius); background:transparent;
  font:inherit; font-size:13.5px; font-weight:600; line-height:24px; color:var(--ds-ink);
  text-align:left; text-decoration:none; cursor:pointer;
  transition:background .12s;
}
.portal__usermenu-item:hover { background:var(--ds-cream); }
.portal__usermenu-item svg { height:16px; width:16px; flex:none; color:var(--ds-muted); }
.portal__usermenu-item.is-active { color:var(--ds-primary); }
.portal__usermenu-item.is-active svg { color:var(--ds-primary); }
.portal__usermenu-check { margin-left:auto; height:16px; width:16px; }
.portal__usermenu-logout { margin:var(--ds-space-1) 0 0; padding-top:var(--ds-space-1); border-top:1px solid var(--ds-line); }

/* ── main content ────────────────────────────────────────────────────────── */
/* `margin-inline:auto` keeps the content column (and the cards in it) centred at
   every width. The card resize is driven by `.portal__body`'s `margin-left`
   transition (.25s, in lockstep with the sidebar): when the sidebar collapses,
   the body reclaims its 264px and the cards grow into it evenly from both
   sides — a smooth width reflow (text stays crisp, no transform/scale). */
.portal__main { flex:1 1 auto; box-sizing:border-box; width:100%; max-width:1280px; margin-inline:auto; padding:var(--ds-space-4) var(--ds-space-4) var(--ds-space-6); }
.portal__main > * + * { margin-top:var(--ds-space-3); }
/* the global notice band clears the marketing fixed-header by default; inside the
   portal the topbar is sticky and short, so reset that clearance here. */
.portal .site-notices { max-width:none; margin:0; padding:0; }

/* The transient success flash floats as a TOAST inside the portal: fixed under
   the topbar, top-right, overlaying the content — it never participates in
   layout, so tables don't jump when it appears and the 5s auto-dismiss never
   reflows the page. The persistent verify-email banner (.notice--warning)
   stays in flow on purpose: a permanent floating overlay would sit on top of
   content forever. Marketing pages keep the flow band (scoped .portal). */
.portal .notice--success {
  position:fixed;
  top:calc(var(--ds-topbar-h) + var(--ds-space-2));
  right:var(--ds-space-3);
  z-index:90;
  width:min(416px, calc(100vw - 2 * var(--ds-space-3)));
  box-shadow:0 8px 24px -8px rgba(33,29,23,.3), var(--ds-shadow-soft);
  animation:ds-toast-in .25s ease;
}
@keyframes ds-toast-in {
  from { opacity:0; transform:translateY(-8px); }
  to   { opacity:1; transform:none; }
}
@media (max-width:600px) {
  .portal .notice--success { right:var(--ds-space-2); top:calc(var(--ds-topbar-h) + var(--ds-space-1)); }
}

/* ── content card (design-system) ────────────────────────────────────────── */
.card {
  background:var(--ds-surface); border:1px solid var(--ds-line); border-radius:var(--ds-radius-xl2);
  box-shadow:var(--ds-shadow-soft); padding:var(--ds-space-4);
}
.card + .card { margin-top:var(--ds-space-3); }
.card__head { margin:0 0 var(--ds-space-2); }
/* a split header — title on the left, a secondary link/action on the right. */
.card__head--split { display:flex; align-items:center; justify-content:space-between; gap:var(--ds-space-2); flex-wrap:wrap; }
/* Mincho heading led by a slim 朱 (vermilion) tick — the editorial 見出し罫 that
   carries the brand red at hairline scale across every card. */
.card__title {
  display:flex; align-items:center; gap:var(--ds-space-2);
  font-family:var(--ds-font-display); font-size:19px; font-weight:600; letter-spacing:.05em;
  line-height:32px; margin:0; color:var(--ds-ink);
}
.card__title::before {
  content:""; flex:none; width:3px; height:24px; border-radius:9999px;
  background:var(--ds-primary);
}
.card__lead  { color:var(--ds-muted); font-size:13px; line-height:24px; margin:var(--ds-space-1) 0 0; }

/* a quiet card footer — a hairline rule then the secondary action (e.g. the
   "退会" outline button under the membership panel). */
.card__foot { margin:var(--ds-space-4) 0 0; padding:var(--ds-space-3) 0 0; border-top:1px solid var(--ds-line); }

/* the one-line caveat under that action — 退会 and 解約 sit close together and are
   easy to mistake for each other, so the footer link carries its own explanation. */
.card__foot-note { display:block; margin-top:6px; color:var(--ds-muted); font-size:12px; line-height:20px; }

/* one centred row of actions where at least one of them is a <form> — a <form> is
   not valid inside <p>, so the browser closes the paragraph early and the buttons
   fall onto separate lines. This wrapper is the container to use instead. */
.btn-row { display:flex; flex-wrap:wrap; align-items:center; justify-content:center; gap:var(--ds-space-2); margin:var(--ds-space-3) 0 0; }
.btn-row form { margin:0; }

/* --tight: the same row used INSIDE a table cell or card footer, where the block
   margin above would push the row out of its cell. Actions only, no heading. */
.btn-row--tight { margin:0; gap:var(--ds-space-1); }

@media (max-width:600px) {
  .btn-row:not(.btn-row--tight) { flex-direction:column; align-items:stretch; }
  .btn-row:not(.btn-row--tight) .btn3, .btn-row:not(.btn-row--tight) .btn-ghost,
  .btn-row:not(.btn-row--tight) form, .btn-row:not(.btn-row--tight) form .btn3,
  .btn-row:not(.btn-row--tight) form .btn-ghost { width:100%; text-align:center; }
}

/* ── ⓘ info tooltip (x-info-tip) — an icon beside a heading that reveals its
   explanation on hover / keyboard focus, so helper prose takes no layout space.
   :focus (not :focus-visible) also opens it, covering tap-to-focus on touch. ── */
.info-tip { position:relative; display:inline-flex; }
.info-tip__trigger {
  display:inline-flex; align-items:center; justify-content:center;
  width:var(--ds-chip-h); height:var(--ds-chip-h); padding:0;
  border:0; border-radius:9999px; background:none; color:var(--ds-muted); cursor:help;
}
.info-tip__trigger:hover, .info-tip__trigger:focus { color:var(--ds-ink); }
.info-tip__trigger:focus-visible { outline:2px solid var(--ds-primary); outline-offset:2px; }
.info-tip__trigger svg { width:16px; height:16px; }
.info-tip__bubble {
  position:absolute; left:0; top:calc(100% + var(--ds-space-1));
  z-index:60; width:max-content; max-width:min(320px, calc(100vw - var(--ds-space-6)));
  background:var(--ds-ink); color:#fff; font-size:13px; font-weight:500; line-height:24px;
  letter-spacing:normal; text-align:left;
  padding:var(--ds-space-1) var(--ds-space-2); border-radius:var(--ds-radius); box-shadow:var(--ds-shadow-soft);
  opacity:0; visibility:hidden; pointer-events:none; transition:opacity .15s;
}
.info-tip:hover .info-tip__bubble,
.info-tip__trigger:focus + .info-tip__bubble { opacity:1; visibility:visible; }

/* ── Account page (8.1 export + 6.6 退会). Two distinct blocks: a calm data-export
   section and a red DANGER ZONE for the irreversible withdrawal, so the destructive
   action can never read as a routine save. ──────────────────────────────────── */
.card .account-section { margin:var(--ds-space-2) 0 0; }
.card .account-section__title { margin:0 0 4px; font-size:15px; font-weight:800; color:var(--ds-ink); letter-spacing:.01em; text-align:left; }
.account-section .btn_b { margin-top:10px; }
/* An action row (<p class="c"> holding pill buttons) that follows a text
   paragraph gets pulled up by the legacy `main p + p { margin-top:-15px }`
   (styles.css, written for the marketing pages' tight prose) — at 40px tall the
   buttons then sit ON the preceding line (e.g. 一般会員's カード情報を変更 over
   its lead). Pin a real top gap; higher specificity beats the legacy rule. */
.portal .card p.c:has(> .btn3, > .btn-ghost, > .btn-danger) { margin-top:var(--ds-space-2); }
.account-download { gap:8px; }
.account-download svg { width:15px; height:15px; }

.danger-zone {
  margin:var(--ds-space-4) 0 0; padding:var(--ds-space-3);
  border:1px solid var(--ds-red-100); border-radius:var(--ds-radius-xl2); background:var(--ds-red-50);
}
.card .danger-zone__title { display:flex; align-items:center; gap:8px; margin:0 0 var(--ds-space-1); font-size:15px; font-weight:800; color:var(--ds-primary-dark); }
.danger-zone__title svg { width:18px; height:18px; flex:0 0 auto; }
.danger-zone__warn { margin:0 0 4px; font-weight:700; color:var(--ds-primary-dark); font-size:13.5px; }
.danger-zone p:not(.danger-zone__warn) { margin:0; color:var(--ds-ink); font-size:13px; line-height:1.7; }
.danger-zone__actions { display:flex; justify-content:flex-end; align-items:center; gap:var(--ds-space-2); flex-wrap:wrap; margin-top:var(--ds-space-3); }

/* destructive action — outline red by default (reads "careful, irreversible"),
   fills on hover; distinct from the solid-red primary CTA. */
.btn-danger {
  display:inline-flex; align-items:center; justify-content:center; gap:8px;
  height:var(--ds-control-h-md); padding:0 var(--ds-space-3);   /* same size as .btn3 / .btn-ghost — it shares an action row with them */
  border:1px solid var(--ds-primary); border-radius:9999px; background:#fff;
  color:var(--ds-primary); font-family:inherit; font-size:13px; font-weight:800; letter-spacing:.02em;
  cursor:pointer; transition:background .15s, color .15s, border-color .15s;
}
.btn-danger:hover { background:var(--ds-primary); border-color:var(--ds-primary); color:#fff; }
@media (max-width:600px) {
  .danger-zone__actions { flex-direction:column-reverse; align-items:stretch; }
  .danger-zone__actions .btn-ghost, .danger-zone__actions .btn-danger { width:100%; }
}

/* a prominent price line for the membership/fee panels */
.price-line { margin:var(--ds-space-2) 0; font-size:14px; color:var(--ds-muted); }
.price-line strong { font-family:var(--ds-font-display); font-size:24px; color:var(--ds-ink); letter-spacing:.02em; margin-right:.15em; }

/* a highlighted "current X" stat box (admin pricing: the current plan). */
.plan-panel { display:flex; flex-direction:column; gap:2px;
  margin:0 0 var(--ds-space-3); padding:var(--ds-space-3) var(--ds-space-4);
  border:1px solid var(--ds-line); border-radius:var(--ds-radius-xl2); background:var(--ds-cream); }
.plan-panel__label { color:var(--ds-muted); font-size:12px; font-weight:700; letter-spacing:.04em; }
.plan-panel__price { color:var(--ds-muted); font-size:14px; }
.plan-panel__price strong { font-family:var(--ds-font-display); font-size:28px; color:var(--ds-ink); letter-spacing:.02em; margin-right:.1em; }

/* 合格証書一覧 — one bordered row per earned certificate; stacks on phones. */
.cert-list { list-style:none; margin:var(--ds-space-3) 0 0; padding:0; display:flex; flex-direction:column; gap:var(--ds-space-2); }
.cert-item { display:flex; align-items:center; justify-content:space-between; gap:var(--ds-space-2);
  padding:var(--ds-space-3); border:1px solid var(--ds-line); border-radius:var(--ds-radius-xl2); background:var(--ds-surface); }
.cert-item__main { display:flex; flex-direction:column; min-width:0; }
.cert-item__grade { font-weight:700; font-size:15px; color:var(--ds-ink); }
.cert-item__date { color:var(--ds-muted); font-size:12px; margin-top:2px; }
.cert-empty { color:var(--ds-muted); font-size:13px; text-align:center; padding:var(--ds-space-4) 0; }
@media (max-width: 600px) {
  .cert-item { flex-direction:column; align-items:stretch; }
  .cert-item .btn3 { width:100%; box-sizing:border-box; text-align:center; }
}

/* quick-link list used on the chef hub */
.portal-links { list-style:none; margin:0; padding:0; display:grid; gap:var(--ds-space-1); }
.portal-links--grid { grid-template-columns:repeat(auto-fill,minmax(220px,1fr)); }
.portal-link-card {
  display:flex; align-items:center; gap:var(--ds-space-2);
  padding:var(--ds-space-2); border:1px solid var(--ds-line); border-radius:var(--ds-radius-xl2);
  text-decoration:none; color:var(--ds-ink); font-weight:700; font-size:14px;
  background:var(--ds-surface); transition:border-color .15s, box-shadow .15s, transform .1s;
}
.portal-link-card:hover { border-color:var(--ds-red-100); box-shadow:var(--ds-shadow-soft); transform:translateY(-1px); }
.portal-link-card__icon {
  height:var(--ds-control-h-md); width:var(--ds-control-h-md); flex:none; border-radius:var(--ds-radius);
  background:var(--ds-cream); color:var(--ds-primary);
  display:grid; place-items:center;
}
.portal-link-card__icon svg { height:24px; width:24px; }
.portal-link-card--soon { color:var(--ds-muted); cursor:default; }
.portal-link-card--soon:hover { border-color:var(--ds-line); box-shadow:none; transform:none; }
.portal-link-card--soon .portal-link-card__icon { background:var(--ds-line); color:var(--ds-muted); }

/* membership pill on the hub */
.portal-status { display:flex; flex-wrap:wrap; align-items:center; gap:var(--ds-space-1) var(--ds-space-2); }
.portal-status__pill {
  display:inline-flex; align-items:center; gap:var(--ds-space-1);
  height:var(--ds-control-h-sm); padding:0 var(--ds-space-2); border-radius:9999px;
  font-size:13px; font-weight:700; line-height:16px;
  border:1px solid transparent; box-sizing:border-box;
}
.portal-status__pill--member { background:var(--ds-green-50); border-color:var(--ds-green-100); color:var(--ds-green-700); }
.portal-status__pill--none   { background:var(--ds-gold-50);  border-color:var(--ds-gold-100);  color:var(--ds-gold-700); }

/* The 一般会員 invitation that sits under the status pill for a chef who has sat an
   exam but never joined (chef/dashboard.blade.php). Deliberately quiet — it is an
   offer, not a task, so it takes the muted body colour rather than a .notice band,
   which on this page is reserved for things the chef must act on. Tokens only. */
.portal-status__invite { margin:var(--ds-space-2) 0 0; color:var(--ds-muted); font-size:14px; line-height:1.7; }
/* the appeal sentence (求人アピール, 2026-09-09) — ink, not muted: it is the line that
   sells membership / the job board, and in muted it read as a footnote. The
   requirement notes around it stay muted; this is the one that has to be read. */
.card__appeal { margin:var(--ds-space-2) 0 0; color:var(--ds-ink); font-size:15px; line-height:1.7; }
.portal-status__action { margin:var(--ds-space-2) 0 0; }

/* Subscription / account details — one tidy bordered panel of label↔value rows.
   Desktop/tablet: label left, value (text or a status pill) right. Phone: the row
   stacks (label above value). Values can hold a .portal-status__pill. */
.sub-details { list-style:none; margin:0 0 var(--ds-space-3); padding:0;
  border:1px solid var(--ds-line); border-radius:var(--ds-radius-xl2); overflow:hidden; background:var(--ds-surface); }
.sub-details__row { display:flex; align-items:center; justify-content:space-between; gap:var(--ds-space-2) var(--ds-space-4);
  margin:0; padding:var(--ds-space-3) var(--ds-space-4); border-top:1px solid var(--ds-line); }
.sub-details__row:first-child { border-top:0; }
.sub-details__label { margin:0; color:var(--ds-muted); font-size:13px; }
.sub-details__value { margin:0; color:var(--ds-ink); font-size:14px; font-weight:600; text-align:right; }
@media (max-width: 600px) {
  .sub-details__row   { flex-direction:column; align-items:flex-start; gap:2px; }
  .sub-details__value { text-align:left; }
}

/* breadcrumb-free heading helper for inner pages */
.portal-section-lead { color:var(--ds-muted); font-size:13px; line-height:24px; margin:0 0 var(--ds-space-2); }

/* ── mobile drawer (CSS-only via the hidden checkbox sibling) ─────────────── */
.portal__toggle { position:absolute; width:1px; height:1px; opacity:0; pointer-events:none; }
.portal__burger {
  display:none; height:var(--ds-control-h-md); width:var(--ds-control-h-md); flex:none;
  border:1px solid var(--ds-line); border-radius:var(--ds-radius); background:var(--ds-surface);
  cursor:pointer; place-items:center;
}
.portal__burger svg { height:24px; width:24px; color:var(--ds-ink); }
.portal__scrim { display:none; }

/* Desktop sidebar collapse/expand toggle — pinned to the bottom of the rail; the
   reliable control for switching between the 88px rail and the full sidebar
   (every other rail element is a link). Only on the persistent desktop sidebar. */
.portal__collapse { display:none; }
@media (min-width:1025px) {
  .portal__collapse {
    margin-top:auto; display:flex; align-items:center; justify-content:center;
    width:100%; height:var(--ds-control-h-md); box-sizing:border-box;
    border:0; border-top:1px solid var(--ds-line); background:none; cursor:pointer;
    color:var(--ds-muted); transition:color .15s, background .15s;
  }
  .portal__collapse:hover { color:var(--ds-ink); background:var(--ds-cream); }
  .portal__collapse svg { height:20px; width:20px; transition:transform .25s ease; }
  /* chevron points left to "collapse" when open; flips to point right to "expand". */
  .is-sidebar-collapsed .portal__collapse svg { transform:rotate(180deg); }
}

.portal__sidebar { transition:transform .25s ease, width .25s ease; }

@media (max-width:1024px) {
  .portal__sidebar { transform:translateX(-100%); box-shadow:0 20px 60px -20px rgba(43,36,34,.5); }
  .portal__body { margin-left:0; }
  .portal__burger { display:grid; }
  .portal__toggle:checked ~ .portal .portal__sidebar { transform:none; }
  .portal__toggle:checked ~ .portal__scrim {
    display:block; position:fixed; inset:0; z-index:35; background:rgba(43,36,34,.42);
  }
}

/* ≥1025px: collapsing shrinks the inline sidebar to an icon-only rail. To avoid
   jank, the rail keeps the SAME vertical metrics, left padding and font sizes as
   the expanded sidebar — toggling animates only `width`, so icons never jump.
   The 88px width leaves each icon centred on the 8pt grid: 16px sidebar padding
   + 16px link padding + 12px half-icon = 44px = width / 2. Labels/badges/brand-
   text just drop out (their removal doesn't shift the icons). Clicking the
   sidebar toggles it — anywhere except a real link, which navigates instead
   (see layouts.portal). */
@media (min-width:1025px) {
  .is-sidebar-collapsed .portal__sidebar { width:88px; cursor:pointer; }
  .is-sidebar-collapsed .portal__body { margin-left:88px; }

  /* brand text fades + collapses its width/margin to zero in sync with the sidebar
     width; the mark's left padding eases down so it centres in the 88px rail
     (16px sidebar + 8px padding + 20px half-mark = 44px = width / 2). No snap. */
  .is-sidebar-collapsed .portal__brand { padding-left:var(--ds-space-1); padding-right:var(--ds-space-1); }
  .is-sidebar-collapsed .portal__brand-text { opacity:0; max-width:0; margin-left:0; }

  /* Rail (88px): the group label text can't fit, so each header becomes a thin
     divider between icon clusters instead (#144). */
  .is-sidebar-collapsed .portal__navhead {
    font-size:0; line-height:0; padding:0; margin:var(--ds-space-1) var(--ds-space-2) var(--ds-space-2);
    border-top:1px solid var(--ds-line);
  }
  .is-sidebar-collapsed .portal__navhead--soon { display:none; }

  /* labels fade and collapse width to zero in sync with the rail — same as the
     brand text — so nothing wraps or jumps mid-animation. */
  .is-sidebar-collapsed .portal__link { position:relative; }
  .is-sidebar-collapsed .portal__link-text { opacity:0; max-width:0; margin-left:0; }

  /* A queue count must survive the rail — hiding it is how staff miss work. The
     pill sheds its digits and becomes a dot on the icon's top-right corner; the
     hover tooltip still names the screen. */
  .is-sidebar-collapsed .portal__badge {
    position:absolute; top:6px; left:34px; margin:0;
    width:8px; height:8px; max-width:8px; padding:0;
    font-size:0; line-height:0; color:transparent;
    background:var(--ds-primary);
  }

  /* hover tooltip — the label, surfaced just past the rail's right edge */
  .is-sidebar-collapsed .portal__link:hover::after {
    content:attr(data-label);
    position:absolute; left:calc(100% + var(--ds-space-2)); top:50%; transform:translateY(-50%);
    z-index:60; white-space:nowrap; pointer-events:none;
    background:var(--ds-ink); color:#fff; font-size:12px; font-weight:600;
    line-height:24px; padding:0 var(--ds-space-1); border-radius:var(--ds-radius); box-shadow:var(--ds-shadow-soft);
  }
}
@media (max-width:600px) {
  .portal__main { padding:var(--ds-space-2) var(--ds-space-2) var(--ds-space-6); }
  .card { padding:var(--ds-space-2); }
  .card__title { font-size:18px; }
  .portal__title { font-size:18px; }
  .portal__user-meta { display:none; }

  /* Account menu trigger → a bare round avatar BUTTON on phones: drop the pill box,
     the caret and the name/role, leaving just the tappable 40px avatar. The dropdown
     panel still opens right-aligned beneath it. */
  .portal__usermenu > summary { padding:0; border:0; background:none; gap:0; }
  .portal__usermenu > summary:hover { background:none; }
  .portal__usermenu-caret { display:none; }
  .portal__usermenu > summary:hover .portal__avatar { background:var(--ds-primary-dark); }
  .portal__usermenu[open] > summary .portal__avatar { box-shadow:0 0 0 3px var(--ds-red-50); }
}

/* =============================================================================
   Exam grade picker (mypage/exam/apply) — selectable grade cards so a chef can
   compare what each 級 involves, what passing earns, and the fee before choosing.
   Pure CSS selection state via :has() (no JS); the real radio stays focusable.
   ========================================================================== */
/* radio gutter shared by the header row and the detail block, so the detail text
   lines up directly under the grade name (not under the radio). */
.grade-pick { --grade-gutter:40px; display:grid; gap:var(--ds-space-2); margin:var(--ds-space-2) 0 var(--ds-space-1); }
.grade-option { display:block; cursor:pointer; }
.grade-option__radio { position:absolute; width:1px; height:1px; opacity:0; pointer-events:none; }

.grade-card2 {
  display:block;
  border:1.5px solid var(--ds-line); border-radius:var(--ds-radius-xl2); padding:var(--ds-space-2) var(--ds-space-3); background:var(--ds-surface);
  transition:border-color .15s, box-shadow .15s, background .15s;
}
.grade-option:hover .grade-card2 { border-color:var(--ds-red-100); background:var(--ds-surface); }
.grade-option:has(.grade-option__radio:checked) .grade-card2 {
  border-color:var(--ds-primary); background:#fff;
  box-shadow:0 0 0 3px rgba(230,0,18,.12), var(--ds-shadow-soft);
}
.grade-option:has(.grade-option__radio:focus-visible) .grade-card2 {
  box-shadow:0 0 0 3px rgba(230,0,18,.30);
}

/* header row: radio + name + mode tag (left), fee (right) — all centred so the
   radio sits exactly level with the grade name. */
.grade-card2__top { display:flex; align-items:center; gap:var(--ds-space-2); }
.grade-card2__radio {
  width:24px; height:24px; flex:none; box-sizing:border-box;
  border:2px solid var(--ds-ink); border-radius:50%; position:relative; transition:border-color .15s;
}
.grade-option:has(.grade-option__radio:checked) .grade-card2__radio { border-color:var(--ds-primary); }
.grade-option:has(.grade-option__radio:checked) .grade-card2__radio::after {
  content:""; position:absolute; inset:4px; border-radius:50%; background:var(--ds-primary);
}
.grade-card2__name { font-weight:700; font-size:17px; color:var(--ds-ink); line-height:24px; }
.grade-tag {
  display:inline-flex; align-items:center; height:var(--ds-chip-h);
  font-size:11px; font-weight:700; line-height:16px; padding:0 var(--ds-space-1); border-radius:9999px;
  background:var(--ds-cream); color:var(--ds-primary); white-space:nowrap;
}
.grade-card2__fee { margin-left:auto; text-align:right; line-height:24px; flex:none; }
.grade-card2__fee b { font-size:20px; font-weight:700; color:var(--ds-ink); }
.grade-card2__fee small { display:block; font-size:11px; line-height:16px; color:var(--ds-muted); margin-top:0; }

/* detail block, indented to align under the grade name */
.grade-card2__detail { display:block; padding-left:var(--grade-gutter); }
.grade-card2__meta { margin:var(--ds-space-2) 0 0; display:grid; gap:var(--ds-space-1); }
.grade-meta-row { display:flex; gap:var(--ds-space-2); font-size:13px; line-height:24px; }
.grade-meta-row dt { flex:none; width:80px; color:var(--ds-muted); font-weight:700; }
.grade-meta-row dd { margin:0; color:var(--ds-ink); }

.grade-card2__lock {
  display:inline-flex; align-items:center; gap:var(--ds-space-1); margin-top:var(--ds-space-2);
  height:var(--ds-control-h-sm); box-sizing:border-box;
  font-size:12px; font-weight:700; line-height:16px;
  color:var(--ds-gold-700); background:var(--ds-gold-50); border:1px solid var(--ds-gold-100);
  padding:0 var(--ds-space-1); border-radius:var(--ds-radius);
}
.grade-card2__lock svg { width:16px; height:16px; flex:none; }

/* locked: not selectable, dimmed, no hover/selection affordance */
.grade-option.is-locked { cursor:not-allowed; }
.grade-option.is-locked .grade-card2 { background:var(--ds-app-bg); border-color:var(--ds-line); box-shadow:none; }
.grade-option.is-locked:hover .grade-card2 { border-color:var(--ds-line); background:var(--ds-app-bg); }
.grade-option.is-locked .grade-card2__name,
.grade-option.is-locked .grade-card2__fee b { color:var(--ds-muted); }
.grade-option.is-locked .grade-card2__radio { border-color:var(--ds-line); }
.grade-option.is-locked .grade-tag { background:var(--ds-line); color:var(--ds-muted); }

.grade-guide-link {
  display:inline-flex; align-items:center; gap:var(--ds-space-1); margin-top:var(--ds-space-1);
  font-size:13px; font-weight:700; line-height:24px; color:var(--ds-primary); text-decoration:none;
}
.grade-guide-link:hover { color:var(--ds-primary-dark); text-decoration:underline; }
.grade-guide-link svg { width:16px; height:16px; }

@media (max-width:600px) {
  .grade-card2 { padding:var(--ds-space-2); }
  .grade-card2__top { flex-wrap:wrap; }
  .grade-card2__fee { margin-left:auto; }
  .grade-card2__detail { padding-left:0; }
  .grade-meta-row dt { width:64px; } /* 8×8 */
}

/* ── exam video upload (per set×angle slot) ──────────────────────────────────
   The chef's YouTube-URL submission list inside a .card. One .exam-slot per
   (set × angle), divided by a hairline; the label rides a fixed left column
   while the wide URL field + submit share the right column. Token-driven so it
   sits on the same palette as .card / .grade-* (no hardcoded design values). */
.exam-upload { margin-top:var(--ds-space-3); }
.exam-slot { padding:var(--ds-space-3) 0; border-top:1px solid var(--ds-line); }
.exam-slot:first-child { border-top:0; }

/* The label rides row 1 only (its area spans no further), so a validation error
   in row 2 never re-centers it against the taller column — the label stays
   aligned to the input row whether or not the error shows. */
.exam-slot__grid {
  display:grid;
  grid-template-columns:200px 1fr auto;
  grid-template-areas:
    "head input  button"
    ".    error  error";
  align-items:center;
  column-gap:var(--ds-space-3); row-gap:var(--ds-space-1);
}
.exam-slot__head   { grid-area:head; }
.exam-slot__name   { font-weight:700; font-size:15px; color:var(--ds-ink); line-height:24px; }
.exam-slot__angle  { font-size:13px; line-height:16px; color:var(--ds-muted); margin-top:0; }
.exam-slot__status {
  display:inline-flex; align-items:center; gap:var(--ds-space-1); margin-top:var(--ds-space-1);
  height:var(--ds-chip-h); box-sizing:border-box;
  font-size:11px; font-weight:700; line-height:16px; padding:0 var(--ds-space-1); border-radius:9999px;
  color:var(--ds-gold-700); background:var(--ds-gold-50); border:1px solid var(--ds-gold-100);
}
.exam-slot__status.is-reviewed {
  color:var(--ds-green-700); background:var(--ds-green-50); border-color:var(--ds-green-100);
}
.exam-input {
  grid-area:input; min-width:0; box-sizing:border-box; height:var(--ds-control-h);
  font-size:14px; line-height:24px; color:var(--ds-ink); background:#fff;
  padding:0 var(--ds-space-2); border:1px solid var(--ds-line); border-radius:var(--ds-radius);
  transition:border-color .15s, box-shadow .15s;
}
.exam-input::placeholder { color:var(--ds-muted); }
.exam-input:hover { border-color:var(--ds-red-100); }
.exam-input:focus { outline:none; border-color:var(--ds-primary); box-shadow:0 0 0 3px var(--ds-red-50); }
.exam-input.is-error { border-color:var(--ds-primary); background:var(--ds-red-50); }
/* Rendered by <button> AND <a> (e.g. the review screen's back link) — the
   explicit display/line-height/text-decoration keep both elements pixel-equal,
   instead of the anchor inheriting the global link underline + inline metrics.

   Size + shape deliberately mirror `.btn3` (same height token, pill radius,
   padding, font size): the exam screens put this in an ordinary action row next
   to a .btn-ghost, so it must line up with every other button in the app. What
   it keeps of its own is the disabled treatment below — the upload screen leans
   on a solidly greyed-out button to say "not every slot is filled yet". */
.exam-btn {
  grid-area:button; border:0; cursor:pointer; font-family:inherit;
  display:inline-flex; align-items:center; justify-content:center;
  box-sizing:border-box; height:var(--ds-control-h-md); line-height:16px; text-decoration:none;
  white-space:nowrap;
  font-size:13px; font-weight:700; letter-spacing:.08em;
  color:#fff; background:var(--ds-primary);
  padding:0 var(--ds-space-3); border-radius:9999px;
  transition:background .15s, transform .1s;
}
a.exam-btn:hover { color:#fff; text-decoration:none; }
.exam-btn:hover { background:var(--ds-primary-dark); }
.exam-btn:active { transform:scale(.98); }
.exam-btn:disabled,
.exam-btn:disabled:hover {
  background:var(--ds-line); color:var(--ds-muted);
  cursor:not-allowed; transform:none;
}
/* a disabled action button (e.g. withdraw while a payment is pending) */
.btn3:disabled,
.btn3:disabled:hover {
  opacity:.5; cursor:not-allowed; box-shadow:none; transform:none; filter:grayscale(1);
}
.exam-error { grid-area:error; margin:0; font-size:12px; line-height:16px; font-weight:600; color:var(--ds-primary-dark); }

/* The save / submit actions, pinned top-right of the upload card. Save stores every
   filled slot; Submit (for grading) is the explicit lock and stays disabled until
   every slot is filled and there is nothing left unsaved. */
.exam-actions { display:flex; justify-content:flex-end; gap:var(--ds-space-2); flex-wrap:wrap; margin-bottom:var(--ds-space-1); }
/* Scoring confirm bar: "Back to edit" on the left, the save/publish group on the right. */
.exam-actions--split { justify-content:space-between; align-items:center; }
.exam-actions .scoring-save { display:flex; align-items:center; gap:var(--ds-space-2); flex-wrap:wrap; }
@media (max-width:600px) {
  .exam-actions--split { flex-direction:column-reverse; align-items:stretch; }
  .exam-actions .scoring-save { flex-direction:column; }
}

/* The written-exam intro guidance list (案内), shown before the sitting begins. */
.exam-guide { margin:var(--ds-space-2) 0 var(--ds-space-3); padding-left:var(--ds-space-3); color:var(--ds-ink); font-size:14px; line-height:24px; }
.exam-guide li { margin:var(--ds-space-1) 0; }

/* ── Written-exam quiz (ticket 3.C.1). Each question is a card; each choice is a
   full-width selectable row that highlights when picked (via :has(:checked)).
   Replaces the legacy per-question <table class="ta1 ta3"> — one column, generous
   tap targets, reflows cleanly at every width. Radio inputs keep their name/value,
   so the auto-save JS is untouched. ─────────────────────────────────────────── */
.quiz { margin-top:var(--ds-space-2); }
.quiz-q { background:var(--ds-surface); border:1px solid var(--ds-line); border-radius:var(--ds-radius); padding:var(--ds-space-3); margin:0 0 var(--ds-space-2); }
.quiz-q__head { display:flex; gap:var(--ds-space-2); align-items:flex-start; margin:0 0 var(--ds-space-2); }
.quiz-q__num { flex:0 0 auto; min-width:26px; height:26px; box-sizing:border-box; padding:0 6px; border-radius:13px; background:var(--ds-primary); color:#fff; font-weight:800; font-size:13px; line-height:26px; text-align:center; }
.quiz-q__text { font-weight:700; font-size:15px; line-height:1.6; color:var(--ds-ink); }
.quiz-q__options { display:flex; flex-direction:column; gap:8px; }
.quiz-opt { position:relative; display:flex; align-items:center; gap:12px; min-height:48px; padding:11px 14px; border:1px solid var(--ds-line); border-radius:var(--ds-radius); background:var(--ds-surface); cursor:pointer; transition:border-color .12s, background .12s, box-shadow .12s; }
.quiz-opt:hover { border-color:var(--ds-red-100); background:var(--ds-cream); }
.quiz-opt__radio { position:absolute; opacity:0; width:1px; height:1px; margin:0; }
.quiz-opt__mark { flex:0 0 auto; position:relative; width:20px; height:20px; border-radius:50%; border:2px solid var(--ds-line); background:#fff; transition:border-color .12s; }
.quiz-opt__text { font-size:14.5px; line-height:1.5; color:var(--ds-ink); }
.quiz-opt:has(.quiz-opt__radio:checked) { border-color:var(--ds-primary); background:var(--ds-red-50); box-shadow:inset 0 0 0 1px var(--ds-primary); }
.quiz-opt:has(.quiz-opt__radio:checked) .quiz-opt__mark { border-color:var(--ds-primary); }
.quiz-opt:has(.quiz-opt__radio:checked) .quiz-opt__mark::after { content:''; position:absolute; inset:3px; border-radius:50%; background:var(--ds-primary); }
.quiz-opt:has(.quiz-opt__radio:focus-visible) { border-color:var(--ds-primary); box-shadow:0 0 0 3px var(--ds-red-50); }
.quiz-q__error { display:block; margin-top:var(--ds-space-1); }
@media (max-width:600px) { .quiz-q { padding:var(--ds-space-2); } }

/* Written-exam sitting (B12/#132). The sheet blocks text selection so the paper can't
   be swept into the clipboard (paired with oncopy/oncut/oncontextmenu on the element);
   inputs stay selectable so answers remain operable. */
.exam-sheet { -webkit-user-select:none; -moz-user-select:none; user-select:none; }
.exam-sheet input, .exam-sheet textarea { -webkit-user-select:text; -moz-user-select:text; user-select:text; }

/* Countdown bar — sticks to the top of the sitting so the remaining time is always in
   view; turns red (.is-urgent) inside the last 5 minutes. Tabular figures keep the
   mm:ss from jittering as digits change. */
/* Fixed (not sticky): the portal's `html,body { overflow-x:hidden }` (styles.css) makes
   the body the scroll container, which kills `position:sticky` for descendants. Pinning to
   the viewport just under the 72px topbar keeps the clock in view while the chef scrolls
   the questions — top-right so it clears the left sidebar and the left-aligned prompts. */
.exam-timer { position:fixed; top:calc(var(--ds-topbar-h) + var(--ds-space-2)); right:var(--ds-space-4); z-index:20;
  display:flex; align-items:baseline; justify-content:center;
  gap:var(--ds-space-2); padding:var(--ds-space-2) var(--ds-space-3); border-radius:var(--ds-radius);
  background:var(--ds-surface); border:1px solid var(--ds-line); box-shadow:var(--ds-shadow-soft); }
.exam-timer__label { font-size:12px; font-weight:600; letter-spacing:.04em; color:var(--ds-muted); }
.exam-timer__clock { font-size:26px; font-weight:800; line-height:1; color:var(--ds-ink);
  font-variant-numeric:tabular-nums; font-feature-settings:"tnum"; }
.exam-timer.is-urgent { background:var(--ds-red-50); border-color:var(--ds-primary); }
.exam-timer.is-urgent .exam-timer__clock { color:var(--ds-primary); }
.exam-timer.is-urgent .exam-timer__label { color:var(--ds-primary); }
/* Reserve a right gutter on the questions card so nothing ever scrolls UNDER the fixed
   clock (the clock lives in this gutter). Tablet+desktop only — see the phone rule below. */
@media (min-width:601px) { .exam-sheet { margin-right:176px; } }
/* Phone: no room for a side gutter, so make the clock compact (drop the label) and tuck it
   into the corner; the narrow pill barely grazes the right edge of the full-width cards. */
@media (max-width:600px) {
  .exam-timer { right:var(--ds-space-2); padding:6px var(--ds-space-2); }
  .exam-timer__label { display:none; }
  .exam-timer__clock { font-size:20px; }
}

/* 解答解説 review — one card per question showing the chef's pick vs the correct key
   with a pass/fail accent stripe, then the explanation. Read-only recap after the
   result window opens. */
.review-summary { font-size:15px; font-weight:700; margin:0 0 var(--ds-space-3); color:var(--ds-ink); }
/* Release time shown beside "awaiting result" while a written verdict is withheld. */
.exam-result-at { color:var(--ds-muted); font-size:12px; white-space:nowrap; }
.filter-tabs { display:flex; gap:var(--ds-space-1); margin:0 0 var(--ds-space-3); }
.filter-tab { appearance:none; cursor:pointer; font-size:13px; font-weight:600; padding:6px 16px;
  border-radius:9999px; border:1px solid var(--ds-line); background:var(--ds-surface); color:var(--ds-muted); }
.filter-tab.is-active { border-color:var(--ds-primary); background:var(--ds-red-50); color:var(--ds-primary); }

/* 店舗詳細 dossier (admin store detail) — read-only sections staff review before
   approving/managing a venue: header + profile grid + facility photo wall + sessions. */
.dossier__head { display:flex; align-items:flex-start; justify-content:space-between; gap:var(--ds-space-2); }
.dossier__name { font-size:20px; font-weight:800; margin:0; color:var(--ds-ink); }
.dossier__sub { margin:2px 0 0; font-size:13px; color:var(--ds-muted); }
.dossier__actions { display:flex; align-items:center; flex-wrap:wrap; gap:var(--ds-space-2);
  margin-top:var(--ds-space-3); padding-top:var(--ds-space-3); border-top:1px solid var(--ds-line); }
.dossier__note { font-size:13px; color:var(--ds-muted); margin-right:auto; }
.dossier__section { font-size:15px; font-weight:700; color:var(--ds-ink); margin:0 0 var(--ds-space-3);
  padding-bottom:var(--ds-space-2); border-bottom:2px solid var(--ds-line); }
.dossier__grid { display:grid; grid-template-columns:1fr 1fr; gap:var(--ds-space-2) var(--ds-space-4); margin:0; }
.dossier__grid > div { display:flex; flex-direction:column; gap:2px; }
.dossier__grid-wide { grid-column:1 / -1; }
.dossier__grid dt { font-size:12px; font-weight:600; color:var(--ds-muted); }
.dossier__grid dd { font-size:14.5px; color:var(--ds-ink); margin:0; }
@media (max-width:600px) { .dossier__grid { grid-template-columns:1fr; } }

/* Facility photo wall — one tile per StoreImageKind slot; a missing slot is a labelled
   placeholder (not a gap), so the reviewer sees what was and wasn't uploaded. */
.facility-grid { display:grid; grid-template-columns:repeat(auto-fill, minmax(180px, 1fr)); gap:var(--ds-space-3); }
.facility-shot { margin:0; }
.facility-shot img { width:100%; height:140px; object-fit:cover; border-radius:var(--ds-radius);
  border:1px solid var(--ds-line); background:var(--ds-surface); display:block; }
.facility-shot__empty { width:100%; height:140px; display:flex; align-items:center; justify-content:center;
  border:1px dashed var(--ds-line); border-radius:var(--ds-radius); background:var(--ds-cream);
  color:var(--ds-muted); font-size:13px; }
.facility-shot figcaption { margin-top:6px; font-size:12.5px; font-weight:600; color:var(--ds-ink); text-align:center; }
.entity-card__actions { display:flex; gap:var(--ds-space-1); }
.review-q { border:1px solid var(--ds-line); border-left:4px solid var(--ds-line); border-radius:var(--ds-radius);
  padding:var(--ds-space-3); margin:0 0 var(--ds-space-2); background:var(--ds-surface); }
.review-q.is-correct { border-left-color:#2e7d32; }
.review-q.is-wrong { border-left-color:var(--ds-primary); }
.review-q__head { display:flex; gap:var(--ds-space-2); align-items:flex-start; margin:0 0 var(--ds-space-2); }
.review-q__verdict { flex:0 0 auto; font-size:12px; font-weight:700; padding:2px 10px; border-radius:9999px; }
.review-q.is-correct .review-q__verdict { color:#2e7d32; background:#e8f5e9; }
.review-q.is-wrong .review-q__verdict { color:var(--ds-primary); background:var(--ds-red-50); }
.review-opt { display:flex; align-items:center; gap:10px; min-height:40px; padding:8px 12px; margin:6px 0;
  border:1px solid var(--ds-line); border-radius:var(--ds-radius); font-size:14.5px; color:var(--ds-ink); }
.review-opt.is-answer { border-color:#2e7d32; background:#e8f5e9; font-weight:700; }
.review-opt.is-chosen-wrong { border-color:var(--ds-primary); background:var(--ds-red-50); }
.review-opt__tag { margin-left:auto; font-size:12px; font-weight:700; }
.review-q__explain { margin-top:var(--ds-space-2); padding-top:var(--ds-space-2); border-top:1px dashed var(--ds-line);
  font-size:14px; line-height:1.7; color:var(--ds-ink); }
.review-q__explain-label { font-size:12px; font-weight:700; color:var(--ds-muted); display:block; margin-bottom:4px; }

/* Per-part status chips on the hub — one per exam component (実技 / 筆記), so a chef
   sees each part's state at a glance instead of one opaque application status. */
.exam-parts { display:flex; flex-wrap:wrap; gap:var(--ds-space-1); margin-top:var(--ds-space-1); }
.exam-part { display:inline-flex; align-items:center; height:var(--ds-chip-h); box-sizing:border-box;
  font-size:12px; font-weight:600; line-height:16px; padding:0 var(--ds-space-1); border-radius:9999px;
  color:var(--ds-muted); background:var(--ds-surface); border:1px solid var(--ds-line); }

/* Exam roster table — quiet 和 stationery: no filled header band. The header is
   small tracked-out faded-ink text over a 2px 墨 (sumi) rule — the classic 罫線
   ledger line — with hairline rules between rows and a washi wash on hover.
   Grade names (first column) carry the Mincho display face. Token-driven (no
   palette hardcode); the wrapper scrolls horizontally so the columns never
   squash on a narrow screen. */
.exam-table-wrap {
  overflow-x:auto;
  border:1px solid var(--ds-line);
  border-radius:var(--ds-radius-xl2);
  background:var(--ds-surface);
  box-shadow:var(--ds-shadow-soft);
}
.exam-table { width:100%; min-width:640px; border-collapse:collapse; font-size:14px; }
/* Cells never wrap — a narrow viewport scrolls the table sideways inside the
   wrapper instead of folding 下書き/dates into ugly one-character lines. */
.exam-table th,
.exam-table td { height:var(--ds-row-h); box-sizing:border-box; padding:var(--ds-space-2); text-align:left; vertical-align:middle; white-space:nowrap; line-height:24px; }
/* first column stays put while the wrapper scrolls sideways — the row's
   identity never leaves the screen. Opaque background so rows slide under it. */
.exam-table th:first-child,
.exam-table td:first-child { position:sticky; left:0; z-index:1; background:var(--ds-surface); }
.exam-table tbody tr:hover td:first-child { background:var(--ds-cream); }
.exam-table thead th {
  background:transparent; color:var(--ds-muted);
  font-weight:600; font-size:12px; letter-spacing:.14em; white-space:nowrap;
  border-bottom:2px solid var(--ds-ink);
  padding-top:var(--ds-space-2); padding-bottom:var(--ds-space-1);
}
.exam-table tbody td { border-top:1px solid var(--ds-line); color:var(--ds-ink); }
.exam-table tbody tr:first-child td { border-top:0; }
.exam-table tbody tr:hover { background:var(--ds-cream); }
.exam-table tbody td:first-child {
  font-family:var(--ds-font-display); font-weight:600; font-size:15px;
  letter-spacing:.04em;
}

@media (max-width:600px) {
  .exam-slot__grid {
    grid-template-columns:1fr auto;
    grid-template-areas:
      "head  head"
      "input button"
      "error error";
    row-gap:var(--ds-space-1);
  }
}

/* =============================================================================
   Portal re-skin of the legacy primitives (styles.css) — scoped to .portal so
   the marketing pages keep their look. The admin/inner pages still write
   `.ta1 .ta3` tables and `.btn3` buttons; inside the portal those inherit the
   washi-and-sumi stationery automatically, with no per-view changes.
   ========================================================================== */
/* legacy data/definition tables → hairline 罫線 ledger */
.portal .ta1 { margin:var(--ds-space-2) 0 var(--ds-space-1); border-collapse:collapse; }
/* data tables (ta1 ta3): let columns size to content — the legacy 30% on every th
   was meant for two-column definition tables and skews a 10-column roster. */
.portal .ta1.ta3 th { width:auto; }
.portal .ta1 th,
.portal .ta1 td {
  height:var(--ds-row-h); box-sizing:border-box;
  /* row rules are drawn on TOP edges: the last row then has no trailing line by
     construction — even when a rowspan cell (the roster's merged examinee cell)
     reaches it, which a tr:last-child border-bottom reset could never catch. */
  padding:var(--ds-space-2); border-top:1px solid var(--ds-line); font-size:13.5px; line-height:24px;
  /* data tables read best with vertically-centered cells (Tailwind UI / Primer
     convention) — header label and first data row then share the same rhythm and
     nothing hugs the header rule. */
  text-align:left; vertical-align:middle;
  /* undo legacy break-all — CJK wraps naturally, long tokens (emails/URLs) only
     break when there is truly no room, never one character per line. */
  word-break:normal; overflow-wrap:anywhere;
}
/* a merged group label (the roster's examinee cell) reads from the top so it lines
   up with the first row of its group, not floating in the middle of the span. */
.portal .ta1 td[rowspan] { vertical-align:top; }
.portal .ta1 td small { color:var(--ds-muted); font-size:12px; line-height:16px; }
.portal .ta1 th {
  background:transparent; color:var(--ds-muted);
  font-weight:600; font-size:12px; letter-spacing:.1em;
}
/* COLUMN-HEADER rows only — detected structurally: a first row whose th sits
   next to another th is a header row (data table); a th followed by a td is a
   ROW LABEL (definition table: profile edit, document upload…) and keeps the
   ordinary hairline — the 2px sumi rule + sticky must never land there. */
.portal .ta1 tr:first-child:has(th + th) th {
  /* sumi rule under the header — drawn as an inset box-shadow, NOT border-bottom:
     border-bottom on a header cell in a border-collapse table renders
     inconsistently, while an inset shadow paints every time → same 2px rule on
     every data list. The header is intentionally NOT position:sticky: pinning it
     fought the card's horizontal scroller (the card became the sticky context once
     it had overflow), which dropped the header border and let the first data row
     ride up over the header rule when the page scrolled. A plain header avoids the
     whole class of bugs; these admin lists are short enough not to need pinning. */
  box-shadow:inset 0 -2px 0 var(--ds-ink);
  background:var(--ds-surface);
}

/* ── table alignment utilities (UX law): text left (default), numbers RIGHT
   with tabular figures, short codes / badges / actions CENTERED. Apply the
   same class to the header cell so it aligns with its column. ─────────────── */
.portal .ta1 .cell-num,
.exam-table .cell-num { text-align:right; font-variant-numeric:tabular-nums; }
.portal .ta1 .cell-center,
.exam-table .cell-center { text-align:center; }
.portal .ta1 tr:first-child th,
.portal .ta1 tr:first-child td { border-top:0; }

/* ── Sticky action column (admin rosters) ─────────────────────────────────────
   When an admin table is wider than its card it scrolls sideways; the action
   column (`.cell-action`, always the last column) stays pinned to the right edge
   so its button is never scrolled out of reach — the data columns slide under it.
   A soft left shadow marks the pinned edge. These roster wrappers are desktop-only
   (≤600px they become stacked cards), so this is purely a wide-screen affordance. */
/* `.table-scroll` is the generic member of this family: wrap a table in it and the
   TABLE scrolls sideways, not the whole card. Without a wrapper the card itself
   becomes the scroller (see the `:has(table)` rule below), which drags the toolbar —
   the 追加 button, the pager — out of view along with the columns. */
.pay-table, .score-history-table, .staff-table, .entity-list, .table-scroll { overflow-x:auto; }
.portal .ta1 th.cell-action,
.portal .ta1 td.cell-action,
.exam-table th.cell-action,
.exam-table td.cell-action,
.entity-list__table th.cell-action,
.entity-list__table td.cell-action {
  position:sticky; right:0; z-index:1;
  background:var(--ds-surface);
  box-shadow:-8px 0 8px -8px rgba(17, 17, 17, .14);
}
/* Keep the pinned cell opaque through the row-hover wash so scrolled cells don't
   bleed through the gap. Only for the tables that HAVE such a wash: `.portal .ta1`
   rows do not tint on hover, so tinting their pinned cell drew a stray block behind
   the action button instead of matching anything. The button's own hover (red
   outline) is the affordance there. */
.exam-table tbody tr:hover td.cell-action { background:var(--ds-cream); }
/* A settled note under an action cell's chip (e.g. 確認者/確認日時 on a payment
   staff already signed off) — secondary to the chip above it. */
.cell-note { display:block; margin-top:var(--ds-space-1); color:var(--ds-muted); line-height:16px; }

/* the row right under a real header keeps no top hairline — the header's 2px
   sumi rule IS the separator (no double line). */
.portal .ta1 tr:first-child:has(th + th) + tr th,
.portal .ta1 tr:first-child:has(th + th) + tr td { border-top:0; }

/* legacy action button → refined seal-red pill (color:#fff!important upstream
   forces a filled treatment; we soften everything else). The fixed 180px width
   and heavy block shape go; pagination + row actions share the same quiet pill. */
.portal a.btn3,
.portal .btn3,
.portal input[type="submit"].btn3 {
  width:auto; display:inline-flex; align-items:center; justify-content:center;
  box-sizing:border-box; height:var(--ds-control-h-md); padding:0 var(--ds-space-3); border:0; border-radius:9999px;
  background:var(--ds-primary);
  /* `font-family` because a bare <button> takes the UA font, not the page's — the
     portal uses <button class="btn3"> for every form action, so without this they
     render in a different typeface from the <a class="btn3"> next to them. */
  font-family:inherit;
  font-size:13px; font-weight:700; letter-spacing:.08em; line-height:16px;
  white-space:nowrap; /* a pill never folds its label vertically in a tight cell */
  cursor:pointer; text-decoration:none;
  transition:background .15s, transform .1s;
}
.portal a.btn3:hover,
.portal .btn3:hover,
.portal input[type="submit"].btn3:hover { background:var(--ds-primary-dark); }
.portal a.btn3:active,
.portal .btn3:active { transform:scale(.98); }
/* inside a table row the pill is a secondary action — keep it compact so a
   column of them doesn't read as a wall of red. */
.portal .ta1 .btn3 { height:var(--ds-control-h-sm); padding:0 var(--ds-space-2); font-size:12px; }

/* ghost (outline) pill — a quiet, bordered link button matching the phone card's
   "view" action. Applied to the explicit .btn-ghost class AND to every action
   link inside an admin data table (編集 / 課題セット / 予約一覧を見る …), so all
   in-table action links look identical on every screen size. (All <a> in these
   .ta1 cells are actions — there are no content links — so this is safe.)

   Shape is shared; SIZE splits by context (see the two rules below): a ghost in a
   card footer stands beside a .btn3 and must be the same height as it, while a
   ghost in a table row stands beside the compact in-table .btn3. */
.portal .card a.btn-ghost,
.portal .card button.btn-ghost,
.portal .card input[type="submit"].btn-ghost,
.confirm-dialog button.btn-ghost,
.portal .ta1 td a:not(.btn3),
.portal .ta1 td button.btn-ghost,
.portal .ta1 td input[type="submit"].btn-ghost,
.entity-list__table td a:not(.btn3),
.entity-list__table td button.btn-ghost {
  display:inline-flex; align-items:center; justify-content:center; box-sizing:border-box;
  border:1px solid var(--ds-line); border-radius:9999px; background:var(--ds-surface);
  color:var(--ds-ink); font-weight:700; letter-spacing:.04em;
  cursor:pointer; /* a <button>/<input> ghost is clickable like the anchor version */
  text-decoration:none; white-space:nowrap; transition:background .15s, border-color .15s;
}
/* card / page level — matches .btn3 exactly (same height, padding, font size), so
   a キャンセル + 送信 footer pair reads as one control row, not two sizes. */
.portal .card a.btn-ghost,
.portal .card button.btn-ghost,
.portal .card input[type="submit"].btn-ghost,
.confirm-dialog button.btn-ghost {
  height:var(--ds-control-h-md); padding:0 var(--ds-space-3); font-size:13px;
}
/* in-table — the dense-table exemption, matching `.portal .ta1 .btn3` above.
   Listed after the card rule AND more specific (extra `td`/`.ta1`), so an action
   inside a table that itself sits inside a card still gets the compact size. */
.portal .ta1 td a:not(.btn3),
.portal .ta1 td button.btn-ghost,
.portal .ta1 td input[type="submit"].btn-ghost,
.entity-list__table td a:not(.btn3),
.entity-list__table td button.btn-ghost {
  height:var(--ds-control-h-sm); padding:0 var(--ds-space-2); font-size:12px;
}
.confirm-dialog button.btn-ghost:hover,
.portal .card a.btn-ghost:hover,
.portal .card button.btn-ghost:hover,
.portal .card input[type="submit"].btn-ghost:hover,
.portal .ta1 td a:not(.btn3):hover,
.portal .ta1 td button.btn-ghost:hover,
.portal .ta1 td input[type="submit"].btn-ghost:hover,
.entity-list__table td a:not(.btn3):hover,
.entity-list__table td button.btn-ghost:hover {
  /* Hover lifts the action into the brand red — border and label together — and
     leaves the surface alone. Tinting the background made a ghost sitting on a
     shaded row (the 合計 row, a stack-card) look like a different control from the
     same button on a white row; the outline reads the same on any surface. */
  border-color:var(--ds-primary); color:var(--ds-primary);
}

/* ── Session booking toggle (7.B.3) — a clear pill action (icon + label) for a
   venue to close/reopen a session's bookings. `--close` is a caution tone (changes
   availability), `--open` is the primary "make bookable again" action. Full-width
   on phones. Replaces a bare, unstyled <button class="btn-ghost">. */
.btn-toggle {
  display:inline-flex; align-items:center; justify-content:center; gap:8px;
  height:var(--ds-control-h-md); padding:0 var(--ds-space-3);   /* same size as .btn3 / .btn-ghost */
  border-radius:9999px; border:1px solid var(--ds-line); background:var(--ds-surface);
  color:var(--ds-ink); font-family:inherit; font-size:13px; font-weight:700; letter-spacing:.02em;
  cursor:pointer; transition:background .15s, border-color .15s, color .15s;
}
.btn-toggle svg { width:16px; height:16px; flex:0 0 auto; }
.btn-toggle--close { color:var(--ds-gold-700); border-color:var(--ds-gold-100); background:var(--ds-gold-50); }
.btn-toggle--close:hover { background:var(--ds-gold-100); border-color:var(--ds-gold-500); }
.btn-toggle--open { color:#fff; background:var(--ds-primary); border-color:var(--ds-primary); }
.btn-toggle--open:hover { background:var(--ds-primary-dark); border-color:var(--ds-primary-dark); }
.session-action { margin:var(--ds-space-1) 0 var(--ds-space-2); }
@media (max-width:600px) { .btn-toggle { width:100%; justify-content:center; } }

/* ── portal forms — the legacy CRUD forms write bare <p><label><input>; inside
   the portal those pick up the stationery: block labels in faded ink, washi
   field chrome matching .exam-input, a hairline fieldset. Checkboxes/radios
   keep browser rendering (only sized fields are skinned). ─────────────────── */
.portal .card form p > label { display:block; margin-bottom:var(--ds-space-1); font-size:13px; line-height:16px; font-weight:600; color:var(--ds-muted); }
.portal .card input[type="text"],
.portal .card input[type="number"],
.portal .card input[type="email"],
.portal .card input[type="url"],
.portal .card input[type="password"],
.portal .card input[type="date"],
.portal .card input[type="tel"],
.portal .card select,
.portal .card textarea {
  box-sizing:border-box; width:100%; max-width:520px; height:var(--ds-control-h);
  font:inherit; font-size:14px; line-height:24px; color:var(--ds-ink); background:var(--ds-surface);
  padding:0 var(--ds-space-2); border:1px solid var(--ds-line); border-radius:var(--ds-radius);
  transition:border-color .15s, box-shadow .15s;
}
/* native date control: align its inner padding/affordance with the other fields */
.portal .card input[type="date"] { -webkit-appearance:none; appearance:none; }
.portal .card input[type="date"]::-webkit-calendar-picker-indicator { cursor:pointer; opacity:.6; }
.portal .card input[type="date"]::-webkit-calendar-picker-indicator:hover { opacity:1; }

/* file inputs — same washi field chrome as the text fields, with the native
   "choose file" control reskinned as a quiet pill (the filename rides after it). */
.portal .card input[type="file"] {
  box-sizing:border-box; width:100%; max-width:520px;
  font:inherit; font-size:13px; line-height:24px; color:var(--ds-muted);
  background:var(--ds-surface); border:1px solid var(--ds-line); border-radius:var(--ds-radius);
  padding:11px var(--ds-space-2); cursor:pointer; transition:border-color .15s, box-shadow .15s;
}
.portal .card input[type="file"]:hover { border-color:var(--ds-red-100); }
.portal .card input[type="file"]:focus { outline:none; border-color:var(--ds-primary); box-shadow:0 0 0 3px var(--ds-red-50); }
.portal .card input[type="file"]::file-selector-button,
.portal .card input[type="file"]::-webkit-file-upload-button {
  margin:0 var(--ds-space-2) 0 0; padding:0 var(--ds-space-2); height:26px;
  border:0; border-radius:9999px; background:var(--ds-cream); color:var(--ds-ink);
  font:inherit; font-weight:700; font-size:12px; letter-spacing:.04em; cursor:pointer; transition:background .15s;
}
.portal .card input[type="file"]::file-selector-button:hover,
.portal .card input[type="file"]::-webkit-file-upload-button:hover { background:var(--ds-line); }
.portal .card input:hover,
.portal .card select:hover,
.portal .card textarea:hover { border-color:var(--ds-red-100); }
.portal .card input:disabled,
.portal .card select:disabled,
.portal .card textarea:disabled { background:var(--ds-cream); color:var(--ds-muted); cursor:not-allowed; }
/* on-blur inline validation — :user-invalid fires only after the user has
   interacted with the field, never on first paint (the UX law's "on blur"). */
.portal .card input:user-invalid,
.portal .card select:user-invalid,
.portal .card textarea:user-invalid { border-color:var(--ds-primary); }
.portal .card input[type="number"] { width:auto; min-width:144px; }
.portal .card textarea { height:auto; min-height:96px; padding:var(--ds-space-1) var(--ds-space-2); resize:vertical; }
.portal .card input:focus,
.portal .card select:focus,
.portal .card textarea:focus { outline:none; border-color:var(--ds-primary); box-shadow:0 0 0 3px var(--ds-red-50); }
.portal .card fieldset {
  border:1px solid var(--ds-line); border-radius:var(--ds-radius);
  padding:var(--ds-space-2); margin:var(--ds-space-3) 0;
}
.portal .card legend { padding:0 var(--ds-space-1); font-size:13px; line-height:16px; font-weight:700; letter-spacing:.08em; color:var(--ds-muted); }

/* server-side validation message — always a block directly below its field */
.portal .card .form_error {
  display:block; margin-top:var(--ds-space-1);
  font-size:12px; line-height:16px; font-weight:600; color:var(--ds-primary-dark);
}
.portal .card .form_error::before { content:"⚠ "; }

/* ── Field list (x-field) — a real form element that REPLACES label/value form
   tables (profile edit, etc.). Mobile-first: each field is a stacked block (label
   on its own line, full-width control) so a phone form reads natively — no table
   reflow hacks. From 601px the label moves to a fixed left column with a hairline
   between rows, matching the old definition-table rhythm on desktop. ──────────── */
/* Scoped to `.field-list` so this x-field (label-left column on desktop) styling
   does NOT leak onto the admin `.field-grid` forms, which reuse the same
   .field/.field__label/.field__control class names for a stacked 2-column grid. */
.portal .card .field-list { display:flex; flex-direction:column; gap:var(--ds-space-2); margin:var(--ds-space-2) 0; }
.portal .card .field-list .field { display:flex; flex-direction:column; gap:var(--ds-space-1); }
.portal .card .field-list .field__label { font-size:13px; line-height:16px; font-weight:600; color:var(--ds-muted); }
.portal .card .field-list .field__control { display:flex; flex-direction:column; gap:var(--ds-space-1); min-width:0; }
.portal .card .field-list .field__control input,
.portal .card .field-list .field__control select { width:100%; }       /* full-width on phone */
@media (min-width:601px) {
  .portal .card .field-list .field { flex-direction:row; align-items:flex-start; gap:var(--ds-space-3); padding:var(--ds-space-1) 0; border-top:1px solid var(--ds-line); }
  .portal .card .field-list .field:first-child { border-top:0; }
  .portal .card .field-list .field__label { flex:0 0 200px; padding-top:14px; }   /* label column, baseline-aligned to the 48px control */
  .portal .card .field-list .field__control { flex:1; }
}

/* ── status chip — the coloured verdict badge (結果 columns). Tones map from
   ExamApplicationStatus::tone(): success=合格(matcha), danger=不合格(vermilion),
   neutral=取消(faded ink), pending=in-flight(gold). Token-driven only. ──────── */
.status-chip {
  display:inline-flex; align-items:center;
  height:var(--ds-chip-h); box-sizing:border-box;
  padding:0 var(--ds-space-2); border-radius:9999px; border:1px solid transparent;
  font-size:12px; font-weight:700; line-height:16px; white-space:nowrap;
}
.status-chip--success { color:var(--ds-green-700); background:var(--ds-green-50); border-color:var(--ds-green-100); }
.status-chip--danger  { color:var(--ds-red-700);   background:var(--ds-red-50);   border-color:var(--ds-red-100); }
.status-chip--neutral { color:var(--ds-muted);     background:var(--ds-cream);    border-color:var(--ds-line); }
.status-chip--pending { color:var(--ds-gold-700);  background:var(--ds-gold-50);  border-color:var(--ds-gold-100); }

/* search/filter bar (e.g. 会員管理・受験者一覧) — a labelled fixed-width field
   (320px = 8×40) + actions aligned to the field's baseline. The label is a
   permanent element ABOVE the input (placeholder is hint-only, never a label). */
.portal-search { display:flex; flex-wrap:wrap; align-items:flex-end; gap:var(--ds-space-1); margin-bottom:var(--ds-space-2); }
.portal-search__field { display:flex; flex-direction:column; gap:var(--ds-space-1); flex:0 1 320px; }
.portal-search__label { font-size:12px; line-height:16px; font-weight:600; color:var(--ds-muted); text-align:left; }
.portal-search input[type="text"] { width:100%; max-width:320px; }
/* clear (✕) affordance inside the search field — a plain link back to the
   unfiltered list, rendered only when a query is active (works without JS). The
   input reserves room on the right so text never slides under the ✕. */
.portal-search__control { position:relative; display:block; max-width:320px; }
.portal-search__control input[type="text"] { max-width:none; padding-right:34px; }
.portal-search__clear {
  position:absolute; top:0; right:0; bottom:0; width:34px;
  display:inline-flex; align-items:center; justify-content:center;
  color:var(--ds-muted); text-decoration:none; font-size:13px; line-height:1;
}
.portal-search__clear:hover { color:var(--ds-primary); }
/* hide the ✕ only while the field is empty (placeholder showing) — it appears the
   moment the user types, no JS. */
.portal-search__control input:placeholder-shown ~ .portal-search__clear { display:none; }
/* combobox (list=…) fields render a native ▼ dropdown arrow. Swap it with the ✕:
   keep ▼ while empty (the suggestion list stays reachable), drop it once there is
   text so the arrow and the ✕ never stack in the same corner. */
.portal-search__control input:not(:placeholder-shown)::-webkit-calendar-picker-indicator { display:none; }

/* fields that own their layout opt back out of the generic 520px cap */
.portal .card .exam-input { width:100%; max-width:none; }

/* action buttons that must POST ride inside an inline form */
.exam-action-form { display:inline-flex; margin:0; }
.exam-booked { margin:0; font-size:.9em; color:var(--ds-ink-soft, #555); line-height:1.5; }
.exam-booked strong { color:var(--ds-ink, #222); }
.exam-booked__when { display:block; font-size:.85em; color:var(--ds-ink-soft, #666); }
/* Venue-session picker rows: the site name + location beside the radio, so a chef
   chooses a venue by where it is, not just when it runs. */
.sess-venue { display:inline-flex; align-items:flex-start; gap:var(--ds-space-2, 8px); cursor:pointer; text-align:left; }
.sess-venue__body { display:inline-flex; flex-direction:column; }
.sess-venue__name { font-weight:600; }
.sess-venue__loc { font-size:.85em; color:var(--ds-ink-soft, #666); white-space:normal; }
.sess-venue__link { display:inline-block; margin-top:2px; font-size:.85em; white-space:normal; }
/* Venue detail — facility photo grid (chef's read-only view when picking a place). */
.venue-photos { display:grid; grid-template-columns:repeat(auto-fill, minmax(180px, 1fr)); gap:var(--ds-space-3, 16px); margin-top:var(--ds-space-2, 8px); }
.venue-photo { margin:0; }
.venue-photo__img { width:100%; aspect-ratio:4/3; object-fit:cover; border-radius:var(--ds-radius, 8px); background:var(--ds-surface-2, #f2f2f2); }
.venue-photo__cap { margin-top:4px; font-size:.85em; color:var(--ds-ink-soft, #666); }

/* ── date/time display (x-datetime) — the date is one unbreakable block, the
   time its own; a tight cell may wrap ONLY at the gap between them, so a date
   never folds mid-string. ──────────────────────────────────────────────────── */
.dt { display:inline; }
.dt__date,
.dt__time { display:inline-block; white-space:nowrap; }

/* shared admin list toolbar (x-list-toolbar): action on the left, page-size/total
   cluster on the right, one row. The per-page inside drops its own bottom margin. */
.table-toolbar { display:flex; flex-wrap:wrap; align-items:flex-end; justify-content:space-between; gap:var(--ds-space-2); margin-bottom:var(--ds-space-2); }
/* the per-page selector and a search field inside the toolbar drop their own
   bottom margin — the toolbar owns the spacing below the row. */
.table-toolbar .per-page,
.table-toolbar .portal-search { margin:0; }
/* an empty action slot still reserves the left side so the per-page cluster stays
   right-aligned (read-only lists with no button). */
.table-toolbar__action { display:flex; align-items:center; gap:var(--ds-space-1); }
/* right cluster: the pager (when passed) + the page-size selector, one row. */
.table-toolbar__meta { display:flex; flex-wrap:wrap; align-items:center; gap:var(--ds-space-2); }

/* ── shared list pager (x-list-pagination / partials.pagination-numbers) ──────
   Numbered page links; the current page is the one small solid-red element on the
   row (seal-scale, not a fill). Compact controls follow the dense-table exemption
   height (--ds-control-h-sm). Bottom copy centres under the table; the copy inside
   the toolbar inherits the row's alignment and drops the top margin. */
.list-pager { display:flex; justify-content:center; margin-top:var(--ds-space-3); }
.table-toolbar .list-pager { margin-top:0; justify-content:flex-start; }
.pager { display:flex; flex-wrap:wrap; align-items:center; gap:var(--ds-space-1); list-style:none; margin:0; padding:0; }
.pager__link {
  display:inline-flex; align-items:center; justify-content:center;
  min-width:var(--ds-control-h-sm); height:var(--ds-control-h-sm); padding:0 var(--ds-space-1);
  box-sizing:border-box; border:1px solid var(--ds-line); border-radius:var(--ds-radius);
  background:var(--ds-surface); color:var(--ds-ink);
  font-size:13px; font-weight:600; line-height:16px; text-decoration:none;
  transition:background .15s, border-color .15s;
}
a.pager__link:hover { background:var(--ds-cream); border-color:var(--ds-red-100); color:var(--ds-ink); text-decoration:none; }
.pager__link.is-current { background:var(--ds-primary); border-color:var(--ds-primary); color:#fff; }
.pager__link.is-disabled { color:var(--ds-muted); background:none; border-color:var(--ds-line); cursor:not-allowed; }
.pager__gap { display:inline-flex; align-items:center; justify-content:center; min-width:var(--ds-control-h-sm); height:var(--ds-control-h-sm); color:var(--ds-muted); }

/* Data-list tables scroll horizontally instead of wrapping cell text when the
   content area narrows (e.g. the portal sidebar expands). ONE rule for every list:
   the legacy `.ta1 ta3` class is shared with label/value FORM tables, so we key on
   structure instead — `:has(th + th)` matches only tables with a multi-column header
   row (two adjacent <th>), i.e. real data lists, never a th+td form row. Fix here,
   every list inherits; forms and login tables are untouched.

   The scroller lives on the CARD, not the table: `display:block` on a <table> makes
   the whitespace between <table> and its first row render as a blank line above the
   header (and stops the table filling its width). Keeping the table a normal table
   and letting its card overflow avoids both — cells just stop wrapping, so a list
   too wide for the narrowed card scrolls sideways instead of breaking lines. */
.portal .card:has(table.ta1.ta3 th + th) { overflow-x:auto; }
.portal table.ta1.ta3:has(th + th) th,
.portal table.ta1.ta3:has(th + th) td { white-space:nowrap; word-break:normal; }

/* page-size selector riding above each admin list table */
.per-page { display:flex; justify-content:flex-end; align-items:center; gap:var(--ds-space-1); margin:0 0 var(--ds-space-2); }
.per-page label { display:inline-flex; align-items:center; gap:var(--ds-space-1); font-size:13px; line-height:16px; color:var(--ds-muted); }
/* total row count shown just left of the selector (opt-in via $total) */
.per-page__total { font-size:13px; line-height:16px; color:var(--ds-muted); }
/* toolbar exemption: dense secondary control, not a form field */
.portal .card .per-page select { width:auto; min-width:80px; height:var(--ds-control-h-sm); padding:0 var(--ds-space-1); }

/* =============================================================================
   Phase R — mobile refinements (tickets R.2–R.4). The base styles above are
   already mobile-first; these are the small-screen enhancements + the scroll
   affordance for wide data lists. See docs/responsive-conventions.md and
   docs/responsive-manual-tests.md.
   ========================================================================== */

/* R.2 — scroll affordance: a soft edge shadow on the chef roster wrapper hints
   that a wide table scrolls sideways, and fades out once scrolled to that edge
   (Lea Verou's local-attachment background-shadow technique). The first two
   layers repaint the surface so the shadows ride over it, not a transparent gap. */
.exam-table-wrap {
  background:
    linear-gradient(to right, var(--ds-surface) 30%, rgba(255,254,250,0)) 0 0,
    linear-gradient(to right, rgba(255,254,250,0), var(--ds-surface) 70%) 100% 0,
    radial-gradient(farthest-side at 0 50%, rgba(33,29,23,.12), rgba(33,29,23,0)) 0 0,
    radial-gradient(farthest-side at 100% 50%, rgba(33,29,23,.12), rgba(33,29,23,0)) 100% 0;
  background-repeat:no-repeat;
  background-size:40px 100%, 40px 100%, 14px 100%, 14px 100%;
  background-attachment:local, local, scroll, scroll;
}

/* R.4 — public/marketing media never overflows its column on a phone (legacy
   styles.css is desktop-first). A safe, standard responsive default app-wide. */
img, iframe, video { max-width:100%; height:auto; }

/* Home hero sections keep the fixed-background (parallax) effect on EVERY screen
   size and device — the image stays put while a section's content scrolls. The
   legacy stylesheet disables it on iOS/iPadOS/touch (main.js adds body.noBgFixed →
   styles.css `.noBgFixed .fixedBg { background-attachment:scroll }`) because Safari
   renders fixed backgrounds unreliably. This re-enables the design intent; it
   outranks the disabler (higher specificity + !important, loaded last).
   NOTE: real iOS Safari may still not pin a fixed background inside the scrolling
   `main.home` container — that Safari limitation is why it was opted out. Works in
   every desktop browser at all widths (incl. DevTools device emulation) + Android. */
main.home .fixedBg { background-attachment:fixed !important; background-position:center center; }

@media (max-width:600px) {
  /* R.2 — admin list toolbar stacks: the action and the page-size/total cluster
     each take a full row, and the search field grows full-width instead of the
     fixed 320px box (which would overflow a 375px screen). */
  .table-toolbar { flex-direction:column; align-items:stretch; }
  .table-toolbar__action,
  .table-toolbar .per-page { justify-content:flex-start; }
  .portal-search { width:100%; }
  .portal-search__field { flex:1 1 100%; }
  .portal-search__control,
  .portal-search input[type="text"],
  .portal-search__control input[type="text"] { max-width:none; }

  /* R.3 — definition / form tables (label <th> + value <td> rows: profile edit,
     document upload, written-exam questions) stack vertically so the value column
     never gets crushed on a phone. Detected structurally as the INVERSE of a data
     list: a table with NO multi-<th> header row. Real data lists (th+th header)
     are untouched here — they scroll sideways via the rule above. */
  .portal table.ta1:not(:has(th + th)) tr { display:block; border-top:1px solid var(--ds-line); padding:var(--ds-space-1) 0; }
  .portal table.ta1:not(:has(th + th)) tr:first-child { border-top:0; }
  .portal table.ta1:not(:has(th + th)) th,
  .portal table.ta1:not(:has(th + th)) td { display:block; width:100%; height:auto; border-top:0; padding:var(--ds-space-1) 0; }
  .portal table.ta1:not(:has(th + th)) th { color:var(--ds-muted); font-size:12px; }
  .portal table.ta1:not(:has(th + th)) td input,
  .portal table.ta1:not(:has(th + th)) td select,
  .portal table.ta1:not(:has(th + th)) td textarea { max-width:none; }

  /* R.3 — the form action button is the primary tap target on a phone: full-width
     so it's easy to hit. Scoped to a standalone submit (the legacy CRUD forms wrap
     it in <p class="c"> / <p class="btn_b">); in-table row pills and pagination
     keep their inline pill shape.
     The cancel/back ghost sitting in the same <p> stretches too — a full-width
     primary next to an auto-width secondary reads as two unrelated controls
     (same treatment `.card__foot--split` already gives its pair below). */
  .portal .card form > p.c > input[type="submit"].btn3,
  .portal .card form > p.c > button.btn3,
  .portal .card form > p.c > a.btn-ghost,
  .portal .card form > p.c > button.btn-ghost,
  .portal .card form > p.c > input[type="submit"].btn-ghost { width:100%; }
  /* …and once those buttons are full-width blocks they stack touching (the inline
     word-space between inline-blocks vanishes at width:100%): lay the action row
     out as a column with a real gap so a stacked キャンセル/保存 pair never kisses.
     Applies to every <p class="c"> action row holding pill buttons — forms and
     standalone back-link rows alike. */
  .portal .card p.c:has(> .btn3, > .btn-ghost) {
    display:flex; flex-direction:column; gap:var(--ds-space-1);
  }

  /* R.3 — opt-in mobile CARD layout for data tables (chef /mypage exam table,
     exam history). A `<table class="… stack-cards">` whose <td>s carry data-label
     turns each row into a labelled card on phones — no horizontal scroll, every
     value readable. Admin lists deliberately DON'T use this (they keep the
     scroll). !important on a few props beats the legacy `.ta1`/`.exam-table`
     cell rules without a specificity arms race. */
  .portal table.stack-cards { display:block; width:100%; min-width:0; }
  .portal table.stack-cards thead { display:none; }            /* explicit header (dashboard) */
  .portal table.stack-cards tr:has(th) { display:none; }       /* implicit header row (history) */
  .portal table.stack-cards tbody { display:block; }
  .portal table.stack-cards tr {
    display:block; margin:0 0 var(--ds-space-1); padding:var(--ds-space-1) var(--ds-space-2);
    border:1px solid var(--ds-line); border-radius:var(--ds-radius); background:var(--ds-surface); }
  .portal table.stack-cards td {
    display:flex !important; gap:var(--ds-space-2); align-items:baseline;
    height:auto !important; padding:6px 0 !important; border:0 !important; white-space:normal !important; }
  .portal table.stack-cards td::before {
    content:attr(data-label); flex:0 0 40%; color:var(--ds-muted);
    font-size:12px; font-weight:600; line-height:24px; }
  /* unlabelled cells (e.g. a colspan "no rows yet" message) carry no data-label,
     so don't reserve the 40% label column — render full-width and centred. */
  .portal table.stack-cards td:not([data-label]) { display:block !important; text-align:center; color:var(--ds-muted); }
  .portal table.stack-cards td:not([data-label])::before { content:none; }
  .portal table.stack-cards td:first-child { position:static !important; font-family:inherit; }
  /* an Action cell holds buttons (pay / withdraw / retake) — don't cram them into
     the 60% value column: stack the label + full-width buttons so nothing clips. */
  .portal table.stack-cards td:has(.btn3),
  .portal table.stack-cards td:has(a[href]) { flex-direction:column; align-items:stretch; gap:var(--ds-space-1); }
  .portal table.stack-cards td:has(.btn3)::before,
  .portal table.stack-cards td:has(a[href])::before { display:none; }   /* hide the redundant action label */
  .portal table.stack-cards td:has(.btn3) .exam-action-form { display:block; width:100%; margin:0; }
  /* Full size, not the in-table 32px: once a row has become a card, the dense-table
     exemption no longer applies — this is the card's primary tap target and must
     match every other action button in the app. */
  .portal table.stack-cards td .btn3,
  .portal table.stack-cards td a[href] {
    width:100%; box-sizing:border-box;
    height:var(--ds-control-h-md); padding:0 var(--ds-space-3); font-size:13px;
  }
  /* the chef roster wrapper drops its own border/shadow on phone so the inner
     cards stand alone (no double frame). */
  .portal .exam-table-wrap:has(table.stack-cards) {
    border:0; background:none; box-shadow:none; overflow:visible; }

  /* R.3 — visibility matrix (field × audience checkboxes) → cards on phone. The
     header row hides; each field row becomes a card whose <th> is the heading and
     each audience <td> is a [checkbox] + audience-name row (name from data-label).
     It has a th+th header so R.2 treats it as a data list — !important overrides
     that here without a specificity fight. */
  .portal table.vis-matrix { display:block; width:100%; min-width:0; }
  .portal table.vis-matrix tbody { display:block; }
  .portal table.vis-matrix tr:first-child { display:none; }            /* header row */
  .portal table.vis-matrix tr {
    display:block; margin:0 0 var(--ds-space-1); padding:var(--ds-space-1) var(--ds-space-2);
    border:1px solid var(--ds-line); border-radius:var(--ds-radius); background:var(--ds-surface); }
  .portal table.vis-matrix th {
    display:block; width:100%; height:auto; padding:0 0 var(--ds-space-1) !important; border:0 !important;
    color:var(--ds-ink); font-size:14px; font-weight:700; letter-spacing:0; }
  .portal table.vis-matrix td {
    display:flex !important; align-items:center; gap:10px; width:100%; height:auto !important;
    padding:6px 0 !important; border:0 !important; white-space:normal !important; }
  .portal table.vis-matrix td label { order:1; display:inline-flex; margin:0; }
  .portal table.vis-matrix td::before {
    content:attr(data-label); order:2; color:var(--ds-muted); font-size:13px; line-height:24px; }
}

/* =============================================================================
   Examinee roster (admin/chefs) — phone card view. The desktop view is a rowspan
   spreadsheet (one examinee cell spanning their attempt rows), which can't stack
   cleanly; on phones we hide it and render a purpose-built card per examinee with
   each attempt as a sub-block. The roster is read-only (no inputs), so showing
   one view per breakpoint is safe — nothing is submitted twice.
   ========================================================================== */
.roster-cards { display:none; }
@media (max-width:600px) {
  .portal .card .roster-table { display:none; }
  .roster-cards { display:flex; flex-direction:column; gap:var(--ds-space-2); margin-top:var(--ds-space-2); }
  .roster-card { border:1px solid var(--ds-line); border-radius:var(--ds-radius); background:var(--ds-surface); padding:var(--ds-space-2); }
  .roster-card__head { margin-bottom:var(--ds-space-1); }
  .roster-card__name { display:block; font-weight:700; font-size:15px; color:var(--ds-ink); }
  .roster-card__email { display:block; font-size:12px; color:var(--ds-muted); word-break:break-all; }
  .roster-attempt { border-top:1px solid var(--ds-line); margin-top:var(--ds-space-1); padding-top:var(--ds-space-1); }
  .roster-attempt__top { display:flex; align-items:center; justify-content:space-between; gap:var(--ds-space-1); }
  .roster-attempt__grade { font-family:var(--ds-font-display); font-weight:600; font-size:15px; }
  .roster-attempt__meta { display:grid; grid-template-columns:auto 1fr; gap:2px var(--ds-space-2); margin:var(--ds-space-1) 0 0; }
  .roster-attempt__meta dt { color:var(--ds-muted); font-size:12px; line-height:20px; }
  .roster-attempt__meta dd { margin:0; font-size:13px; line-height:20px; }
  .roster-card__none { margin:var(--ds-space-1) 0 0; color:var(--ds-muted); font-size:13px; }
}

/* =============================================================================
   Scoring history (admin/scoring-history) — phone scorecard view. The desktop
   ledger table is hidden ≤600px; each finalised result becomes a card with the
   applicant + verdict chip, a 実技/筆記/合計 score trio, and a grader/date footer.
   Read-only, so one view per breakpoint is safe.
   ========================================================================== */
.score-cards { display:none; }
@media (max-width:600px) {
  .portal .card .score-history-table { display:none; }
  .score-cards { display:flex; flex-direction:column; gap:var(--ds-space-2); margin-top:var(--ds-space-2); }
  .score-card { border:1px solid var(--ds-line); border-radius:var(--ds-radius); background:var(--ds-surface); padding:var(--ds-space-2); }
  .score-card__head { display:flex; align-items:center; justify-content:space-between; gap:var(--ds-space-1); }
  .score-card__name { font-weight:700; font-size:15px; color:var(--ds-ink); }
  .score-card__grade { font-family:var(--ds-font-display); font-weight:600; color:var(--ds-muted); margin-top:2px; }
  .score-card__scores { display:grid; grid-template-columns:repeat(3,1fr); gap:var(--ds-space-1); margin:var(--ds-space-2) 0; text-align:center; }
  .score-card__scores > div { border:1px solid var(--ds-line); border-radius:var(--ds-radius); padding:var(--ds-space-1) 0; }
  .score-card__scores > div.is-total { border-color:var(--ds-red-100); background:var(--ds-red-50); }
  .score-card__scores dt { color:var(--ds-muted); font-size:11px; line-height:16px; letter-spacing:.04em; }
  .score-card__scores dd { margin:0; font-size:18px; font-weight:700; font-variant-numeric:tabular-nums; line-height:24px; color:var(--ds-ink); }
  .score-card__foot { display:flex; align-items:center; justify-content:space-between; gap:var(--ds-space-1); color:var(--ds-muted); font-size:12px; border-top:1px solid var(--ds-line); padding-top:var(--ds-space-1); }
}

/* =============================================================================
   Exam session bookings (admin/exam-sessions) — phone card view. The desktop
   table is hidden ≤600px; each session becomes a card with the venue, grade,
   status, schedule + occupancy, and a "view bookings" link. Read-only.
   ========================================================================== */
.session-cards { display:none; }
@media (max-width:600px) {
  .portal .card .session-table { display:none; }
  .session-cards { display:flex; flex-direction:column; gap:var(--ds-space-2); margin-top:var(--ds-space-2); }
  .session-card { border:1px solid var(--ds-line); border-radius:var(--ds-radius); background:var(--ds-surface); padding:var(--ds-space-2); }
  .session-card__venue { font-weight:700; font-size:15px; color:var(--ds-ink); }
  .session-card__sub { color:var(--ds-muted); font-size:13px; margin-top:2px; }
  .session-card__meta { display:grid; grid-template-columns:auto 1fr; gap:4px var(--ds-space-2); margin:var(--ds-space-1) 0; border-top:1px solid var(--ds-line); padding-top:var(--ds-space-1); }
  .session-card__meta dt { color:var(--ds-muted); font-size:12px; line-height:20px; }
  .session-card__meta dd { margin:0; font-size:13px; line-height:20px; }
  .session-card__link { display:block; text-align:center; margin-top:var(--ds-space-1); padding:10px; border:1px solid var(--ds-line); border-radius:9999px; color:var(--ds-ink); font-weight:700; font-size:13px; text-decoration:none; }
  .session-card__link:hover { background:var(--ds-cream); }
}

/* Session roster summary header (admin/exam-sessions/show) — venue title + a meta
   list that is a wrapping row on desktop and a clean stacked list on phones. */
.session-summary { margin:0 0 var(--ds-space-2); }
.session-summary__venue { margin:0; font-family:var(--ds-font-display); font-size:18px; font-weight:600; letter-spacing:.04em; color:var(--ds-ink); }
.session-summary__meta { display:flex; flex-wrap:wrap; gap:var(--ds-space-1) var(--ds-space-3); margin:0; }
.session-summary__meta > div { display:flex; align-items:baseline; gap:var(--ds-space-1); }
.session-summary__meta dt { color:var(--ds-muted); font-size:12px; }
.session-summary__meta dd { margin:0; font-size:14px; font-weight:600; color:var(--ds-ink); }
@media (max-width:600px) {
  .session-summary__meta { flex-direction:column; gap:6px; }
}

/* =============================================================================
   Sales / payments (admin/sales) — phone card view. The desktop ledger is hidden
   ≤600px; each settled payment becomes a card with the payer + status chip, the
   amount prominent, and item / member type / paid-on as a meta list.
   ========================================================================== */
.pay-cards { display:none; }
@media (max-width:600px) {
  .portal .card .pay-table { display:none; }
  .pay-cards { display:flex; flex-direction:column; gap:var(--ds-space-2); margin-top:var(--ds-space-2); }
  .pay-card { border:1px solid var(--ds-line); border-radius:var(--ds-radius); background:var(--ds-surface); padding:var(--ds-space-2); }
  .pay-card__head { display:flex; align-items:flex-start; justify-content:space-between; gap:var(--ds-space-1); }
  .pay-card__name { display:block; font-weight:700; font-size:15px; color:var(--ds-ink); }
  .pay-card__email { display:block; font-size:12px; color:var(--ds-muted); word-break:break-all; }
  .pay-card__amount { font-family:var(--ds-font-display); font-weight:700; font-size:22px; color:var(--ds-ink); font-variant-numeric:tabular-nums; margin:var(--ds-space-1) 0; }
  .pay-card__meta { display:grid; grid-template-columns:auto 1fr; gap:4px var(--ds-space-2); margin:0; border-top:1px solid var(--ds-line); padding-top:var(--ds-space-1); }
  .pay-card__meta dt { color:var(--ds-muted); font-size:12px; line-height:20px; }
  .pay-card__meta dd { margin:0; font-size:13px; line-height:20px; }
}

/* =============================================================================
   Staff & permissions (admin/staff) — phone card view. The desktop roster is
   hidden ≤600px; each member becomes a card with name, email, permission chips,
   and a registered/edit footer.
   ========================================================================== */
.staff-cards { display:none; }
@media (max-width:600px) {
  .portal .card .staff-table { display:none; }
  .staff-cards { display:flex; flex-direction:column; gap:var(--ds-space-2); margin-top:var(--ds-space-2); }
  .staff-card { border:1px solid var(--ds-line); border-radius:var(--ds-radius); background:var(--ds-surface); padding:var(--ds-space-2); }
  .staff-card__name { font-weight:700; font-size:15px; color:var(--ds-ink); }
  .staff-card__email { font-size:12px; color:var(--ds-muted); word-break:break-all; margin-top:2px; }
  .staff-card__perms { margin:var(--ds-space-1) 0; border-top:1px solid var(--ds-line); padding-top:var(--ds-space-1); }
  .staff-card__perms-label { display:block; color:var(--ds-muted); font-size:12px; margin-bottom:6px; }
  .staff-card__chips { display:flex; flex-wrap:wrap; gap:var(--ds-space-1); }
  .staff-card__none { color:var(--ds-muted); }
  .staff-card__foot { display:flex; align-items:center; justify-content:space-between; gap:var(--ds-space-1); border-top:1px solid var(--ds-line); padding-top:var(--ds-space-1); color:var(--ds-muted); font-size:12px; }
}

/* ── お問い合わせ / inquiries (Phase 5) ───────────────────────────────────────────
   Thread list (chef + admin) and the chat thread itself. Base rules (no media
   query) so they read at every width; the row/bubble flex naturally on phones. */
.portal .inq-head { display:flex; align-items:center; justify-content:space-between; gap:var(--ds-space-2); flex-wrap:wrap; margin-bottom:var(--ds-space-3); }
.portal .inq-lead { margin:0; color:var(--ds-muted); font-size:13px; }

.portal .inq-list { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:var(--ds-space-1); }
/* a lead sentence above the list (the hub's 求人 count) gets the card rhythm gap */
.portal .card__lead + .inq-list { margin-top:var(--ds-space-2); }
.portal .inq-row { display:flex; align-items:center; justify-content:space-between; gap:var(--ds-space-2); padding:var(--ds-space-2); border:1px solid var(--ds-line); border-radius:10px; background:var(--ds-surface); text-decoration:none; color:var(--ds-ink); transition:background .15s, border-color .15s; }
.portal .inq-row:hover { background:var(--ds-cream); border-color:var(--ds-red-100); }
.portal .inq-row__main { display:flex; flex-direction:column; gap:2px; min-width:0; }
.portal .inq-row__topic { font-weight:700; font-size:14px; display:flex; align-items:center; gap:var(--ds-space-1); flex-wrap:wrap; }
.portal .inq-row__preview { color:var(--ds-muted); font-size:12px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.portal .inq-row__meta { display:flex; align-items:center; gap:var(--ds-space-1); flex-shrink:0; }
.portal .inq-row__time { color:var(--ds-muted); font-size:11px; white-space:nowrap; }

.portal .inq-back { margin:0 0 var(--ds-space-2); }
.portal .inq-back a { color:var(--ds-muted); text-decoration:none; font-size:13px; }
.portal .inq-back a:hover { color:var(--ds-ink); }

.portal .inq-summary { display:flex; flex-wrap:wrap; gap:var(--ds-space-3); margin:0 0 var(--ds-space-3); padding-bottom:var(--ds-space-2); border-bottom:1px solid var(--ds-line); }
.portal .inq-summary dt { color:var(--ds-muted); font-size:11px; font-weight:700; }
.portal .inq-summary dd { margin:0; font-size:14px; }

.portal .msg-thread { list-style:none; margin:0 0 var(--ds-space-4); padding:0; display:flex; flex-direction:column; gap:var(--ds-space-2); }
.portal .msg { display:flex; flex-direction:column; max-width:82%; }
.portal .msg--mine { align-self:flex-end; align-items:flex-end; }
.portal .msg--theirs { align-self:flex-start; align-items:flex-start; }
.portal .msg__meta { font-size:11px; color:var(--ds-muted); margin:0 4px 2px; }
.portal .msg__bubble { display:inline-block; padding:var(--ds-space-1) var(--ds-space-2); border-radius:12px; line-height:1.7; border:1px solid var(--ds-line); background:var(--ds-surface); }
.portal .msg--mine .msg__bubble { background:var(--ds-red-50); border-color:var(--ds-red-100); }

.portal .inq-reply { border-top:1px solid var(--ds-line); padding-top:var(--ds-space-3); }

/* Message composers are prose, not a field: the 520px cap that keeps name/postcode
   inputs from sprawling makes a chat box look like a stub. Let these run the card's
   full width — the reply boxes and the 求人 contact form share this. */
.portal .card .inq-reply textarea,
.portal .card .msg-compose textarea { max-width:none; }

/* a non-clickable list row (e.g. a diploma row whose action is a button) — keep
   the row chrome but drop the link affordances. */
.portal .inq-row--static { cursor:default; }
.portal .inq-row--static:hover { background:var(--ds-surface); border-color:var(--ds-line); }

/* ── entity roster (Phase 7 — 企業管理 / 店舗管理) ────────────────────────────────
   A brand-new, reusable responsive presentation of an admin roster: a data table
   at ≥601px and stacked cards at ≤600px, the same rows rendered twice and toggled
   purely by CSS (no JS). Used by the company + store management screens; ready for
   any later admin list that wants the same treatment. */
.entity-list__table { width:100%; border-collapse:collapse; font-size:14px; }
.entity-list__table thead th {
  text-align:left; padding:var(--ds-space-1) var(--ds-space-2);
  border-bottom:2px solid var(--ds-line); color:var(--ds-muted);
  font-size:12px; font-weight:700; letter-spacing:.03em; white-space:nowrap;
}
.entity-list__table tbody td { padding:var(--ds-space-2); border-bottom:1px solid var(--ds-line); color:var(--ds-ink); vertical-align:middle; }
/* No row-hover wash: the admin lists built on `.ta1` do not tint their rows, and two
   list screens side by side behaving differently read as a glitch. The action button's
   own hover (red outline) carries the affordance on both. */
.entity-list__table .cell-center { text-align:center; }
.entity-list__table tbody td a { color:var(--ds-primary); text-decoration:none; font-weight:600; }
.entity-list__table tbody td a:hover { text-decoration:underline; }

/* Phone card variant — hidden until the phone breakpoint flips it on. */
.entity-cards { display:none; }
.entity-card {
  border:1px solid var(--ds-line); border-radius:var(--ds-radius-xl2);
  background:var(--ds-surface); padding:var(--ds-space-2) var(--ds-space-3); box-shadow:var(--ds-shadow-soft);
}
.entity-card__title { font-family:var(--ds-font-display); font-weight:600; font-size:16px; color:var(--ds-ink); letter-spacing:.02em; }
.entity-card__meta { margin:var(--ds-space-1) 0 0; padding:0; }
.entity-card__row { display:flex; justify-content:space-between; gap:var(--ds-space-2); padding:4px 0; }
.entity-card__row dt { margin:0; color:var(--ds-muted); font-size:12px; font-weight:700; }
.entity-card__row dd { margin:0; font-size:13px; color:var(--ds-ink); text-align:right; word-break:break-all; }
.entity-card__foot {
  display:flex; align-items:center; justify-content:space-between; gap:var(--ds-space-1);
  margin-top:var(--ds-space-1); border-top:1px solid var(--ds-line); padding-top:var(--ds-space-1);
  color:var(--ds-muted); font-size:12px;
}

@media (max-width:600px) {
  .portal .card .entity-list { display:none; }
  .entity-cards { display:flex; flex-direction:column; gap:var(--ds-space-2); margin-top:var(--ds-space-2); }
}

/* ── field grid (Phase 7 admin forms) ─────────────────────────────────────────
   A responsive two-column form layout — pairs of fields side by side on desktop,
   a single stacked column on phones. Brand-new component so the onboarding forms
   read cleanly at every width without leaning on the legacy <p><label> primitives. */
.field-grid { display:grid; grid-template-columns:repeat(2, minmax(0, 1fr)); gap:var(--ds-space-2) var(--ds-space-3); }
.field { display:flex; flex-direction:column; gap:6px; min-width:0; }
.field--wide { grid-column:1 / -1; }
.field__label { color:var(--ds-ink); font-size:13px; font-weight:700; letter-spacing:.02em; }
/* Scoped to real form controls: elsewhere `field__control` IS the input/select,
   but the x-field pattern (mypage/profile) uses <div class="field__control"> as a
   WRAPPER — unscoped, that div also picked up this border, doubling it against the
   input's own border. Targeting the control elements keeps the wrapper borderless. */
input.field__control,
select.field__control,
textarea.field__control {
  height:var(--ds-control-h); width:100%; box-sizing:border-box;
  padding:0 var(--ds-space-2); border:1px solid var(--ds-line); border-radius:var(--ds-radius);
  background:var(--ds-surface); color:var(--ds-ink); font-size:14px; font-family:inherit;
}
input.field__control:focus,
select.field__control:focus,
textarea.field__control:focus { outline:none; border-color:var(--ds-primary); box-shadow:0 0 0 3px rgba(var(--ds-sun-rgb),.12); }
select.field__control { appearance:none; -webkit-appearance:none; cursor:pointer; }
.field__hint  { color:var(--ds-muted); font-size:12px; line-height:1.5; }
.field__error { color:var(--ds-primary-dark); font-size:12px; }
/* A form field group greyed while inapplicable (e.g. 筆記併課 for a 筆記-only grade, #142). */
p.is-disabled { opacity:.55; }

/* a split footer — cancel on the left, primary on the right. */
.card__foot--split { display:flex; align-items:center; justify-content:space-between; gap:var(--ds-space-2); flex-wrap:wrap; }

@media (max-width:600px) {
  .field-grid { grid-template-columns:1fr; }
  .card__foot--split { flex-direction:column-reverse; align-items:stretch; }
  .card__foot--split .btn3, .card__foot--split .btn-ghost { width:100%; text-align:center; }
}

/* ── chef directory (Phase 7 — 求職者検索 / 検定者詳細) ──────────────────────────
   Brand-new responsive components the hiring-company portal uses to browse and
   view certified chefs. The grid auto-fits tiles (a wall on desktop, one column
   on phones); the profile head sits beside its facts on desktop and stacks on
   phones. Fully fluid — no per-width duplication of markup. */
.chef-grid {
  list-style:none; margin:var(--ds-space-2) 0 0; padding:0;
  display:grid; gap:var(--ds-space-2);
  grid-template-columns:repeat(auto-fill, minmax(150px, 1fr));
}
.chef-tile {
  display:flex; flex-direction:column; align-items:center; text-align:center; gap:6px;
  padding:var(--ds-space-2); border:1px solid var(--ds-line); border-radius:var(--ds-radius-xl2);
  background:var(--ds-surface); color:var(--ds-ink); text-decoration:none;
  transition:border-color .15s, box-shadow .15s, transform .15s;
}
.chef-tile:hover { border-color:var(--ds-red-100); box-shadow:var(--ds-shadow-soft); transform:translateY(-2px); }
.chef-tile__photo { width:88px; height:88px; border-radius:50%; overflow:hidden; background:var(--ds-cream); display:grid; place-items:center; }
.chef-tile__photo img { width:100%; height:100%; object-fit:cover; }
.chef-tile__avatar { font-family:var(--ds-font-display); font-size:32px; color:var(--ds-muted); }
.chef-tile__name { font-weight:700; font-size:14px; }
.chef-tile__meta { color:var(--ds-muted); font-size:12px; }

.profile-head { display:flex; gap:var(--ds-space-3); align-items:center; margin:0 0 var(--ds-space-3); padding-bottom:var(--ds-space-3); border-bottom:1px solid var(--ds-line); }
.profile-head__photo { flex:none; width:120px; height:120px; border-radius:var(--ds-radius-xl2); overflow:hidden; background:var(--ds-cream); display:grid; place-items:center; }
.profile-head__photo img { width:100%; height:100%; object-fit:cover; }
.profile-head__avatar { font-family:var(--ds-font-display); font-size:44px; color:var(--ds-muted); }
.profile-head__ident { min-width:0; }
.profile-head__name { font-family:var(--ds-font-display); font-size:22px; font-weight:600; letter-spacing:.03em; margin:0 0 var(--ds-space-1); color:var(--ds-ink); }

.cert-badges { list-style:none; margin:0; padding:0; display:flex; flex-wrap:wrap; gap:6px; }
.cert-badge {
  display:inline-flex; align-items:baseline; gap:6px; padding:4px 10px;
  border:1px solid var(--ds-gold-100); border-radius:9999px; background:var(--ds-gold-50);
  color:var(--ds-gold-700); font-weight:700; font-size:13px;
}
.cert-badge span { color:var(--ds-muted); font-weight:400; font-size:11px; }

/* A badge that DOES something — the certificate's verification page, or a clip that
   opens in the player. Same pill, plus the affordances a static badge has no need of. */
a.cert-badge, button.cert-badge { cursor:pointer; font:inherit; font-weight:700; font-size:13px; text-decoration:none; transition:border-color .15s, box-shadow .15s; }
a.cert-badge:hover, button.cert-badge:hover { border-color:var(--ds-gold-700); }
a.cert-badge:focus-visible, button.cert-badge:focus-visible { outline:none; box-shadow:0 0 0 3px var(--ds-gold-100); }

.profile-facts { margin:0; padding:0; }
.profile-facts__row { display:flex; justify-content:space-between; gap:var(--ds-space-3); padding:var(--ds-space-2) 0; border-bottom:1px solid var(--ds-line); }
.profile-facts__row dt { margin:0; color:var(--ds-muted); font-size:13px; font-weight:700; }
.profile-facts__row dd { margin:0; font-size:14px; color:var(--ds-ink); text-align:right; word-break:break-word; }
.profile-facts__hidden { color:var(--ds-muted); font-style:italic; }
.profile-facts__empty { color:var(--ds-muted); }

@media (max-width:600px) {
  .profile-head { flex-direction:column; text-align:center; align-items:center; }
  .profile-head__ident .cert-badges { justify-content:center; }
}

/* filter bar (Phase 7 — chef search 7.A.3). A responsive row of selects + an
   action; wraps on tablet and stacks full-width on phones. */
.filter-bar {
  display:flex; flex-wrap:wrap; align-items:flex-end; gap:var(--ds-space-2);
  margin:0 0 var(--ds-space-2); padding:var(--ds-space-2) var(--ds-space-3);
  border:1px solid var(--ds-line); border-radius:var(--ds-radius-xl2); background:var(--ds-cream);
}
.filter-bar__field { display:flex; flex-direction:column; gap:6px; flex:1 1 180px; min-width:0; }
.filter-bar__actions { display:flex; gap:var(--ds-space-1); align-items:center; }
/* The filter-bar reuses the generic .field__label / .field__control class names,
   but its field is a vertical STACK — so the x-field desktop rule that turns the
   label into a fixed 200px-wide left COLUMN (see `.portal .card .field__label`
   above) instead makes it a 200px-TALL block here, opening a large gap above the
   selects. Neutralise that leak for the filter-bar only (label sits on its own
   line, control fills the row). */
.portal .card .filter-bar__field .field__label { flex:none; padding-top:0; }
.portal .card .filter-bar__field .field__control { flex:0 1 auto; }

@media (max-width:600px) {
  .filter-bar { flex-direction:column; align-items:stretch; }
  /* Column mode: the row flex-basis (180px) would otherwise become each field's
     HEIGHT (tall fields, big gaps) — let fields size to their content instead. */
  .filter-bar__field { flex:0 0 auto; }
  .filter-bar__actions { justify-content:space-between; }
  .filter-bar__actions .btn3 { flex:1; }
}

/* venue facility photos (Phase 7 — 設備写真 7.B.2): a responsive slot grid. */
.photo-grid { display:grid; gap:var(--ds-space-2); grid-template-columns:repeat(auto-fill, minmax(200px, 1fr)); margin-top:var(--ds-space-2); }
.photo-slot { display:flex; flex-direction:column; gap:6px; }
.photo-slot__label { font-size:13px; font-weight:700; color:var(--ds-ink); }
/* Square, and `contain` rather than `cover`: this is the screen where a venue
   checks WHAT it attached, so the frame must show the whole file. Cropping here
   reads as "the upload will cut my photo" — it does not, the original bytes are
   stored untouched. A 4:3 landscape shot renders at exactly the size the old 4:3
   `cover` frame gave it; a portrait one now fits instead of losing its top and
   bottom. */
.photo-slot__frame { aspect-ratio:1/1; border:1px solid var(--ds-line); border-radius:var(--ds-radius); overflow:hidden; background:var(--ds-cream); display:grid; place-items:center; }
.photo-slot__frame img { width:100%; height:100%; object-fit:contain; }
.photo-slot__empty { font-size:40px; color:var(--ds-muted); line-height:1; }

/* 利用規約 — the legal links under every portal screen, and the scrolling box the
   agreement gate reads them in. Quiet by design: the portal is a workspace, and a
   marketing footer at the foot of every working screen would compete with it. */
.portal__legal { display:flex; flex-wrap:wrap; gap:var(--ds-space-2); margin:var(--ds-space-5) 0 0; font-size:12px; }
.portal__legal a { color:var(--ds-muted); text-decoration:none; }
.portal__legal a:hover { color:var(--ds-ink); text-decoration:underline; }
.terms-scroll {
  max-height:56vh; overflow-y:auto; margin:var(--ds-space-2) 0;
  padding:var(--ds-space-3); border:1px solid var(--ds-line); border-radius:var(--ds-radius);
  background:var(--ds-surface);
}
.terms-scroll h3, .terms-scroll h4, .terms-scroll h5 { text-align:left; }
/* A tile is a link now — keep it looking like the picture, not like link text. */
.photo-slot__frame a { display:block; width:100%; height:100%; line-height:0; }
.photo-slot__file { font-size:12px; }
.photo-slot__remove { align-self:flex-start; background:none; border:none; color:var(--ds-primary-dark); font-size:12px; cursor:pointer; padding:0; text-decoration:underline; }
.is-hidden { display:none; }

/* job-offer form (Phase 7 — 求人 7.A.2): grouped feature checkboxes as pills. */
.offer-fieldset { border:1px solid var(--ds-line); border-radius:var(--ds-radius-xl2); padding:var(--ds-space-2) var(--ds-space-3); margin:var(--ds-space-2) 0 0; }
.offer-fieldset legend { font-size:13px; font-weight:700; color:var(--ds-muted); padding:0 6px; }
.check-grid { display:flex; flex-wrap:wrap; gap:var(--ds-space-1); }
.check-pill { display:inline-flex; align-items:center; gap:6px; padding:6px 12px; border:1px solid var(--ds-line); border-radius:9999px; background:var(--ds-surface); font-size:13px; cursor:pointer; }
.check-pill:has(input:checked) { background:var(--ds-red-50); border-color:var(--ds-red-100); color:var(--ds-primary-dark); font-weight:700; }
.check-pill input { accent-color:var(--ds-primary); }
.offer-status-actions form { display:inline; }

/* shortlist star toggle (Phase 7 — お気に入り 7.A.5). */
.profile-topbar { display:flex; align-items:center; justify-content:space-between; gap:var(--ds-space-2); flex-wrap:wrap; margin-bottom:var(--ds-space-1); }

/* A card heading that carries the form's actions on its own row — for long forms
   where the footer buttons would otherwise sit a screenful below the first field. */
.card__head--actions { display:flex; align-items:center; justify-content:space-between; gap:var(--ds-space-2); flex-wrap:wrap; }
.form-topbar__actions { display:flex; align-items:center; gap:var(--ds-space-1); }
.form-topbar__actions form { margin:0; }

@media (max-width:600px) {
  .card__head--actions { align-items:stretch; }
  .form-topbar__actions { justify-content:space-between; }
  .form-topbar__actions .btn3, .form-topbar__actions .btn-ghost { flex:1; text-align:center; }
}
.profile-topbar .inq-back { margin:0; }
.star-btn {
  display:inline-flex; align-items:center; gap:6px; height:var(--ds-control-h-md);
  padding:0 var(--ds-space-2); border:1px solid var(--ds-gold-100); border-radius:9999px;
  background:var(--ds-surface); color:var(--ds-gold-700); font-weight:700; font-size:13px; cursor:pointer;
  transition:background .15s, border-color .15s;
}
.star-btn svg { width:16px; height:16px; }
.star-btn:hover { background:var(--ds-gold-50); }
.star-btn.is-on { background:var(--ds-gold-50); border-color:var(--ds-gold-500); }

/* In-page exam-video review + modal player (#150, legacy parity) */
.review-videos { display:flex; flex-direction:column; gap:var(--ds-space-2); margin-bottom:var(--ds-space-2); }
.review-video__label { font-size:13px; font-weight:600; color:var(--ds-ink); margin-bottom:var(--ds-space-1); }
.review-video__frame { position:relative; aspect-ratio:16/9; max-width:560px; background:var(--ds-cream); border:1px solid var(--ds-line); border-radius:var(--ds-radius); overflow:hidden; display:flex; align-items:center; justify-content:center; }
.review-video__frame iframe { width:100%; height:100%; border:0; }
.review-video__play {
  display:inline-flex; align-items:center; justify-content:center; box-sizing:border-box;
  height:var(--ds-control-h-md); padding:0 var(--ds-space-3);   /* same size as .btn3 */
  border:0; border-radius:9999px; background:var(--ds-primary); color:#fff;
  font-family:inherit; font-size:13px; font-weight:700; letter-spacing:.08em; cursor:pointer;
}
.review-video__play:hover { filter:brightness(.95); }

.video-modal { position:fixed; inset:0; z-index:100; display:flex; align-items:center; justify-content:center; }
.video-modal[hidden] { display:none; }
.video-modal__backdrop { position:absolute; inset:0; background:rgba(0,0,0,.6); }
.video-modal__dialog { position:relative; width:min(900px,92vw); background:var(--ds-surface); border-radius:var(--ds-radius-xl2); padding:var(--ds-space-3); box-shadow:var(--ds-shadow-soft); }
.video-modal__close { position:absolute; top:-14px; right:-14px; width:36px; height:36px; border-radius:999px; border:1px solid var(--ds-line); background:var(--ds-surface); font-size:20px; line-height:1; cursor:pointer; }
.video-modal__header { padding:0 var(--ds-space-1) var(--ds-space-2); border-bottom:1px solid var(--ds-line); margin-bottom:var(--ds-space-2); }
.video-modal__title { margin:0; font-size:15px; font-weight:700; line-height:24px; color:var(--ds-ink); }
.video-modal__meta { margin:0; font-size:12.5px; line-height:20px; color:var(--ds-muted); }
.video-modal__meta:empty { display:none; }
.video-modal__frame { aspect-ratio:16/9; }
.video-modal__frame iframe { width:100%; height:100%; border:0; border-radius:var(--ds-radius); }

/* ── side panel (drawer over the list) ────────────────────────────────────────
   An admin sub-screen opened beside the row it belongs to instead of replacing
   the page, so the list stays readable while you edit. Fetched and filled by
   public/js/side-panel.js; this file only dresses the shell.

   Sits above the sidebar (z-40) and the video modal's backdrop convention, and
   below nothing else — while it is open `html.is-panel-open` locks the page
   behind it so the list does not scroll under the sheet. */
.side-panel { position:fixed; inset:0; z-index:60; }
.side-panel[hidden] { display:none; }
.side-panel__scrim { position:absolute; inset:0; background:rgba(33,29,23,.38); }

.side-panel__sheet {
  position:absolute; top:0; right:0; bottom:0; width:min(720px, 92vw);
  display:flex; flex-direction:column;
  background:var(--ds-app-bg); border-left:1px solid var(--ds-line);
  box-shadow:-18px 0 48px -24px rgba(33,29,23,.45);
  animation:side-panel-in .22s ease;
}
@keyframes side-panel-in { from { transform:translateX(24px); opacity:.4; } to { transform:none; opacity:1; } }
@media (prefers-reduced-motion:reduce) { .side-panel__sheet { animation:none; } }

.side-panel__head {
  flex:none; display:flex; align-items:center; gap:var(--ds-space-2);
  min-height:var(--ds-topbar-h); padding:0 var(--ds-space-3);
  background:var(--ds-surface); border-bottom:1px solid var(--ds-line);
}
.side-panel__title { flex:1 1 auto; margin:0; font-size:18px; line-height:24px; color:var(--ds-ink); }
.side-panel__back, .side-panel__close {
  flex:none; height:36px; width:36px; display:grid; place-items:center;
  padding:0; border:1px solid var(--ds-line); border-radius:9999px;
  background:var(--ds-surface); color:var(--ds-ink); cursor:pointer;
}
.side-panel__back[hidden] { display:none; }
.side-panel__back:hover, .side-panel__close:hover { background:var(--ds-cream); }
.side-panel__back svg, .side-panel__close svg { height:18px; width:18px; }

/* The fetched screen keeps its own card markup, so the body only scrolls. */
.side-panel__body { flex:1 1 auto; min-height:0; overflow-y:auto; padding:var(--ds-space-3); }
.side-panel__body > .card { margin:0; }

.is-panel-open body { overflow:hidden; }

/* Phone: a full-height sheet — a 720px drawer has nowhere to sit. */
@media (max-width:600px) {
  .side-panel__sheet { width:100%; border-left:0; }
  .side-panel__body { padding:var(--ds-space-2); }
}

/* 職員一覧の権限セル — the one cell in an admin table allowed to wrap. Multi-column
   tables set `white-space:nowrap` so cells don't fold mid-value, but a staffer
   holding every capability turned that into a 2,000px-wide row and pushed the
   columns after it out of reach. Chips are self-contained, so wrapping between
   them loses nothing.

   The chips lay out as a flex wrap so the gap is the grid step in BOTH directions:
   as inline elements they only had word-spacing between them and their wrapped rows
   sat flush against each other. */
.portal table.ta1.ta3 td.staff-perms { white-space:normal; min-width:14rem; max-width:26rem; }
.portal table.ta1.ta3 td.staff-perms .status-chip { margin:0; }
.staff-perms__chips { display:flex; flex-wrap:wrap; gap:var(--ds-space-1); }

/* ── confirmation dialog ──────────────────────────────────────────────────────
   Shown before a write whose consequences are worth reading (deactivating a
   colleague's sign-in, say). A real <dialog>, so focus trapping, Escape and the
   backdrop are the platform's job, not ours. */
.confirm-dialog {
  width:min(480px, 92vw); padding:var(--ds-space-3);
  border:1px solid var(--ds-line); border-radius:var(--ds-radius-xl2);
  background:var(--ds-surface); color:var(--ds-ink); box-shadow:var(--ds-shadow-soft);
}
.confirm-dialog::backdrop { background:rgba(33,29,23,.38); }

/* 設備写真 full-size viewer (partials/photo-lightbox + public/js/lightbox.js).
   The dialog itself fills the viewport and is transparent: everything outside the
   picture is backdrop, so "click away to go back" needs no invisible hit areas.
   The wash is the washi tone + blur the topbar already uses — light, as asked, and
   the blur is what separates the photo from the page rather than darkness. */
/* IMPORTANT: no `display` on this bare selector. The UA sheet closes a dialog with
   `dialog:not([open]) { display:none }`, and ANY author `display` here beats it —
   the closed dialog would stay in the page as a full-size element. Hence the
   display value lives only on [open]. */
.photo-lightbox {
  max-width:min(96vw, 1100px); max-height:92vh; margin:auto;
  padding:var(--ds-space-3); box-sizing:border-box;
  border:0; background:transparent; overflow:visible;
}
/* Sized to the picture, pinned to the VIEWPORT and centred in it — `position:fixed`
   and `inset:0` are spelled out rather than inherited from the UA's `dialog:modal`
   rule, so the photo always opens over whatever the reader is looking at instead of
   landing somewhere up the document and making them scroll back to it.
   (Forcing the box itself to 100vw/100vh was the earlier mistake: its contents then
   had to be centred by hand, and the photo got clipped when they did not quite fit.) */
.photo-lightbox[open] {
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  position:fixed; inset:0;
}
.photo-lightbox::backdrop { background:rgba(245,242,234,.82); backdrop-filter:blur(8px); -webkit-backdrop-filter:blur(8px); }
.photo-lightbox__figure { margin:0; display:flex; flex-direction:column; align-items:center; gap:var(--ds-space-2); min-height:0; max-width:100%; }
.photo-lightbox__figure img {
  display:block; max-width:100%; max-height:calc(92vh - 96px);
  object-fit:contain; border-radius:var(--ds-radius); background:var(--ds-surface); box-shadow:var(--ds-shadow-soft);
}
.photo-lightbox__caption { font-size:13px; font-weight:700; color:var(--ds-ink); text-align:center; }
.photo-lightbox__close {
  position:fixed; top:var(--ds-space-2); right:var(--ds-space-2);
  width:40px; height:40px; border-radius:999px; border:1px solid var(--ds-line);
  background:var(--ds-surface); color:var(--ds-ink); font-size:22px; line-height:1; cursor:pointer;
}
.confirm-dialog__title { margin:0 0 var(--ds-space-2); font-size:16px; font-weight:700; line-height:24px; }
.confirm-dialog__body { display:flex; flex-direction:column; gap:var(--ds-space-1); }
.confirm-dialog__body p { margin:0; font-size:13.5px; line-height:22px; color:var(--ds-muted); }
.confirm-dialog__actions {
  display:flex; justify-content:flex-end; gap:var(--ds-space-2);
  margin-top:var(--ds-space-3); padding-top:var(--ds-space-2); border-top:1px solid var(--ds-line);
}

/* ── confirmation dialog ──────────────────────────────────────────────────────
   Shown before a write whose consequences are worth reading (deactivating a
   colleague's sign-in, say). A real <dialog>, so focus trapping, Escape and the
   backdrop are the platform's job, not ours. */
.confirm-dialog {
  width:min(480px, 92vw); padding:var(--ds-space-3);
  border:1px solid var(--ds-line); border-radius:var(--ds-radius-xl2);
  background:var(--ds-surface); color:var(--ds-ink); box-shadow:var(--ds-shadow-soft);
}
.confirm-dialog::backdrop { background:rgba(33,29,23,.38); }
.confirm-dialog__title { margin:0 0 var(--ds-space-2); font-size:16px; font-weight:700; line-height:24px; }
.confirm-dialog__body { display:flex; flex-direction:column; gap:var(--ds-space-1); }
.confirm-dialog__body p { margin:0; font-size:13.5px; line-height:22px; color:var(--ds-muted); }
.confirm-dialog__actions {
  display:flex; justify-content:flex-end; gap:var(--ds-space-2);
  margin-top:var(--ds-space-3); padding-top:var(--ds-space-2); border-top:1px solid var(--ds-line);
}

/* ── practical score sheet (採点内容の詳細) ──────────────────────────────────────
   One block per 課題セット, one row per 採点項目: the item and its mark, nothing else.
   A row that lost points is marked (.is-partial) so the eye still finds it without
   a chart. */





/* ── practical score table (採点内容の詳細) ───────────────────────────────────
   A cross-tab of 課題セット × 採点項目. The rubric is the same two items for every
   set, so the marks form a small matrix: one column per item lets a reader compare
   the same item across sets at a glance, which is what a disputed result turns on.
   Numbers are tabular so the columns line up. */
.score-table td.cell-num, .score-table th.cell-num { font-variant-numeric:tabular-nums; white-space:nowrap; }
.score-table__max { font-size:12px; font-weight:600; color:var(--ds-muted); }

/* A cell that did not earn full marks — the thing worth finding on this page. */
.score-table td.is-partial { color:var(--ds-primary); font-weight:700; }
.score-table td.is-partial .score-table__max { color:var(--ds-primary); opacity:.7; }

.score-table__subtotal { font-weight:700; }
.score-table__total-row td { background:var(--ds-cream); border-top:2px solid var(--ds-line); }

/* Several angle buttons share one cell; let them wrap rather than stretch the row.
   Deliberately NOT `.cell-action`: that class pins the last column for tables wide
   enough to scroll, and brings an opaque fill + edge shadow with it — visible here as
   a panel behind the buttons on a table that never scrolls. */
.score-table__videos { white-space:normal; }
.score-table__videos .btn-ghost { margin:2px; }

/* 検定者詳細's 実技動画: one small 実技の記録-style table per 級. The heading carries the
   grade so the tables stack without repeating it in every row. */
.video-record + .video-record { margin-top:var(--ds-space-3); }
.video-record__grade { display:flex; align-items:baseline; gap:8px; margin:var(--ds-space-2) 0 6px; font-weight:800; font-size:14px; color:var(--ds-ink); }
.video-record__grade span { color:var(--ds-muted); font-weight:400; font-size:12px; }

/* Bulk switches above the visibility matrix — a quiet pair, since the decision that
   matters is per row; these only save the tedium of nine. */
.vis-bulk { display:flex; gap:var(--ds-space-1); margin:0 0 var(--ds-space-2); }

/* The column tips open LEFTWARD. A card holding a multi-column table is a scroll
   container (`overflow-x:auto`), which clips absolutely-positioned children — and the
   default left-anchored bubble ran past the card's right edge on the last columns and
   was cut off. Opening inward keeps every bubble inside the box. */
.vis-matrix .info-tip__bubble {
  left:auto; right:0;
  /* …and wraps: a multi-column table sets `white-space:nowrap` on its cells so
     values never fold, which the bubble inherited and became one long clipped line. */
  white-space:normal;
}

/* Sign-up publish opt-in — the visibility question asked on the registration form
   rather than left to a settings screen the chef may never open. A plain list, since
   the decision is a series of yes/no answers, not a grid. */
/* Width-matched to the form table above it (the legacy `.ta2` sits at a fixed 804px
   and is centred), so the block reads as part of the form rather than a full-bleed
   band across the page. */
.publish-optin { max-width:804px; margin:var(--ds-space-3) auto 0; padding:var(--ds-space-3); border:1px solid var(--ds-line); border-radius:var(--ds-radius-xl2); background:var(--ds-surface); box-sizing:border-box; }
.publish-optin__lead { margin:0 0 var(--ds-space-2); font-size:14px; line-height:24px; color:var(--ds-ink); text-align:left; }
.publish-optin__list { list-style:none; margin:0; padding:0; display:grid; grid-template-columns:repeat(auto-fill, minmax(9rem, 1fr)); gap:var(--ds-space-1) var(--ds-space-2); }
.publish-optin__list label { display:flex; align-items:center; gap:var(--ds-space-1); font-size:14px; color:var(--ds-ink); cursor:pointer; }
.publish-optin__note { margin:var(--ds-space-2) 0 0; font-size:12.5px; line-height:20px; color:var(--ds-muted); text-align:left; }

/* 設備写真 — the nine slots grouped 外観 / 内装 / アイテム, as the venue review reads
   them. A quiet section head so the groups separate without shouting. */
/* `text-align` is set explicitly because this label also renders inside <main> on the
   会場申込完了 page, where the marketing sheet centres `main h3` below 900px. The rest
   of the declarations already win there on their own: one class (0,1,0) outranks
   `main h3` (0,0,2). */
.photo-group__title { margin:var(--ds-space-3) 0 var(--ds-space-1); font-size:13px; font-weight:800; color:var(--ds-muted); letter-spacing:.04em; text-align:left; }
.photo-group__title:first-of-type { margin-top:var(--ds-space-1); }
/* A slot the review is still waiting on. */
.photo-slot.is-missing .photo-slot__frame { border-style:dashed; border-color:var(--ds-red-100); }
/* Picked in the browser but not yet saved. Deliberately NOT the same as a stored
   photo: a solid accent edge plus a note, so a venue that closes the tab without
   saving is not left believing the upload went through. */
.photo-slot.is-pending .photo-slot__frame { border-color:var(--ds-primary); }
.photo-slot__pending { align-self:flex-start; font-size:12px; font-weight:700; color:var(--ds-primary-dark); }
