/* public/styles.css
   The Nichiwa brand system: warm paper, one serif voice, black/beige/red.
   Authoritative token values: docs/design/nichiwa-rebrand-2026-08/tokens/.

   CSP notes, all load-bearing:
   - No inline-data-URI url() backgrounds anywhere in this file. img-src allows
     that scheme for canvas/SVG barcode rendering only, not CSS backgrounds.
   - Fonts are SELF-HOSTED. style-src 'self' blocks the fonts.googleapis.com
     stylesheet and, with no font-src, default-src 'self' blocks the font files
     too — Google Fonts is unreachable here twice over. Regenerate the subsets
     with scripts/build-fonts.sh.
   - Inline style="" attributes are silently dropped, so every declaration must
     live in a class here. api/test/no-inline-style.test.js enforces it. */

@font-face {
  font-family: 'Source Serif 4';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/SourceSerif4-400.woff2') format('woff2');
}
@font-face {
  font-family: 'Source Serif 4';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/fonts/SourceSerif4-600.woff2') format('woff2');
}
/* Display face for the five SKU names and the 酒藏 cellar label ONLY. It is
   subset to exactly those glyphs, which is safe because they are our own fixed
   catalogue strings — never user- or POS-supplied text. */
@font-face {
  font-family: 'Nichiwa Brush';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/MaShanZheng-subset.woff2') format('woff2');
}

:root {
  --nichiwa-black: #000000;
  --nichiwa-red: #e60012;
  --nichiwa-beige: #dacaab;
  --nichiwa-gold: #b8963e; /* V0.2.31: black (ブラックゴールド) tier accent — brass, warmer than beige */
  --nichiwa-offwhite: #fcfbf5;
  --nichiwa-ink: #221714;
  --nichiwa-grey-50: #9fa0a0;

  --warm-100: #f5f1e7;
  --warm-200: #ebe3d2;
  --warm-300: #dacaab;
  --warm-400: #c2b191;
  --warm-500: #9a8a6d;
  --warm-600: #6e6252;
  --warm-700: #48403a;
  --warm-800: #2e2521;

  --red-600: #c4000f;
  --red-700: #9c000c;

  --text-primary: var(--nichiwa-ink);
  --text-secondary: var(--warm-600);
  --text-muted: var(--warm-500);
  --text-inverse: var(--nichiwa-offwhite);

  --surface-page: var(--nichiwa-offwhite);
  --surface-raised: #ffffff;
  --surface-sunken: var(--warm-100);
  --surface-beige: var(--nichiwa-beige);
  --surface-ink: var(--nichiwa-black);

  --border-hairline: rgba(35, 24, 21, 0.22);
  --border-subtle: rgba(35, 24, 21, 0.12);
  --border-inverse: rgba(252, 251, 245, 0.32);

  --focus-ring: var(--nichiwa-red);
  --state-disabled-fg: var(--warm-400);
  --state-disabled-bg: var(--warm-100);

  /* "Ink" easing: slow-fast-slow, like a brush stroke. Nothing bounces. */
  --ease-ink: cubic-bezier(0.65, 0, 0.35, 1);
  --duration-fast: 140ms;
  --duration-slow: 420ms;

  --tracking-caps: 0.18em;

  /* Latin resolves to Source Serif 4; CJK falls through to the platform
     mincho at zero bytes. A full CJK webfont would be megabytes, and these
     users are on phones at a register. */
  --font-body: 'Source Serif 4', 'Songti SC', 'Hiragino Mincho ProN',
    'Yu Mincho', 'Noto Serif CJK SC', 'Noto Serif SC', Georgia, serif;
  --font-brush: 'Nichiwa Brush', 'Songti SC', 'Hiragino Mincho ProN', serif;

  --nw-nav-height: 56px;
}

@media (prefers-reduced-motion: reduce) {
  :root {
    --duration-fast: 0ms;
    --duration-slow: 0ms;
  }
}

*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

/* The UA stylesheet's [hidden] { display: none } is an AUTHOR-beatable rule:
   any author `display` declaration wins over it regardless of specificity. This
   app toggles visibility with el.hidden all over (load-more, error banner, live
   row, gold perks), and several of those elements are display:block or :flex —
   so without this they never hide. The "加载更多" button was permanently
   visible on production for exactly this reason. */
[hidden] {
  display: none !important;
}

html,
body {
  height: 100%;
}

body {
  font-family: var(--font-body);
  background: var(--surface-page);
  color: var(--text-primary);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  padding-bottom: calc(var(--nw-nav-height) + 12px);
}

img {
  max-width: 100%;
  display: block;
}

button,
input,
select,
textarea {
  font: inherit;
  color: inherit;
}

a {
  color: inherit;
  text-decoration: none;
}

ul,
ol {
  list-style: none;
}

/* Page header: eyebrow / title / ink rule. Written by NW.renderPageHeader. */
.nw-page-header {
  padding: 24px 20px 0;
}

.nw-eyebrow {
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
  color: var(--text-secondary);
  margin-bottom: 6px;
}

.nw-page-title {
  font-size: 23px;
  font-weight: 600;
  color: var(--text-primary);
  padding-bottom: 10px;
  border-bottom: 1px solid var(--nichiwa-ink);
}

/* Bottom tab nav — text-only, no icons: the brand is type-led, and a word
   beats a pictogram for 会员卡 / 酒窖 / 首页. Active state is a 2px red rule
   across the TOP of the item, not a coloured label. */
.nw-bottom-nav {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  height: calc(var(--nw-nav-height) + env(safe-area-inset-bottom));
  padding-bottom: env(safe-area-inset-bottom);
  background: var(--surface-page);
  border-top: 1px solid var(--border-hairline);
  z-index: 100;
}

.nw-bottom-nav a,
.nw-bottom-nav button {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  font-size: 12px;
  color: var(--text-muted);
  background: none;
  border: none;
  border-top: 2px solid transparent;
  transition: color var(--duration-fast) var(--ease-ink);
}

.nw-bottom-nav .nw-nav-active {
  color: var(--text-primary);
  border-top-color: var(--nichiwa-red);
}

/* Latin sub-label under each tab. Brand ornament, identical in both locales,
   so it is hard-coded in the markup rather than routed through NW.t(). */
.nw-nav-latin {
  font-size: 8px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
}

/* Toast: black pill above the tab bar, auto-dismissed after 2.4s. */
.nw-toast {
  position: fixed;
  left: 50%;
  bottom: calc(var(--nw-nav-height) + env(safe-area-inset-bottom) + 20px);
  transform: translateX(-50%);
  background: var(--nichiwa-black);
  color: var(--text-inverse);
  padding: 10px 20px;
  border-radius: 999px;
  font-size: 13px;
  opacity: 0;
  pointer-events: none;
  z-index: 200;
  transition: opacity var(--duration-slow) var(--ease-ink);
}

.nw-toast-visible {
  opacity: 1;
}

/* Focus: a 2px red ring, the same accent as everything else. */
:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}

/* Version footer (populated by NW.injectFooter). */
footer#nw-footer {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 12px;
  padding: 12px;
  font-size: 12px;
  color: var(--text-secondary);
}

.nw-footer-version::before {
  content: 'v';
}

.nw-lang-toggle {
  background: none;
  border: 1px solid var(--border-hairline);
  border-radius: 4px;
  padding: 2px 8px;
  cursor: pointer;
}

.nw-lang-toggle:active {
  background: var(--surface-sunken);
}

/* ---------------------------------------------------------------------
   Task 11: login flow + card tab
   --------------------------------------------------------------------- */

/* Pages without a bottom nav (login) don't need the shell's reserved
   bottom padding. */
body.nw-no-nav {
  padding-bottom: 12px;
}

/* Buttons. State changes are COLOUR ONLY — no lift, no scale, no shadow.
   The brand is a print system; things do not hover off the page. */
.nw-btn-primary,
.nw-btn-secondary,
.nw-btn-link {
  display: block;
  width: 100%;
  font-size: 15px;
  font-weight: 600;
  text-align: center;
  cursor: pointer;
  transition:
    background-color var(--duration-fast) var(--ease-ink),
    color var(--duration-fast) var(--ease-ink),
    border-color var(--duration-fast) var(--ease-ink);
}

.nw-btn-primary {
  background: var(--nichiwa-red);
  color: var(--text-inverse);
  border: none;
  min-height: 48px;
  border-radius: 10px;
  padding: 12px 16px;
}

.nw-btn-primary:hover {
  background: var(--red-600);
}

.nw-btn-primary:active {
  background: var(--red-700);
}

.nw-btn-primary:disabled {
  background: var(--state-disabled-bg);
  color: var(--state-disabled-fg);
  cursor: not-allowed;
}

.nw-btn-secondary {
  background: transparent;
  color: var(--text-primary);
  border: 1px solid var(--border-hairline);
  min-height: 44px;
  border-radius: 8px;
  padding: 10px 16px;
}

.nw-btn-secondary:hover {
  background: var(--warm-100);
}

.nw-btn-secondary:disabled {
  color: var(--state-disabled-fg);
  cursor: not-allowed;
}

.nw-btn-link {
  background: none;
  border: none;
  color: var(--text-primary);
  font-weight: 400;
  text-decoration: underline;
  padding: 8px 0;
}

.nw-btn-link:hover {
  color: var(--nichiwa-red);
}

.nw-busy {
  opacity: 0.6;
}

.nw-field-label {
  display: block;
  font-size: 13px;
  color: var(--text-secondary);
  margin-bottom: 6px;
}

.nw-field-error {
  color: var(--nichiwa-red);
  font-size: 13px;
  margin: 8px 0;
}

/* The brand has three colours and no pink. An error reads as an error from the
   red rule and red text; a tinted fill would invent a fourth colour. */
.nw-error-banner {
  background: var(--surface-sunken);
  color: var(--nichiwa-red);
  border-left: 2px solid var(--nichiwa-red);
  border-radius: 10px;
  padding: 10px 14px;
  font-size: 14px;
  margin: 0 20px 12px;
}

/* --- Login page --- */

.nw-login-main {
  max-width: 420px;
  margin: 0 auto;
  padding: 40px 20px;
}

.nw-login-brand {
  display: flex;
  justify-content: center;
  margin-bottom: 32px;
}

.nw-login-logo {
  width: 120px;
  height: auto;
}

.nw-login-step h1 {
  font-size: 23px;
  font-weight: 600;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--nichiwa-ink);
  margin-bottom: 20px;
}

.nw-phone-row {
  display: flex;
  gap: 8px;
  margin-bottom: 4px;
}

.nw-country-select {
  min-height: 46px;
  border: 1px solid var(--border-hairline);
  border-radius: 8px;
  padding: 0 10px;
  background: var(--surface-raised);
}

.nw-phone-input,
.nw-otp-input,
.nw-claim-input {
  width: 100%;
  min-height: 46px;
  border: 1px solid var(--border-hairline);
  border-radius: 8px;
  padding: 0 12px;
  background: var(--surface-raised);
}

.nw-otp-sent-to {
  font-size: 13px;
  color: var(--text-secondary);
  margin-bottom: 16px;
  word-break: break-all;
}

.nw-login-step .nw-btn-primary {
  margin-top: 12px;
}

.nw-login-step .nw-btn-secondary {
  margin-top: 10px;
}

.nw-login-step .nw-btn-link {
  margin-top: 10px;
}

.nw-agree-terms {
  font-size: 11px;
  color: var(--text-muted);
  text-align: center;
  margin-top: 16px;
  line-height: 1.7;
}

.nw-privacy-inline-link {
  text-decoration: underline;
}

.nw-claim-divider {
  display: flex;
  align-items: center;
  gap: 12px;
  color: var(--text-muted);
  font-size: 12px;
  margin: 20px 0;
}

.nw-claim-divider::before,
.nw-claim-divider::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--border-subtle);
}

/* --- Card page --- */

.nw-page-header {
  padding: 16px;
}

.nw-page-header h1 {
  font-size: 18px;
}

.nw-card-main {
  max-width: 480px;
  margin: 0 auto;
  padding: 0 16px 16px;
}

/* Three tier treatments. The ground changes, and everything layered on it
   follows: logo colour (swapped in card.js), badge fill, secondary text. */
.nw-card {
  border-radius: 16px;
  padding: 22px;
  margin-bottom: 16px;
}

.nw-card-base {
  background: var(--surface-raised);
  border: 1px solid var(--border-hairline);
  color: var(--text-primary);
}

.nw-card-silver {
  background: var(--nichiwa-grey-50);
  border: 1px solid rgba(252, 251, 245, 0.35);
  color: var(--text-inverse);
}

.nw-card-gold {
  background: var(--nichiwa-black);
  border: 1px solid var(--nichiwa-beige);
  color: var(--text-inverse);
}

.nw-card-black {
  background: var(--nichiwa-black);
  border: 1px solid var(--nichiwa-gold);
  color: var(--text-inverse);
}

.nw-card-top {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: 20px;
}

.nw-card-logo {
  width: 56px;
  height: 56px;
}

.nw-card-tier {
  border-radius: 999px;
  padding: 4px 12px;
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
  white-space: nowrap;
}

.nw-card-base .nw-card-tier {
  border: 1px solid var(--border-hairline);
  color: var(--warm-600);
}

.nw-card-silver .nw-card-tier {
  background: var(--nichiwa-offwhite);
  color: var(--nichiwa-grey-50);
}

.nw-card-gold .nw-card-tier {
  background: var(--nichiwa-beige);
  color: var(--nichiwa-ink);
}

.nw-card-black .nw-card-tier {
  background: var(--nichiwa-gold);
  color: var(--nichiwa-black);
}

.nw-card-holder {
  font-size: 17px;
  font-weight: 600;
  margin-bottom: 4px;
}

.nw-card-member-row {
  font-size: 13px;
  margin-bottom: 14px;
}

.nw-card-base .nw-card-member-row,
.nw-card-base .nw-card-live {
  color: var(--warm-600);
}

.nw-card-silver .nw-card-member-row,
.nw-card-silver .nw-card-live {
  color: rgba(252, 251, 245, 0.85);
}

.nw-card-gold .nw-card-member-row,
.nw-card-gold .nw-card-live,
.nw-card-black .nw-card-member-row,
.nw-card-black .nw-card-live {
  color: rgba(252, 251, 245, 0.62);
}

.nw-card-member-number {
  margin-left: 6px;
  letter-spacing: 0.14em;
}

/* Live row + pulse. See the anti-screenshot note in card.js. */
.nw-card-live {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 11px;
  margin-bottom: 12px;
}

.nw-card-live-left {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.nw-card-clock {
  font-variant-numeric: tabular-nums;
}

.nw-card-pulse {
  width: 7px;
  height: 7px;
  border-radius: 999px;
  background: var(--nichiwa-red);
  animation: nw-pulse 1.6s var(--ease-ink) infinite;
}

@keyframes nw-pulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.25;
  }
}

/* Top-tier perks line (gold + black cards), under a beige divider. Named
   -perks, not -gold: the tier treatment already owns .nw-card-gold and the
   two would collide. */
.nw-card-perks {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid rgba(218, 202, 171, 0.45);
  display: flex;
  gap: 10px;
  align-items: baseline;
  flex-wrap: wrap;
}

.nw-card-perks-label {
  color: var(--nichiwa-beige);
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
}

.nw-card-perks-text {
  color: rgba(252, 251, 245, 0.62);
  font-size: 11px;
}

.nw-card-barcode-wrap {
  min-height: 110px;
}

.nw-barcode-block {
  position: relative;
  overflow: hidden;
  background: #ffffff;
  border-radius: 10px;
  padding: 10px;
  display: flex;
  justify-content: center;
}

/* The scan line. A sibling of the canvas, never painted into it. */
.nw-barcode-sweep {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 2px;
  opacity: 0.65;
  background: var(--nichiwa-red);
  animation: nw-sweep 3.4s var(--ease-ink) infinite;
}

@keyframes nw-sweep {
  0% {
    left: 4%;
  }
  50% {
    left: 96%;
  }
  100% {
    left: 4%;
  }
}

.nw-barcode-block canvas {
  max-width: 100%;
  height: auto;
}

.nw-card-qr {
  display: block;
  margin: 0 auto;
  width: min(180px, 60vw);
  height: auto;
  image-rendering: pixelated; /* crisp modules when CSS scales the canvas */
}

.nw-barcode-pending {
  background: var(--border-hairline);
  min-height: 94px;
  align-items: center;
  color: var(--text-secondary);
  text-align: center;
}

.nw-pending-msg {
  font-size: 13px;
  padding: 8px;
}

.nw-claim-prompt {
  background: rgba(255, 255, 255, 0.08);
  border: 1px dashed var(--border-hairline);
  border-radius: 8px;
  padding: 16px;
  text-align: center;
}

.nw-claim-prompt-title {
  font-weight: 700;
  margin-bottom: 6px;
}

.nw-claim-prompt-body {
  font-size: 13px;
  color: var(--border-hairline);
  margin-bottom: 12px;
}

.nw-claim-prompt-cta {
  display: inline-block;
  width: auto;
  padding: 10px 20px;
}

.nw-card-scan-hint {
  margin-top: 12px;
  font-size: 12px;
  color: var(--border-hairline);
  text-align: center;
}

.nw-points-summary {
  background: var(--surface-raised);
  border-radius: 14px;
  padding: 20px;
  margin-bottom: 16px;
  text-align: center;
}

.nw-points-balance-label {
  font-size: 11px;
  color: var(--text-secondary);
}

.nw-points-balance {
  font-size: 39px;
  font-weight: 600;
  color: var(--nichiwa-red);
  line-height: 1.12;
}

.nw-points-rate-line {
  font-size: 13px;
  color: var(--text-secondary);
  margin-bottom: 8px;
}

/* Tier progress (V0.2.19). Fill width is set via CSSOM from card.js. */
.nw-tier-progress {
  margin-bottom: 8px;
}

.nw-tier-progress-track {
  height: 6px;
  border-radius: 3px;
  background: var(--warm-200);
  overflow: hidden;
}

.nw-tier-progress-fill {
  height: 100%;
  width: 0;
  border-radius: 3px;
  background: var(--nichiwa-ink);
  transition: width 400ms ease;
}

.nw-tier-progress-label {
  margin-top: 6px;
  font-size: 13px;
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
}

.nw-points-timing {
  font-size: 13px;
  color: var(--text-muted);
  margin-bottom: 4px;
}

.nw-points-expiry {
  font-size: 13px;
  color: var(--text-secondary);
}

.nw-points-expiry-risk {
  color: var(--nichiwa-red);
  font-weight: 600;
}

.nw-history {
  background: var(--surface-raised);
  border-radius: 14px;
  padding: 16px;
}

.nw-history h2 {
  font-size: 15px;
  font-weight: 600;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--nichiwa-ink);
  margin-bottom: 4px;
}

.nw-history-list {
  display: flex;
  flex-direction: column;
}

.nw-history-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 0;
  border-bottom: 1px solid var(--border-subtle);
}

.nw-history-item:last-child {
  border-bottom: none;
}

.nw-history-main {
  flex: 1 1 auto;
  min-width: 0;
}

.nw-history-type {
  font-size: 14px;
}

.nw-history-date {
  font-size: 12px;
  color: var(--text-secondary);
}

.nw-history-points {
  font-size: 15px;
  font-variant-numeric: tabular-nums;
  margin-left: auto;
}

.nw-history-points-pos {
  color: var(--text-primary);
}

.nw-history-points-neg {
  color: var(--text-muted);
}

.nw-history-empty {
  font-size: 13px;
  color: var(--text-secondary);
  text-align: center;
  padding: 12px 0;
}

.nw-history-load-more {
  margin-top: 12px;
}

/* ---------------------------------------------------------------------
   Task 12: cellar tab, home tab, staff admin
   --------------------------------------------------------------------- */

/* --- Cellar page --- */

.nw-cellar-main {
  max-width: 480px;
  margin: 0 auto;
  padding: 0 16px 16px;
}

.nw-cellar-loading,
.nw-cellar-empty {
  text-align: center;
  color: var(--text-secondary);
  font-size: 13px;
  padding: 24px 0;
}

.nw-cellar-unlinked {
  background: var(--surface-raised);
  border: 1px dashed var(--border-hairline);
  border-radius: 14px;
  padding: 20px;
  text-align: center;
}

/* .nw-claim-prompt-body's colour was authored for the dark .nw-card background
   it originally shipped inside (card.js) — reusing the class here, on a light
   box, needs a readable override rather than a near-invisible pairing. */
.nw-cellar-unlinked .nw-claim-prompt-body {
  color: var(--text-secondary);
}

.nw-cellar-list {
  display: flex;
  flex-direction: column;
}

.nw-cellar-month {
  display: flex;
  align-items: baseline;
  gap: 12px;
  padding: 24px 20px 6px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: var(--tracking-caps);
  color: var(--text-secondary);
}

.nw-cellar-month:first-child {
  margin-top: 0;
}

.nw-cellar-line-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.nw-cellar-line-item {
  border-bottom: 1px solid var(--border-subtle);
}

.nw-cellar-line-row {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  padding: 14px 20px;
  cursor: pointer;
}

.nw-cellar-line-main {
  flex: 1;
  min-width: 0;
}

.nw-cellar-line-name-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 4px;
}

.nw-cellar-line-name {
  font-size: 15px;
  font-weight: 600;
  color: var(--text-primary);
}

.nw-cellar-vintage-badge {
  border-radius: 999px;
  border: 1px solid var(--border-hairline);
  padding: 1px 8px;
  font-size: 10px;
  margin-left: 8px;
  color: var(--text-secondary);
}

.nw-cellar-line-meta {
  font-size: 11px;
  color: var(--text-muted);
  margin-top: 4px;
}

.nw-cellar-line-amount {
  font-size: 15px;
  color: var(--text-primary);
  text-align: right;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.nw-cellar-detail {
  padding: 0 14px 14px;
  border-top: 1px solid var(--surface-sunken);
}

.nw-cellar-reactions {
  display: flex;
  gap: 8px;
  margin-top: 12px;
}

.nw-reaction-chip {
  flex: 1 1 0;
  border: 1px solid var(--border-hairline);
  border-radius: 999px;
  padding: 8px 4px;
  font-size: 12px;
  background: var(--surface-raised);
  cursor: pointer;
}

.nw-cellar-detail-error {
  margin-top: 8px;
}

.nw-cellar-note-wrap {
  margin-top: 12px;
}

.nw-cellar-note {
  width: 100%;
  min-height: 72px;
  border: 1px solid var(--border-hairline);
  border-radius: 6px;
  padding: 10px;
  font-size: 14px;
  resize: vertical;
}

.nw-cellar-note-actions {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 8px;
}

.nw-cellar-note-save {
  width: auto;
  padding: 8px 20px;
}

.nw-cellar-note-saved {
  font-size: 12px;
  color: var(--text-secondary);
}

/* --- Home page --- */

.nw-home-main {
  max-width: 480px;
  margin: 0 auto;
  padding: 0 16px 16px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.nw-home-welcome p {
  font-size: 16px;
  font-weight: 600;
}

.nw-home-nudge {
  background: #fdecea;
  color: var(--nichiwa-red);
  border-radius: 10px;
  padding: 12px 14px;
  font-size: 13px;
}

.nw-home-tier-explainer,
.nw-home-store-info {
  background: var(--surface-raised);
  border-radius: 14px;
  padding: 16px;
}

.nw-home-tier-explainer h2,
.nw-home-store-info h2 {
  font-size: 15px;
  margin-bottom: 10px;
}

.nw-home-tier-list li {
  font-size: 13px;
  color: var(--text-secondary);
  padding: 4px 0;
}

.nw-home-store-info p {
  font-size: 13px;
  color: var(--text-secondary);
}

.nw-home-links {
  display: flex;
  gap: 10px;
}

.nw-home-links a {
  width: auto;
  flex: 1 1 0;
}

/* --- Staff admin page --- */

.nw-admin-main {
  max-width: 720px;
  margin: 0 auto;
  padding: 0 16px 32px;
  display: flex;
  flex-direction: column;
  gap: 24px;
}

.nw-admin-section {
  background: var(--surface-raised);
  border-radius: 14px;
  padding: 16px;
}

.nw-admin-section h2 {
  font-size: 16px;
  margin-bottom: 12px;
}

.nw-admin-section h3 {
  font-size: 14px;
  margin: 16px 0 10px;
}

.nw-admin-search-row {
  display: flex;
  gap: 8px;
  margin-bottom: 12px;
}

.nw-admin-search-input {
  flex: 1 1 auto;
  border: 1px solid var(--border-hairline);
  border-radius: 6px;
  padding: 10px 12px;
  font-size: 14px;
}

.nw-admin-search-btn {
  width: auto;
  padding: 10px 20px;
}

.nw-admin-empty {
  font-size: 13px;
  color: var(--text-secondary);
  padding: 8px 0;
}

.nw-admin-success {
  font-size: 13px;
  color: var(--nichiwa-red);
  margin: 8px 0;
}

.nw-admin-table-wrap {
  overflow-x: auto;
}

.nw-admin-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}

.nw-admin-table th,
.nw-admin-table td {
  text-align: left;
  padding: 8px 10px;
  border-bottom: 1px solid var(--surface-sunken);
  white-space: nowrap;
}

.nw-admin-table th {
  color: var(--text-secondary);
  font-weight: 600;
}

.nw-admin-row-clickable {
  cursor: pointer;
}

.nw-admin-row-clickable:active {
  background: var(--surface-sunken);
}

.nw-admin-detail-balance-line {
  font-size: 14px;
  margin-bottom: 4px;
}

.nw-admin-detail-balance {
  font-weight: 700;
  color: var(--nichiwa-red);
  margin-left: 6px;
}

.nw-admin-pending-actions {
  display: flex;
  gap: 8px;
}

.nw-admin-approve-btn,
.nw-admin-reject-btn {
  width: auto;
  padding: 6px 14px;
  font-size: 12px;
}

#nw-admin-export-link {
  width: auto;
  display: inline-block;
  padding: 10px 20px;
}

/* ---------------------------------------------------------------------
   Task 13: privacy policy page (public/privacy.html) — standalone, no
   NW shell (no login, no /api/* calls). Both zh and ja sections are
   always in the DOM; privacy.js toggles which one is visible via
   .nw-privacy-hidden.
   --------------------------------------------------------------------- */

.nw-privacy-main {
  max-width: 640px;
  margin: 0 auto;
  padding: 24px 20px 48px;
  line-height: 1.7;
}

.nw-privacy-lang-row {
  display: flex;
  justify-content: center;
  gap: 8px;
  margin-bottom: 24px;
}

.nw-privacy-lang-btn {
  background: none;
  border: 1px solid var(--border-hairline);
  border-radius: 4px;
  padding: 4px 12px;
  font-size: 13px;
  cursor: pointer;
}

.nw-privacy-lang-active {
  border-color: var(--nichiwa-red);
  color: var(--nichiwa-red);
}

.nw-privacy-hidden {
  display: none;
}

.nw-privacy-section h1 {
  font-size: 20px;
  margin-bottom: 12px;
}

.nw-privacy-section h2 {
  font-size: 15px;
  margin: 20px 0 6px;
}

.nw-privacy-section p {
  font-size: 14px;
  margin-bottom: 8px;
}

.nw-privacy-section ol {
  list-style: decimal;
  margin: 6px 0 10px 20px;
}

.nw-privacy-section ol li {
  font-size: 14px;
  margin-bottom: 4px;
}

.nw-privacy-meta {
  margin-top: 24px;
  color: var(--text-secondary);
  font-size: 13px;
}

.nw-privacy-back {
  margin-top: 24px;
  text-align: center;
}

.nw-privacy-back a {
  text-decoration: underline;
  font-size: 14px;
}

/* Environment badge — deliberately loud. This is the only visual difference
   between staging and production, and it guards a one-way door. */
.nw-env-badge {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 9999;
  padding: 6px 12px;
  background: #B8860B;
  color: #fff;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-align: center;
  pointer-events: none;
  padding-top: calc(6px + env(safe-area-inset-top));
}

.nw-login-step .nw-btn-primary,
.nw-login-step .nw-btn-secondary,
.nw-login-step .nw-btn-link {
  margin-top: 12px;
}

/* ---- Cellar ---------------------------------------------------------- */

/* Vertical 酒藏 / CELLAR mark, top right. Brush face for the kanji — one of
   the only two places the display face is used. */
.nw-cellar-main {
  position: relative;
}

.nw-cellar-vertical {
  position: absolute;
  top: 4px;
  right: 20px;
  writing-mode: vertical-rl;
  text-align: right;
  display: flex;
  align-items: center;
  gap: 8px;
  pointer-events: none;
}

.nw-cellar-vertical-zh {
  font-family: var(--font-brush);
  font-size: 19px;
  color: var(--text-primary);
}

.nw-cellar-vertical-latin {
  font-size: 9px;
  font-weight: 600;
  letter-spacing: var(--tracking-caps);
  color: var(--text-muted);
}

/* Bottle photo slot. Empty state until the product service fills it. */
.nw-cellar-photo {
  width: 56px;
  height: 74px;
  flex: none;
  border-radius: 10px;
  background: var(--warm-100);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

.nw-cellar-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.nw-cellar-photo-empty {
  font-size: 9px;
  line-height: 1.3;
  color: var(--warm-500);
  text-align: center;
  padding: 0 4px;
}

/* Reaction chips. Selected is this view's ONE accent. */
.nw-reaction-chip {
  border-radius: 999px;
  border: 1px solid var(--border-hairline);
  background: transparent;
  color: var(--text-primary);
  padding: 6px 14px;
  font-size: 12px;
  cursor: pointer;
  transition:
    background-color var(--duration-fast) var(--ease-ink),
    border-color var(--duration-fast) var(--ease-ink),
    color var(--duration-fast) var(--ease-ink);
}

/* cellar.js sets BOTH a class and aria-pressed on the selected chip. Match
   both, so neither is left as a silently-dead selector for the next reader. */
.nw-reaction-chip-active,
.nw-reaction-chip[aria-pressed='true'] {
  background: var(--nichiwa-red);
  border-color: var(--nichiwa-red);
  color: var(--text-inverse);
}

.nw-cellar-note {
  width: 100%;
  min-height: 84px;
  border: 1px solid var(--border-hairline);
  border-radius: 8px;
  padding: 10px;
  background: var(--surface-raised);
  line-height: 1.7;
}

/* ---- Home ------------------------------------------------------------ */

.nw-home-welcome {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  padding: 20px 20px 0;
  font-size: 17px;
}

/* Home's ONE red element. Everything else on this page is ink and beige. */
.nw-home-logo {
  width: 40px;
  height: 40px;
  flex: none;
}

/* Expiry nudge: a red outline, not a red fill — red is an accent here,
   never a background for body copy. */
.nw-home-nudge {
  margin: 20px;
  padding: 12px 16px;
  border: 1px solid var(--nichiwa-red);
  border-radius: 12px;
  font-size: 13px;
  color: var(--text-primary);
}

/* Tier plate: beige ground, the ladder ruled off beneath it. */
.nw-home-tier-explainer {
  margin: 20px;
  padding: 22px;
  border-radius: 14px;
  background: var(--surface-beige);
  color: var(--nichiwa-ink);
}

.nw-home-tier-explainer h2 {
  font-size: 15px;
  font-weight: 600;
}

.nw-home-tier-list {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid rgba(35, 24, 21, 0.22);
}

.nw-home-tier-list li {
  font-size: 13px;
  padding: 4px 0;
  color: var(--warm-700);
}

/* Principal brands. Centred heading between vertical bracket ornaments. */
.nw-home-brands {
  padding: 24px 20px 0;
}

.nw-home-brands h2 {
  text-align: center;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: var(--tracking-caps);
  color: var(--text-secondary);
}

.nw-home-brands h2::before {
  content: '︿ ';
}

.nw-home-brands h2::after {
  content: ' ﹀';
}

.nw-home-brand-row {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 14px 0;
  border-bottom: 1px solid var(--border-subtle);
}

/* Brush display face — the other of only two places it is used. */
.nw-home-brand-name {
  font-family: var(--font-brush);
  font-size: 19px;
  color: var(--text-primary);
}

/* Deliberate uniform fallback: the API tells the client when a hero's
   DisplayNameZh contains a glyph the brush subset doesn't cover
   (zhNameFullyCovered, api/src/functions/product.js) — a curator can now
   rename a hero through the Logistics admin without ever touching
   sku-catalogue.json or the font build. Swapping the WHOLE name to the serif
   body face reads as an intentional design choice; letting the browser fall
   back per-glyph reads as a broken font (uneven stroke weight mid-word).
   Combined selectors so this wins on specificity without !important. */
.nw-home-brand-name.nw-brush-fallback,
.nw-product-name.nw-brush-fallback {
  font-family: var(--font-body);
}

.nw-home-brand-latin {
  font-size: 9px;
  font-weight: 600;
  letter-spacing: var(--tracking-caps);
  color: var(--text-muted);
  margin-left: auto;
}

/* Store row. */
.nw-home-store-info {
  margin: 24px 20px;
  padding: 14px 16px;
  border: 1px solid var(--border-hairline);
  border-radius: 12px;
}

.nw-home-store-info h2 {
  font-size: 13px;
  font-weight: 600;
  margin-bottom: 4px;
}

.nw-home-store-info p {
  font-size: 13px;
  color: var(--text-secondary);
  line-height: 1.7;
}

.nw-home-links {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 0 20px 24px;
}

/* ---- Staff admin (desktop) -------------------------------------------- */

.nw-admin-main {
  max-width: 1120px;
  margin: 0 auto;
  padding: 24px 20px 48px;
}

/* One bordered panel holds the whole console. */
.nw-admin-panel {
  border: 1px solid var(--border-hairline);
  border-radius: 14px;
  background: var(--surface-raised);
  padding: 28px;
}

/* Tab navigation (V0.2.41). Visibility is scoped off <main data-nw-tab> so
   each section's own `hidden` attribute keeps meaning what it always meant
   (the detail panel shows only once a member is selected). */
.nw-admin-tabs {
  position: sticky;
  top: 0;
  z-index: 30;
  display: flex;
  gap: 8px;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  background: var(--nichiwa-offwhite);
  border-bottom: 1px solid var(--border-hairline);
  margin-bottom: 20px;
  padding: 10px 2px;
}

/* Chip BUTTONS, not underlined text — Isaac's 8/16 device feedback: the
   underline tabs were finger-hostile. Bordered surface, filled when
   active; at phone width the row WRAPS so all five stay visible (a tab
   hidden behind sideways scroll is a tab staff forget exists). */
.nw-admin-tab {
  appearance: none;
  background: var(--surface-raised);
  border: 1px solid var(--border-hairline);
  border-radius: 10px;
  padding: 10px 16px;
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: var(--tracking-caps);
  color: var(--text-secondary);
  white-space: nowrap;
  cursor: pointer;
}

.nw-admin-tab[aria-current="true"] {
  background: var(--nichiwa-ink);
  border-color: var(--nichiwa-ink);
  color: var(--nichiwa-offwhite);
}

.nw-admin-tab-badge {
  display: inline-block;
  min-width: 18px;
  margin-left: 6px;
  padding: 1px 5px;
  border-radius: 9px;
  background: var(--nichiwa-red);
  color: #fff;
  font-size: 11px;
  text-align: center;
}

[data-nw-tab="members"] .nw-admin-section:not([data-tab="members"]),
[data-nw-tab="receipt"] .nw-admin-section:not([data-tab="receipt"]),
[data-nw-tab="pending"] .nw-admin-section:not([data-tab="pending"]),
[data-nw-tab="reports"] .nw-admin-section:not([data-tab="reports"]),
[data-nw-tab="export"] .nw-admin-section:not([data-tab="export"]) {
  display: none !important;
}

/* Separator between sections of the SAME tab only — the old any-adjacent
   rule would border against display:none siblings from other tabs. The
   contiguity this relies on is test-pinned (admin-tabs.test.js). */
.nw-admin-section[data-tab="members"] + .nw-admin-section[data-tab="members"],
.nw-admin-section[data-tab="reports"] + .nw-admin-section[data-tab="reports"] {
  margin-top: 36px;
  padding-top: 28px;
  border-top: 1px solid var(--border-subtle);
}

/* Brand-numbered sections. The number comes off the section's data-num
   attribute, so admin.js keeps setting heading text freely and never has to
   know about numbering. */
.nw-admin-section[data-num]::before {
  content: attr(data-num);
  display: block;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: var(--tracking-caps);
  color: var(--text-secondary);
  margin-bottom: 6px;
}

.nw-admin-section h2 {
  font-size: 17px;
  font-weight: 600;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--nichiwa-ink);
  margin-bottom: 16px;
}

.nw-admin-section h3 {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--text-secondary);
  margin: 24px 0 10px;
}

.nw-admin-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}

.nw-admin-table th {
  text-align: left;
  font-weight: 600;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--nichiwa-ink);
  white-space: nowrap;
}

.nw-admin-table td {
  padding: 12px 0;
  border-bottom: 1px solid var(--border-subtle);
}

.nw-admin-table-wrap {
  overflow-x: auto;
}

/* Balances and point columns line up on the decimal. */
.nw-admin-detail-balance,
.nw-admin-table td.nw-admin-num {
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* 待審核 is the one place red appears in the console. */
.nw-admin-status-pending {
  color: var(--nichiwa-red);
}

/* Pending-queue review context (V0.2.16). The brand system has no green:
   'match' is affirmative ink, warnings are the one red. */
.nw-admin-phone-badge {
  font-weight: 600;
  white-space: nowrap;
}

.nw-admin-phone-mismatch {
  color: var(--nichiwa-red);
}

.nw-admin-phone-none {
  color: var(--text-muted);
  font-weight: 400;
}

.nw-admin-pending-flag {
  display: block;
  color: var(--nichiwa-red);
  font-weight: 600;
  font-size: 12px;
  white-space: nowrap;
}

.nw-admin-pending-kana {
  display: block;
  font-size: 11px;
  color: var(--text-muted);
}

.nw-admin-pending-warning {
  font-size: 13px;
  color: var(--nichiwa-red);
  padding: 12px 0;
}

/* Provisional cellar lines (V0.2.23): fresh receipt-credited purchases,
   replaced by authoritative rows when the slip syncs. */
.nw-cellar-provisional-chip {
  font-size: 11px;
  color: var(--text-muted);
  border: 1px solid var(--border-hairline);
  border-radius: 3px;
  padding: 1px 5px;
  white-space: nowrap;
}

/* レシート即時付与 (V0.2.23): mobile-first — this section is used on the
   store iPad/phone at the till. */
.nw-admin-receipt-actions {
  display: flex;
  gap: 10px;
  margin-bottom: 12px;
}

.nw-admin-receipt-form {
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-width: 420px;
}

.nw-admin-receipt-form .nw-field-label {
  margin-top: 8px;
}

.nw-admin-receipt-member-name {
  font-size: 13px;
  font-weight: 600;
  min-height: 1.2em;
}

.nw-admin-receipt-member-missing {
  color: var(--nichiwa-red);
}

.nw-admin-receipt-lines {
  list-style: none;
  font-size: 13px;
  color: var(--text-secondary);
  padding: 4px 0;
}

/* Unlink (V0.2.19): armed state = the button IS the confirmation. */
.nw-admin-unlink {
  margin-top: 16px;
}

.nw-admin-unlink-armed {
  color: var(--nichiwa-red);
  border-color: var(--nichiwa-red);
  font-weight: 600;
}

/* Redeem confirmation modal (V0.2.27) — replaces the two-click armed red
   bar (Isaac's complaint: the armed state read as red text on a red-tinted
   border, illegible). The two buttons below reuse .nw-btn-primary /
   .nw-btn-secondary UNCHANGED — that alone fixes the contrast problem;
   nothing new is invented for them. Card styling matches the house
   .nw-admin-panel/.nw-admin-section convention (surface-raised,
   border-hairline, 14px radius) rather than the brief's literal 8px/
   --nw-paper skeleton, which don't match this file's real tokens. The
   overlay's shadow is a static (non-hover) elevation cue separating the
   dialog from the page behind it — not a state-change animation, so it
   doesn't conflict with the "no lift, no scale, no shadow" rule that
   governs button hover/active states above. */
.nw-modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 100;
  padding: 24px;
}

.nw-modal {
  background: var(--surface-raised);
  border: 1px solid var(--border-hairline);
  border-radius: 14px;
  padding: 24px;
  max-width: 360px;
  width: 100%;
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.25);
}

.nw-modal h3 {
  font-size: 16px;
  margin-bottom: 12px;
}

.nw-modal-member {
  color: var(--text-secondary);
  font-size: 14px;
  margin-bottom: 4px;
}

.nw-modal-amount {
  font-size: 1.5rem;
  font-weight: 600;
  margin-bottom: 16px;
}

#nw-redeem-modal-confirm {
  margin-top: 20px;
}

#nw-redeem-modal-cancel {
  margin-top: 10px;
}

.nw-redeem-tiers {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0.5rem 0;
}
.nw-redeem-tier-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: 7.5rem;
  gap: 0.15rem;
}
.nw-redeem-tier-btn:disabled {
  opacity: 0.55;
}
.nw-redeem-tier-yen {
  font-size: 1.15rem;
  font-weight: 600;
}
.nw-redeem-tier-points,
.nw-redeem-tier-short {
  font-size: 0.8rem;
}
.nw-redeem-modal-yen {
  font-size: 1.8rem;
  font-weight: 600;
  text-align: center;
  margin: 0.5rem 0 0.25rem;
}
.nw-redeem-qr-wrap {
  display: flex;
  justify-content: center;
  margin: 0.75rem 0;
}
/* V0.2.29 review fix (Important 6): symbology-specific classes, not a
   shared #id rule — a Code128 render (~218×86) under the QR-only
   pixelated-square sizing below would come out mangled. showRedeemResult
   sets the class per REDEEM_CODE_SYMBOLOGY. */
.nw-redeem-code-qr {
  display: block;
  width: min(220px, 70vw);
  height: auto;
  image-rendering: pixelated;
}
.nw-redeem-code-128 {
  display: block;
  width: auto;
  max-width: 100%;
  height: auto;
}
.nw-redeem-instruction {
  text-align: center;
}

.nw-admin-scan-btn {
  margin-top: 0.5rem;
}
.nw-scan-modal {
  max-width: 30rem;
}
#nw-admin-scan-video {
  width: 100%;
  max-height: 60vh;
  background: #111111;
  border-radius: 4px;
}

.nw-admin-search-row {
  display: flex;
  gap: 10px;
  margin-bottom: 16px;
}

.nw-admin-search-input {
  flex: 1;
  min-height: 44px;
  border: 1px solid var(--border-hairline);
  border-radius: 8px;
  padding: 0 12px;
  background: var(--surface-raised);
}

.nw-admin-search-btn {
  width: auto;
  min-width: 120px;
}

.nw-admin-empty {
  font-size: 13px;
  color: var(--text-muted);
  padding: 12px 0;
}

.nw-admin-success {
  font-size: 13px;
  color: var(--text-primary);
  padding: 8px 0;
}

/* The admin console is desktop-first but must not break on a phone. */
/* ---- iPhone pass (V0.2.42) — staff work this console on phones. ------- */

/* iOS Safari zooms the whole page on focusing any control under 16px;
   admin-wide floor, all widths (a slightly larger desktop input is fine,
   a zoom-jumping register flow is not). */
.nw-admin-main input,
.nw-admin-main textarea,
.nw-admin-main select {
  font-size: 16px;
}

/* 44px tap floor on real action buttons (never .nw-btn-link — inline). */
.nw-admin-main .nw-btn-primary,
.nw-admin-main .nw-btn-secondary,
.nw-admin-tab {
  min-height: 44px;
}

/* Notch / rounded-corner clearance for the sticky bar (viewport-fit=cover). */
.nw-admin-tabs {
  padding-left: max(2px, env(safe-area-inset-left));
  padding-right: max(2px, env(safe-area-inset-right));
}

@media (max-width: 640px) {
  /* Full-bleed console: the floating bordered panel is desk furniture. */
  .nw-admin-main {
    padding: 0 0 calc(32px + env(safe-area-inset-bottom));
  }
  .nw-admin-panel {
    border-left: none;
    border-right: none;
    border-radius: 0;
    padding: 16px 14px calc(24px + env(safe-area-inset-bottom));
  }
  .nw-admin-tabs {
    margin-bottom: 12px;
    flex-wrap: wrap;
    overflow-x: visible;
  }

  .nw-admin-search-row {
    flex-direction: column;
  }
  .nw-admin-search-btn {
    width: 100%;
  }

  /* Row-record tables become labeled cards — the label is data-label,
     written at render time from the SAME header element the desktop table
     shows, so no column label ever has a second spelling. The two compact
     numeric matrices (acq daily/coupon) stay tables and get the compact
     rules below instead. */
  .nw-admin-cardable,
  .nw-admin-cardable tbody,
  .nw-admin-cardable tr,
  .nw-admin-cardable td {
    display: block;
  }
  .nw-admin-cardable thead {
    display: none;
  }
  .nw-admin-cardable tr {
    border: 1px solid var(--border-subtle);
    border-radius: 10px;
    padding: 10px 14px;
    background: var(--surface-raised);
  }
  .nw-admin-cardable tr + tr {
    margin-top: 10px;
  }
  .nw-admin-cardable td {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 12px;
    padding: 4px 0;
    border-bottom: none;
    white-space: normal;
  }
  .nw-admin-cardable td::before {
    content: attr(data-label);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: var(--tracking-caps);
    color: var(--text-secondary);
    flex-shrink: 0;
  }
  .nw-admin-cardable td.nw-admin-num {
    text-align: right;
  }
  /* A label-less cell is an actions cell: stack its buttons full width. */
  .nw-admin-cardable td:not([data-label]) {
    display: block;
    padding-top: 8px;
  }
  .nw-admin-cardable td:not([data-label]) button {
    display: block;
    width: 100%;
    margin: 8px 0 0;
  }

  /* Compact fit for the two numeric matrices — they fit an iPhone width
     as tables; card-splitting a 14-day trend would kill column scanning. */
  #nw-admin-acq-daily-table th,
  #nw-admin-acq-daily-table td,
  #nw-admin-acq-redeem-table th,
  #nw-admin-acq-redeem-table td {
    font-size: 12px;
    padding: 6px 4px;
  }
}

/* ---- Store page ------------------------------------------------------- */
.nw-store-main { padding: 0 20px 24px; }

.nw-back-link {
  display: inline-block;
  font-size: 13px;
  color: var(--text-primary);
  text-decoration: underline;
  padding: 16px 0;
}
.nw-back-link::before { content: '← '; }
.nw-back-link:hover { color: var(--nichiwa-red); }

.nw-store-map {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  height: 150px;
  border-radius: 12px;
  background: var(--warm-100);
  border: 1px solid var(--border-subtle);
  margin-bottom: 24px;
}
.nw-store-map-label { font-size: 11px; color: var(--warm-500); }
.nw-store-map-cta {
  font-size: 11px; font-weight: 600; text-transform: uppercase;
  letter-spacing: var(--tracking-caps); color: var(--nichiwa-red);
}

.nw-store-name {
  font-size: 19px; font-weight: 600;
  padding-bottom: 8px; border-bottom: 1px solid var(--nichiwa-ink);
}
.nw-store-rows { margin-top: 14px; }
.nw-store-rows dt {
  font-size: 10px; font-weight: 600; text-transform: uppercase;
  letter-spacing: var(--tracking-caps); color: var(--text-secondary); margin-top: 14px;
}
.nw-store-rows dd { font-size: 15px; margin-top: 2px; }

.nw-store-company {
  margin-top: 28px; padding-top: 20px;
  border-top: 1px solid var(--border-subtle);
  font-size: 13px; color: var(--text-secondary); line-height: 1.75;
}
.nw-store-company-name { font-weight: 600; color: var(--text-primary); }
.nw-store-company a { text-decoration: underline; }
.nw-store-company a:hover { color: var(--nichiwa-red); }

/* The home store row is now a link to /store.html. The chevron rides the
   TITLE line rather than floating: a floated ::after on a block whose
   children are blocks lands BELOW the paragraph, which reads as a stray
   glyph instead of an affordance. Same convention as .nw-home-brands h2. */
.nw-home-store-link { display: block; }
.nw-home-store-link h2 {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
}
.nw-home-store-link h2::after { content: '›'; color: var(--nichiwa-red); }

/* ---- Product (SKU) page ----------------------------------------------- */
.nw-product-main { padding: 0 20px 24px; }

.nw-product-hero { margin-bottom: 18px; }
.nw-product-eyebrow {
  font-size: 10px; font-weight: 600; text-transform: uppercase;
  letter-spacing: var(--tracking-caps); color: var(--text-secondary);
}
/* The brush face is subset to exactly sku-catalogue.json's `zh` glyphs, which
   is what this element renders — so coverage is guaranteed by
   api/test/brush-coverage.test.js. Do not point it at any other string. */
.nw-product-name {
  font-family: var(--font-brush);
  font-size: 31px;
  line-height: 1.25;
  color: var(--text-primary);
  margin-top: 4px;
}
.nw-product-latin {
  font-size: 10px; font-weight: 600; text-transform: uppercase;
  letter-spacing: var(--tracking-caps); color: var(--text-muted);
  margin-top: 6px;
  padding-bottom: 10px; border-bottom: 1px solid var(--nichiwa-ink);
}

.nw-product-photo {
  display: flex; align-items: center; justify-content: center;
  height: 260px; overflow: hidden;
  border-radius: 12px;
  background: var(--warm-100);
  border: 1px solid var(--border-subtle);
  margin-bottom: 20px;
}
.nw-product-photo-label { font-size: 11px; color: var(--warm-500); }
.nw-product-photo-img { max-width: 100%; max-height: 100%; object-fit: contain; }

/* 2×2 spec grid. A spec with no value omits its row entirely (product.js), so
   the grid closes up rather than showing a dash. */
.nw-product-specs {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px 12px;
  margin-bottom: 22px;
}
.nw-product-specs dt {
  font-size: 10px; font-weight: 600; text-transform: uppercase;
  letter-spacing: var(--tracking-caps); color: var(--text-secondary);
}
.nw-product-specs dd { font-size: 15px; margin-top: 2px; }

.nw-product-body p {
  font-size: 14px; line-height: 1.85; color: var(--text-secondary);
  margin-bottom: 12px;
}

.nw-product-plate {
  margin-top: 8px; padding: 18px 16px;
  border-radius: 12px;
  background: var(--surface-beige);
}
.nw-product-plate-title {
  font-size: 13px; font-weight: 600; color: var(--nichiwa-ink);
  padding-bottom: 8px; margin-bottom: 12px;
  border-bottom: 1px solid var(--border-hairline);
}
.nw-product-pairing dt {
  font-size: 10px; font-weight: 600; text-transform: uppercase;
  letter-spacing: var(--tracking-caps); color: var(--warm-700); margin-top: 12px;
}
.nw-product-pairing dt:first-child { margin-top: 0; }
.nw-product-pairing dd { font-size: 13px; line-height: 1.7; color: var(--nichiwa-ink); margin-top: 3px; }

/* ---- V0.2.14: member-facing video display ------------------------------
   Hidden by default (product.html); product.js unhides it only once a real,
   non-empty video list has arrived. Same beige-plate card language as
   .nw-product-plate above, so an appearing video section reads as part of
   the same design system rather than a bolted-on widget. */
.nw-product-videos {
  margin-top: 8px; padding: 18px 16px;
  border-radius: 12px;
  background: var(--surface-beige);
}
.nw-product-videos-title {
  font-size: 13px; font-weight: 600; color: var(--nichiwa-ink);
  padding-bottom: 8px; margin-bottom: 12px;
  border-bottom: 1px solid var(--border-hairline);
}
.nw-product-video-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px;
}
.nw-product-video-row + .nw-product-video-row {
  margin-top: 12px; padding-top: 12px;
  border-top: 1px solid var(--border-hairline);
}
.nw-product-video-label {
  font-size: 13px; color: var(--nichiwa-ink);
}
.nw-product-video-play {
  width: auto; min-height: 36px; padding: 8px 18px; flex-shrink: 0;
}
/* A row that has started playing stacks its <video> full-width below where
   the label + button sat — the row switches from a horizontal flex line to
   a vertical stack the moment a <video> child is present. */
.nw-product-video-row:has(.nw-product-video-player) {
  flex-direction: column; align-items: stretch;
}
.nw-product-video-player {
  width: 100%; border-radius: 8px; background: #000;
}
.nw-product-video-error {
  font-size: 12px; color: var(--nichiwa-red); margin-top: 6px;
}

/* ---- Task 8: cellar photos, category mark, product links --------------- */
.nw-cellar-photo-img { width: 100%; height: 100%; object-fit: contain; }

.nw-cellar-line-mark {
  display: flex;
  align-items: baseline;
  gap: 6px;
  margin-top: 3px;
  font-size: 9px;
  font-weight: 600;
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--text-muted);
}
.nw-cellar-line-sep { color: var(--warm-400); }
.nw-cellar-line-latin { color: var(--text-muted); }

.nw-cellar-product-link {
  display: inline-block;
  margin-top: 12px;
  font-size: 12px;
  font-weight: 600;
  color: var(--nichiwa-red);
  text-decoration: underline;
}

/* The brand row's whole width is the tap target, not just the name. */
.nw-home-brand-link {
  display: flex;
  align-items: baseline;
  gap: 10px;
  width: 100%;
}

/* Spec grid cells: two across, label stacked above value. The wrapper div is
   required — bare dt/dd children of a 2-column grid produce label | value
   rows, not the design's 2x2 of spec cells. */
.nw-product-spec dt {
  font-size: 10px; font-weight: 600; text-transform: uppercase;
  letter-spacing: var(--tracking-caps); color: var(--text-secondary);
}
.nw-product-spec dd { font-size: 15px; margin-top: 2px; }

/* --- V0.2.31: black-tier accents ------------------------------------------ */

.nw-card-black .nw-card-perks-label {
  color: var(--nichiwa-gold);
}

/* Silver's ground is mid-grey — the default beige perks label washes out. */
.nw-card-silver .nw-card-perks-label {
  color: var(--nichiwa-offwhite);
}

/* --- V0.2.31: benefits page ------------------------------------------------ */

.nw-benefits-main {
  max-width: 560px;
  margin: 0 auto;
  padding: 18px 20px 96px;
}

.nw-benefits-title {
  font-size: 26px;
  margin: 14px 0 4px;
}

.nw-benefits-welcome {
  margin-top: 16px;
  padding: 16px 18px;
  background: var(--nichiwa-black);
  color: var(--text-inverse);
  border: 1px solid var(--nichiwa-gold);
  border-radius: 10px;
}

.nw-benefits-welcome h2 {
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
  color: var(--nichiwa-gold);
}

.nw-benefits-welcome p {
  margin-top: 8px;
  font-size: 16px;
  line-height: 1.6;
}

.nw-benefits-tiers,
.nw-benefits-facts,
.nw-benefits-events,
.nw-benefits-planned {
  margin-top: 28px;
}

.nw-benefits-tiers h2,
.nw-benefits-facts h2,
.nw-benefits-events h2,
.nw-benefits-planned h2 {
  font-size: 17px;
  margin-bottom: 10px;
}

/* The ladder: four miniature member cards on their real card grounds. */
.nw-benefits-ladder {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.nw-tiercard {
  border-radius: 12px;
  padding: 14px 18px 12px;
}

.nw-tiercard-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
}

.nw-tiercard-name {
  font-size: 15px;
  font-weight: 600;
  letter-spacing: 0.04em;
}

.nw-tiercard-rate {
  font-size: 24px;
  font-weight: 700;
  font-variant-numeric: lining-nums;
  white-space: nowrap;
}

.nw-tiercard-threshold {
  margin-top: 2px;
  font-size: 11.5px;
  letter-spacing: 0.06em;
}

.nw-tiercard-base {
  background: var(--surface-raised);
  border: 1px solid var(--border-hairline);
  color: var(--text-primary);
}
.nw-tiercard-base .nw-tiercard-rate { color: var(--nichiwa-red); }
.nw-tiercard-base .nw-tiercard-threshold { color: var(--text-muted); }

.nw-tiercard-silver {
  background: var(--nichiwa-grey-50);
  border: 1px solid rgba(252, 251, 245, 0.35);
  color: var(--text-inverse);
}
.nw-tiercard-silver .nw-tiercard-threshold { color: rgba(252, 251, 245, 0.8); }

.nw-tiercard-gold {
  background: var(--nichiwa-black);
  border: 1px solid var(--nichiwa-beige);
  color: var(--text-inverse);
}
.nw-tiercard-gold .nw-tiercard-rate { color: var(--nichiwa-beige); }
.nw-tiercard-gold .nw-tiercard-threshold { color: rgba(252, 251, 245, 0.62); }

.nw-tiercard-black {
  background: var(--nichiwa-black);
  border: 1px solid var(--nichiwa-gold);
  color: var(--text-inverse);
}
.nw-tiercard-black .nw-tiercard-rate { color: var(--nichiwa-gold); }
.nw-tiercard-black .nw-tiercard-threshold { color: rgba(252, 251, 245, 0.62); }

.nw-benefits-note {
  margin-top: 10px;
  font-size: 12px;
  color: var(--text-muted);
  line-height: 1.6;
}

.nw-benefits-list {
  list-style: none;
  font-size: 14px;
  line-height: 1.7;
}

.nw-benefits-list li {
  padding: 7px 0 7px 18px;
  position: relative;
  border-bottom: 1px solid var(--border-subtle);
}

.nw-benefits-list li::before {
  content: '·';
  position: absolute;
  left: 4px;
  color: var(--nichiwa-red);
  font-weight: 700;
}

.nw-benefits-planned-badge {
  display: inline-block;
  margin-left: 6px;
  padding: 2px 8px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: var(--tracking-caps);
  color: var(--warm-600);
  border: 1px solid var(--border-hairline);
  border-radius: 999px;
  vertical-align: 2px;
}

/* --- V0.2.34: member coupons ----------------------------------------------- */

.nw-coupons {
  margin-top: 18px;
}

.nw-coupons h2 {
  font-size: 17px;
}

.nw-coupons-hint {
  margin-top: 4px;
  font-size: 12px;
  color: var(--text-muted);
  line-height: 1.6;
}

.nw-coupons-list {
  margin-top: 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* A coupon reads as a small ticket: head row (yen / cost / action), then
   the XP-style progress bar toward the tier's point cost (V0.2.35). */
.nw-coupon {
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: 100%;
  padding: 13px 16px 12px;
  background: var(--surface-raised);
  border: 1px solid var(--border-hairline);
  border-left: 3px solid var(--nichiwa-red);
  border-radius: 10px;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.nw-coupon:disabled {
  cursor: default;
  border-left-color: var(--border-hairline);
}

.nw-coupon:disabled .nw-coupon-yen {
  color: var(--text-muted);
}

.nw-coupon-head {
  display: flex;
  align-items: baseline;
  gap: 12px;
}

.nw-coupon-yen {
  font-size: 19px;
  font-weight: 700;
  font-variant-numeric: lining-nums;
}

.nw-coupon-cost {
  font-size: 12px;
  color: var(--text-secondary);
  flex: 1;
}

.nw-coupon-cta {
  font-size: 13px;
  font-weight: 600;
  color: var(--nichiwa-red);
}

.nw-coupon:disabled .nw-coupon-cta {
  color: var(--text-muted);
  font-weight: 400;
}

/* XP bar: same track/fill grammar as the card's tier-progress bar. */
.nw-coupon-bar {
  display: block;
  height: 6px;
  border-radius: 999px;
  background: var(--warm-100);
  overflow: hidden;
}

.nw-coupon-bar-fill {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: var(--nichiwa-red);
  transition: width 0.6s var(--ease-ink, ease);
}

.nw-coupon:disabled .nw-coupon-bar-fill {
  background: var(--warm-400);
}

.nw-coupon-progress {
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
}

/* --- V0.2.36: the two coupon kinds carry distinct color schemes -----------
   Unconditional (points buy the discount outright) = the red ticket.
   Conditional (min-spend requirement) = ink ground with a brass accent —
   visibly a different instrument before the member reads a word. */

.nw-coupon-conditional {
  background: var(--nichiwa-black);
  border: 1px solid var(--nichiwa-gold);
  border-left: 3px solid var(--nichiwa-gold);
  color: var(--text-inverse);
}

.nw-coupon-conditional .nw-coupon-cost,
.nw-coupon-conditional .nw-coupon-progress {
  color: rgba(252, 251, 245, 0.7);
}

.nw-coupon-conditional .nw-coupon-cta {
  color: var(--nichiwa-gold);
}

.nw-coupon-conditional .nw-coupon-bar {
  background: rgba(252, 251, 245, 0.18);
}

.nw-coupon-conditional .nw-coupon-bar-fill {
  background: var(--nichiwa-gold);
}

.nw-coupon-conditional:disabled {
  border-left-color: rgba(218, 202, 171, 0.4);
}

.nw-coupon-condition {
  font-size: 11.5px;
  color: var(--nichiwa-gold);
}

/* A consumed once-ever coupon stays visible, quietly. */
.nw-coupon-used {
  opacity: 0.5;
}

.nw-coupon-used .nw-coupon-yen {
  text-decoration: line-through;
  text-decoration-thickness: 1px;
}

.nw-coupon-unconditional .nw-coupon-condition {
  color: var(--text-secondary);
}

/* The max-affordable headline under the balance (V0.2.36). */
.nw-coupon-max {
  margin-top: 6px;
  font-size: 13px;
  font-weight: 600;
  color: var(--nichiwa-red);
}

.nw-coupon-max-none {
  color: var(--text-muted);
  font-weight: 400;
}

/* Request-modal extras (V0.2.36). */
.nw-coupon-condition-line {
  margin-top: 6px;
  text-align: center;
  font-size: 13px;
  font-weight: 600;
  color: var(--warm-600);
}

.nw-coupon-request-note {
  margin-top: 8px;
  padding: 8px 12px;
  font-size: 13px;
  background: var(--warm-100);
  border-left: 3px solid var(--nichiwa-red);
  line-height: 1.6;
}

.nw-coupon-done-title {
  font-size: 18px;
  text-align: center;
}

.nw-coupon-done-body {
  margin-top: 10px;
  text-align: center;
  font-size: 14px;
  line-height: 1.7;
}

/* Staff modal: the scanned-request banner line. */
.nw-redeem-modal-scanned {
  margin: 6px 0 4px;
  padding: 7px 10px;
  font-size: 12.5px;
  background: var(--warm-100);
  border-left: 3px solid var(--nichiwa-red);
  line-height: 1.6;
}

/* --- V0.2.35: dedicated coupon page ---------------------------------------- */

.nw-coupon-main {
  max-width: 560px;
  margin: 0 auto;
  padding: 18px 20px 96px;
}

.nw-coupon-balance {
  margin: 8px 0 18px;
}

.nw-coupon-modal-dialog h2 {
  font-size: 16px;
}

.nw-coupon-modal-yen {
  font-size: 1.8rem;
  font-weight: 600;
  text-align: center;
  margin: 0.5rem 0 0.25rem;
}

.nw-coupon-modal-points {
  text-align: center;
  font-size: 14px;
  color: var(--text-secondary);
}

.nw-coupon-modal-warn {
  margin-top: 12px;
  padding: 8px 12px;
  font-size: 13px;
  background: var(--warm-100);
  border-left: 3px solid var(--nichiwa-red);
  line-height: 1.6;
}

#nw-coupon-modal-confirm {
  margin-top: 18px;
  width: 100%;
}

#nw-coupon-modal-cancel {
  margin-top: 10px;
  width: 100%;
}

.nw-coupon-result-amount {
  font-size: 1.6rem;
  font-weight: 700;
  text-align: center;
}

.nw-coupon-qr-wrap {
  display: flex;
  justify-content: center;
  margin: 0.75rem 0;
}

.nw-coupon-qr {
  width: 240px;
  height: 240px;
  image-rendering: pixelated;
}

.nw-coupon-result-instruction,
.nw-coupon-result-manual {
  text-align: center;
  font-size: 14px;
  line-height: 1.6;
}

/* The live row is the anti-screenshot proof — the same moving clock as the
   member card. DO NOT remove it to "clean up" the design. */
.nw-coupon-live {
  margin-top: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}

.nw-coupon-countdown {
  color: var(--text-secondary);
}

.nw-coupon-reshow-note {
  margin-top: 8px;
  text-align: center;
  font-size: 12px;
  color: var(--text-secondary);
}

#nw-coupon-modal-done {
  margin-top: 16px;
  width: 100%;
}

/* --- V0.2.31: card → benefits link ---------------------------------------- */

.nw-card-benefits-link {
  display: inline-block;
  margin-top: 10px;
  font-size: 13px;
}

/* --- V0.2.31: claim-step bonus note ---------------------------------------- */

.nw-claim-bonus-note {
  margin: 6px 0 12px;
  padding: 8px 12px;
  font-size: 14px;
  color: var(--nichiwa-ink);
  background: var(--warm-100);
  border-left: 3px solid var(--nichiwa-red);
}

/* --- V0.2.31/33: counter QR poster (print page) ----------------------------
   Staff-only print page, deliberately outside the app shell. Designed as a
   liquor LABEL whose product is membership: double-rule frame, centered
   composition, one red brush seal (酒 — a covered glyph, so no font work;
   the print-only third use of the brush face alongside cellar + home).
   Sheet 1 = A4 poster, sheet 2 = 2×2 counter cards with cut guides.
   print-color-adjust is REQUIRED — without it browsers strip the seal's
   red and the sheet prints as plain text. */

.nw-poster-body {
  background: var(--warm-200);
  print-color-adjust: exact;
  -webkit-print-color-adjust: exact;
}

.nw-poster-toolbar {
  max-width: 720px;
  margin: 0 auto;
  padding: 16px 20px 0;
  display: flex;
  gap: 14px;
  align-items: baseline;
}

.nw-poster-toolbar-hint {
  font-size: 12px;
  color: var(--text-muted);
}

.nw-poster-sheet {
  background: var(--nichiwa-offwhite);
  max-width: 720px;
  margin: 16px auto;
  padding: 18px;
  border: 1px solid var(--border-subtle);
}

/* The label frame: outer heavy rule + inner hairline, like a bottle label. */
.nw-poster-frame {
  border: 2px solid var(--nichiwa-ink);
  outline: 1px solid var(--nichiwa-ink);
  outline-offset: 3px;
  padding: 52px 44px 40px;
  text-align: center;
}

.nw-poster-eyebrow {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.42em;
  text-indent: 0.42em; /* recenters tracked caps */
  color: var(--nichiwa-red);
}

.nw-poster-brand {
  margin-top: 14px;
  font-size: 46px;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-indent: 0.18em;
  line-height: 1.15;
}

.nw-poster-brand-sub {
  margin-top: 8px;
  font-size: 14px;
  color: var(--text-secondary);
  letter-spacing: 0.1em;
}

.nw-poster-rule {
  width: 56px;
  margin: 26px auto 0;
  border: none;
  border-top: 1px solid var(--nichiwa-ink);
}

.nw-poster-lead {
  margin-top: 24px;
  font-size: 19px;
}

.nw-poster-lead-zh {
  display: block;
  margin-top: 4px;
  font-size: 15px;
  color: var(--text-secondary);
}

/* The amount is the hero; the seal is the signature. The seal hangs off
   the number's shoulder like a stamp pressed after the fact. */
.nw-poster-amount {
  position: relative;
  display: inline-block;
  margin-top: 6px;
  padding: 0 54px; /* room for the seal so it never collides with the frame */
}

.nw-poster-points {
  font-size: 104px;
  font-weight: 700;
  font-variant-numeric: lining-nums;
  letter-spacing: 0.01em;
  color: var(--nichiwa-red);
  line-height: 1.05;
}

.nw-poster-points-unit {
  display: inline-block;
  margin-left: 8px;
  font-size: 24px;
  font-weight: 600;
  text-align: left;
  vertical-align: baseline;
}

.nw-poster-points-unit small {
  display: block;
  font-size: 14px;
  font-weight: 400;
  color: var(--text-secondary);
}

.nw-poster-seal {
  position: absolute;
  right: -8px;
  top: -18px;
  width: 58px;
  height: 58px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--nichiwa-red);
  color: var(--nichiwa-offwhite);
  font-family: var(--font-brush);
  font-size: 38px;
  line-height: 1;
  border-radius: 4px;
  box-shadow: inset 0 0 0 1.5px var(--nichiwa-offwhite), inset 0 0 0 3px var(--nichiwa-red);
  transform: rotate(-4deg);
}

.nw-poster-worth {
  margin-top: 14px;
  font-size: 13px;
  color: var(--text-secondary);
}

/* QR on a beige mat, like a label's crest plate. */
.nw-poster-qr-plate {
  display: inline-block;
  margin-top: 28px;
  padding: 14px;
  background: #ffffff;
  border: 1px solid var(--nichiwa-beige);
  box-shadow: 0 0 0 6px var(--warm-100);
}

.nw-poster-qr {
  display: block;
  width: 220px;
  height: 220px;
  image-rendering: pixelated;
}

.nw-poster-scan {
  margin-top: 18px;
  font-size: 16px;
  font-weight: 600;
}

.nw-poster-url {
  margin-top: 4px;
  font-size: 12px;
  color: var(--text-muted);
  letter-spacing: 0.05em;
}

/* Perk rows: ja left, zh right, on hairlines — a label's spec table. */
.nw-poster-perks {
  list-style: none;
  margin: 28px auto 0;
  max-width: 440px;
  text-align: left;
}

.nw-poster-perks li {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 18px;
  padding: 9px 2px;
  border-top: 1px solid var(--border-subtle);
  font-size: 13.5px;
}

.nw-poster-perks li:last-child {
  border-bottom: 1px solid var(--border-subtle);
}

.nw-poster-perk-zh {
  color: var(--text-secondary);
  white-space: nowrap;
}

.nw-poster-footer-line {
  margin-top: 30px;
  font-size: 11px;
  color: var(--text-secondary);
  letter-spacing: 0.18em;
}

/* Sheet 2: 2×2 counter cards, dashed cut guides. */
.nw-poster-cards {
  display: grid;
  grid-template-columns: 1fr 1fr;
  padding: 18px;
  gap: 0;
}

.nw-poster-card {
  border: 1px dashed var(--border-hairline); /* cut guide */
  padding: 24px 16px 20px;
  text-align: center;
}

.nw-poster-card-eyebrow {
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 0.32em;
  text-indent: 0.32em;
  color: var(--nichiwa-red);
}

.nw-poster-card-offer {
  margin-top: 8px;
  font-size: 14px;
  line-height: 1.6;
}

.nw-poster-card-offer strong {
  color: var(--nichiwa-red);
  font-size: 17px;
}

.nw-poster-card-qr {
  width: 128px;
  height: 128px;
  margin-top: 12px;
  image-rendering: pixelated;
}

.nw-poster-card-brand {
  margin-top: 12px;
  font-size: 10px;
  letter-spacing: 0.14em;
  color: var(--text-secondary);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
}

.nw-poster-card-seal {
  width: 18px;
  height: 18px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--nichiwa-red);
  color: var(--nichiwa-offwhite);
  font-family: var(--font-brush);
  font-size: 12px;
  border-radius: 2px;
  transform: rotate(-4deg);
}

@media (max-width: 560px) {
  .nw-poster-frame {
    padding: 34px 18px 28px;
  }
  .nw-poster-points {
    font-size: 72px;
  }
  .nw-poster-amount {
    padding: 0 40px;
  }
  .nw-poster-seal {
    width: 46px;
    height: 46px;
    font-size: 30px;
    right: -4px;
  }
  .nw-poster-brand {
    font-size: 34px;
  }
}

@media print {
  .nw-poster-body {
    background: #ffffff;
  }
  .nw-poster-toolbar,
  .nw-env-badge {
    display: none !important;
  }
  .nw-poster-sheet {
    border: none;
    max-width: none;
    margin: 0;
    page-break-after: always;
  }
  /* No break after the final sheet — it would print a blank trailing page. */
  .nw-poster-sheet:last-child {
    page-break-after: auto;
  }
}
