/*
 * Counter print station, styled as an unattended kiosk.
 *
 * A desk-mounted screen at the post office counter, not a phone. The customer
 * reads it from arm's length across the counter, so the label preview and the
 * recipient block are set large, and the one thing they have to do — scan — is
 * the biggest thing on the page.
 *
 * Two rules run through everything below:
 *
 *   The screen does not scroll. Nobody at a kiosk discovers content below the
 *   fold, so on a counter screen the shell is locked to the viewport height and
 *   the two columns divide what is left. The label preview is scaled to fit
 *   (label_fit_controller.js) rather than allowed to push the page taller. The
 *   one exception is the recipient card, which may scroll inside itself,
 *   because a Thai address can be arbitrarily long and truncating an address
 *   the customer is being asked to verify would be worse than a scrollbar.
 *   Below 1180px the lock is lifted — a phone-sized window cannot honour it and
 *   still be usable.
 *
 *   The print rules near the bottom are the important part: only .label-sheet
 *   is allowed onto paper, and dialogs render in the top layer, so they are
 *   struck out explicitly rather than trusted to the visibility rule.
 */

/* --- fonts ---------------------------------------------------------------- */

/* Prompt, self-hosted (SIL Open Font License 1.1 — see app/assets/fonts/OFL.txt).
 *
 * Not loaded from Google Fonts, for two reasons that both end with the wrong
 * thing on a sticker:
 *
 *   The link tag carried media="screen", so the font was never fetched for
 *   printing. The label previewed in Prompt and printed in whatever Thai face
 *   the machine happened to own — Thonburi on macOS, Leelawadee on Windows —
 *   which is a different design at a different width, so the printed lines
 *   broke in different places than the ones on screen. The address block is
 *   sized to the millimetre; it cannot be laid out in a font chosen by the
 *   counter PC.
 *
 *   And a kiosk that has to print a customer's parcel label should not need
 *   Google reachable to do it. Self-hosted, the sticker is identical on every
 *   counter and on a dead network.
 *
 * Only the thai and latin subsets are shipped: the label prints Thai and ASCII
 * and nothing else. */

@font-face {
  font-family: "Prompt";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/prompt-thai-400-1e4132e6.woff2") format("woff2");
  unicode-range: U+02D7, U+0303, U+0331, U+0E01-0E5B, U+200C-200D, U+25CC;
}

@font-face {
  font-family: "Prompt";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/prompt-latin-400-d0db6f8b.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Prompt";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/assets/prompt-thai-500-ab337b12.woff2") format("woff2");
  unicode-range: U+02D7, U+0303, U+0331, U+0E01-0E5B, U+200C-200D, U+25CC;
}

@font-face {
  font-family: "Prompt";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/assets/prompt-latin-500-0dd3e126.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Prompt";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/assets/prompt-thai-600-c8f54a7b.woff2") format("woff2");
  unicode-range: U+02D7, U+0303, U+0331, U+0E01-0E5B, U+200C-200D, U+25CC;
}

@font-face {
  font-family: "Prompt";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/assets/prompt-latin-600-208ee182.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Prompt";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/assets/prompt-thai-700-56c9af1d.woff2") format("woff2");
  unicode-range: U+02D7, U+0303, U+0331, U+0E01-0E5B, U+200C-200D, U+25CC;
}

@font-face {
  font-family: "Prompt";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/assets/prompt-latin-700-0d292d0f.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Sarabun, self-hosted (SIL Open Font License 1.1 — app/assets/fonts/OFL-Sarabun.txt).
 *
 * The label only. Sarabun is a looped Thai face — the shape of the Thai
 * government standard and of postal paperwork generally, and what a customer
 * expects to read off a sticker. The interface around it stays in Prompt,
 * which is the counter equipment's own lettering; the sheet is a document, not
 * part of the UI. */
@font-face {
  font-family: "Sarabun";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/sarabun-thai-400-be6add34.woff2") format("woff2");
  unicode-range: U+02D7, U+0303, U+0331, U+0E01-0E5B, U+200C-200D, U+25CC;
}
@font-face {
  font-family: "Sarabun";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/sarabun-latin-400-e1ca36ad.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Sarabun";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/assets/sarabun-thai-500-38b176f7.woff2") format("woff2");
  unicode-range: U+02D7, U+0303, U+0331, U+0E01-0E5B, U+200C-200D, U+25CC;
}
@font-face {
  font-family: "Sarabun";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/assets/sarabun-latin-500-885e4fd1.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Sarabun";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/assets/sarabun-thai-600-57f2cd7e.woff2") format("woff2");
  unicode-range: U+02D7, U+0303, U+0331, U+0E01-0E5B, U+200C-200D, U+25CC;
}
@font-face {
  font-family: "Sarabun";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/assets/sarabun-latin-600-28e3cbf4.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Sarabun";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/assets/sarabun-thai-700-d83c5817.woff2") format("woff2");
  unicode-range: U+02D7, U+0303, U+0331, U+0E01-0E5B, U+200C-200D, U+25CC;
}
@font-face {
  font-family: "Sarabun";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/assets/sarabun-latin-700-8e3746d8.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  /* Native controls render light even on a dark-themed OS, otherwise their
   * OS-drawn popups float dark grey over the white UI. */
  color-scheme: light;

  /* Thailand Post corporate identity. */
  --post-red: #e0231a;
  --post-red-dark: #b81c15;
  --post-red-soft: #fdeeec;
  --post-navy: #23306b;

  --ink: #15171c;
  --ink-soft: #3f444e;
  --muted: #6b7280;
  --line: #e4e7ec;
  --line-soft: #f0f2f5;
  --bg: #f6f7f9;
  --card: #ffffff;

  --success: #12803c;
  --success-soft: #e6f6ec;
  --danger: #c31e14;
  --danger-soft: #fdeeec;

  --radius: 12px;
  --radius-lg: 18px;
  --radius-xl: 22px;

  --shadow-card: 0 1px 2px rgb(16 24 40 / 4%), 0 1px 3px rgb(16 24 40 / 6%);
  --shadow-float: 0 18px 44px rgb(16 24 40 / 18%), 0 2px 8px rgb(16 24 40 / 10%);

  --tap: 48px;
}

* { box-sizing: border-box; }

/* Must win over component display rules (.btn is inline-flex). */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  overflow: hidden;
  background: var(--bg);
  color: var(--ink);
  font-family: "Prompt", "Noto Sans Thai", "Thonburi", -apple-system,
    BlinkMacSystemFont, "Segoe UI", sans-serif;
  font-size: 15px;
  line-height: 1.5;
  -webkit-text-size-adjust: 100%;
  -webkit-font-smoothing: antialiased;

  /* Kiosk hygiene. Nothing on this screen is meant to be selected, dragged or
   * long-pressed: it is a machine at a counter, and a customer who drags a
   * highlight across a stranger's address has done something the station
   * should not have let them do. The manual entry field opts back in below —
   * it is the one place a person is meant to be editing text.
   * touch-action rules out double-tap zoom, which on a touch terminal is the
   * easiest way to end up with a label preview scrolled half off the screen. */
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
  touch-action: manipulation;
}

input, textarea, [contenteditable] {
  user-select: text;
  -webkit-user-select: text;
}

h1, h2, h3, p, dl, dd, ol { margin: 0; }

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

.icon { width: 20px; height: 20px; flex: none; }

/* --- shell ---------------------------------------------------------------- */

/* dvh, not vh: on a touch counter screen with browser chrome that hides on
 * scroll, vh would leave the bottom of the label under the toolbar. */
.station {
  height: 100dvh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.station-top {
  flex: none;
  display: flex;
  align-items: center;
  gap: 18px;
  padding: 12px 24px;
  background: var(--card);
  border-bottom: 1px solid var(--line);
}

.station-brand { text-decoration: none; color: inherit; }

.brand { display: inline-flex; align-items: center; gap: 11px; }
.brand__mark { flex: none; }
.brand--bar .brand__mark { width: 62px; height: 31px; }
.brand__words { display: flex; flex-direction: column; line-height: 1.15; }
.brand__th { font-size: 19px; font-weight: 700; color: var(--post-navy); letter-spacing: -0.01em; }
.brand__en {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: .06em;
  color: var(--post-navy);
  text-transform: none;
}

.station-top__divider { width: 1px; height: 32px; background: var(--line); }
.station-top__title {
  flex: none;
  font-size: 20px;
  font-weight: 600;
  letter-spacing: -0.01em;
}

/* The branch this station serves. Tinted rather than plain text: it is a
 * restriction, and at counter distance a restriction that reads as ordinary
 * caption text is one nobody reads. Allowed to shrink and wrap before the
 * title does, since the title is the same on every station and this is not. */
.station-top__branch {
  min-width: 0;
  padding: 5px 12px;
  border-radius: 999px;
  background: var(--post-red-soft);
  color: var(--post-red);
  font-size: 13.5px;
  font-weight: 600;
  line-height: 1.25;
}

.station-top__meta {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 18px;
}

.station-top__sep { width: 1px; height: 26px; background: var(--line); }

/* The support number used to live in a sidebar. With the sidebar gone it sits
 * in the bar, where it costs no vertical space and is still the first thing
 * anyone stuck reaches for. */
.station-top__support {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-size: 14px;
  color: var(--muted);
}
.station-top__support .icon { color: var(--post-red); }
.station-top__support-text { display: inline-flex; align-items: baseline; gap: 7px; }
.station-top__support strong {
  font-size: 19px;
  font-weight: 700;
  color: var(--post-red);
  font-variant-numeric: tabular-nums;
}

.station-clock { display: inline-flex; align-items: center; gap: 9px; }
.station-clock__icon { color: var(--muted); }
.station-clock__text { display: flex; flex-direction: column; line-height: 1.2; }
.station-clock__time {
  font-size: 19px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.station-clock__date { font-size: 12px; color: var(--muted); }

/* TH/EN switch. Each option is its own button_to form, so the form wrappers
 * have to be flattened back into a row of plain buttons. */
.locale-switch {
  display: inline-flex;
  border: 1px solid var(--line);
  border-radius: 999px;
  overflow: hidden;
}
.locale-switch form { margin: 0; display: flex; }
.locale-switch__option {
  border: 0;
  padding: 7px 15px;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  color: var(--muted);
  background: var(--card);
  cursor: pointer;
}
.locale-switch__option:hover { background: var(--line-soft); }
.locale-switch__option.is-active { background: var(--post-red); color: #fff; }

/* --- main grid ------------------------------------------------------------ */

/* min-height: 0 all the way down. Without it a flex item refuses to shrink
 * below its content, and one long address would push the page taller than the
 * viewport — which is exactly what this layout exists to prevent. */
.station-main {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  padding: 18px 24px;
  overflow: hidden;
}

.station-kiosk {
  position: relative;
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.station-alert {
  flex: none;
  margin-bottom: 14px;
  padding: 13px 18px;
  border-radius: var(--radius);
  background: var(--danger-soft);
  color: var(--danger);
  font-weight: 600;
}

.station-grid {
  flex: 1;
  min-height: 0;
  display: grid;
  grid-template-columns: minmax(360px, 1fr) minmax(430px, 1.1fr);
  gap: 20px;
}

.station-grid__col {
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.panel {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-card);
  padding: 22px;
}

.panel__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 14px;
}

.panel__title { font-size: 18px; font-weight: 600; }
.panel > .panel__title { display: block; margin-bottom: 14px; }
.panel__count { font-size: 13px; color: var(--muted); }

.panel--scan {
  display: flex;
  flex-direction: column;
  gap: 18px;
}

/* Idle: the scan card is the whole column and the prompt is centred in it. */
.panel--scan.panel--idle {
  flex: 1;
  min-height: 0;
  justify-content: center;
}

/* The one place allowed to scroll, and only inside itself — see the note at
 * the top of this file. */
.panel--details {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* --- scan state ----------------------------------------------------------- */

.scan-state {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 13px 16px;
  border-radius: var(--radius);
  border: 1px solid #bfe6cd;
  background: var(--success-soft);
}

.scan-state__icon {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  flex: none;
  border-radius: 50%;
  font-size: 15px;
  font-weight: 700;
  color: #fff;
  background: var(--success);
}

.scan-state__text { min-width: 0; }
.scan-state__headline { font-weight: 600; }
.scan-state__detail {
  font-size: 14px;
  color: var(--ink-soft);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  letter-spacing: .04em;
}

/* --- scan hero ------------------------------------------------------------ */

/* Every size here is viewport-relative so the prompt gives ground gracefully
 * on a 768px-tall counter PC instead of forcing a scrollbar. */
.scan-hero { text-align: center; }

.scan-hero__mark {
  width: clamp(128px, 20vh, 208px);
  aspect-ratio: 1;
  margin: 0 auto clamp(14px, 2.4vh, 22px);
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--post-red-soft);
}

.scan-hero__barcode { width: 68%; height: auto; }

.scan-hero__welcome {
  font-size: clamp(17px, 2.2vh, 21px);
  font-weight: 500;
  color: var(--muted);
}
.scan-hero__cta {
  margin-top: 2px;
  font-size: clamp(24px, 3.4vh, 32px);
  font-weight: 700;
  letter-spacing: -0.02em;
}

/* No max-width: the hint is one sentence and the card is wide enough to hold
 * it on a single line, which reads faster at counter distance than a balanced
 * two-line block. It still wraps on its own once the column gets narrow. */
.scan-hero__hint {
  margin: 10px auto 0;
  font-size: 15px;
  color: var(--muted);
}

/* The scanner only receives keystrokes while the browser window holds OS
 * focus, so its state is surfaced rather than left as a silent failure. Quiet
 * while it is fine, loud the moment it is not. */
.scanner-pill {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: clamp(12px, 2vh, 20px);
  padding: 6px 14px;
  border-radius: 999px;
  background: var(--bg);
  font-size: 12.5px;
  font-weight: 500;
  color: var(--muted);
}

.scanner-pill::before {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--success);
}

.scanner-pill.is-offline {
  background: var(--post-red-soft);
  color: var(--post-red);
  font-weight: 600;
}

.scanner-pill.is-offline::before { background: var(--post-red); }

/* The manual-entry button lives inside the hero now (it is offered only while
 * the station is idle), so it has to carry its own spacing — as a direct child
 * of .panel--scan it used to get the column's 18px gap for free. */
.scan-hero .btn--block { margin-top: clamp(16px, 2.6vh, 26px); }

/* --- customer details ----------------------------------------------------- */

.details__row {
  display: grid;
  grid-template-columns: 130px 1fr;
  gap: 14px;
  padding: 9px 0;
  border-bottom: 1px solid var(--line-soft);
}
.details__row:last-of-type { border-bottom: 0; }
.details__row dt { color: var(--muted); font-size: 14px; }
.details__row dd { font-weight: 500; overflow-wrap: anywhere; }
.details__line { display: block; }

/* --- preview -------------------------------------------------------------- */

.panel--preview {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* White, not grey: the stage stands in for paper.
 *
 * The sheet inside keeps its true 150x100mm and is scaled by transform, so its
 * layout box stays larger than this container when the screen is short. That is
 * fine and deliberate — the box is centred and the visible, scaled result fits;
 * overflow: hidden just stops the untransformed size from claiming scrollbars. */
.preview-stage {
  flex: 1;
  min-height: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--card);
  padding: 14px;
  display: grid;
  place-items: center;
  overflow: hidden;
}

/* The wrapper carries the scaled footprint (set in JS) so the stage centres
 * what is visible; the sheet inside scales from its top-left corner to match. */
.preview-stage__box { flex: none; }

.preview-stage__fit {
  transform: scale(var(--label-scale, 1));
  transform-origin: top left;
}

.preview-empty { text-align: center; color: var(--muted); }
.preview-empty__icon { width: clamp(72px, 11vh, 104px); height: auto; color: #c4c9d2; }
.preview-empty__title {
  margin-top: 14px;
  font-size: clamp(19px, 2.6vh, 23px);
  font-weight: 700;
  color: #9aa1ad;
}
.preview-empty__hint { margin-top: 7px; font-size: 14px; }

.preview-note {
  flex: none;
  margin-top: 14px;
  padding: 12px 16px;
  border-radius: var(--radius-lg);
  font-weight: 500;
  display: flex;
  align-items: center;
  gap: 11px;
  background: var(--success-soft);
  color: var(--success);
}

.preview-note__warn { margin-left: auto; font-weight: 600; color: var(--post-red); }

.preview-note__badge {
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  flex: none;
  border-radius: 50%;
  background: var(--success);
  color: #fff;
  font-size: 13px;
  font-weight: 700;
}

.icon--alert { width: 22px; height: 22px; }

.print-state {
  flex: none;
  margin-top: 8px;
  min-height: 18px;
  font-size: 14px;
  color: var(--muted);
}

.preview-actions {
  flex: none;
  margin-top: 12px;
  display: flex;
  justify-content: flex-end;
  gap: 12px;
}

/* --- buttons -------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  min-height: var(--tap);
  padding: 0 24px;
  border: 0;
  border-radius: var(--radius);
  font: inherit;
  font-size: 16px;
  font-weight: 600;
  cursor: pointer;
  text-decoration: none;
}

.btn:disabled { opacity: .55; cursor: default; }
.btn:focus-visible { outline: 3px solid var(--post-navy); outline-offset: 2px; }

/* The manual-entry button is the one control the browser keeps handing focus
 * to unprompted — it is first in the error dialog, so showModal() autofocuses
 * it — and because a barcode scanner arrives as keystrokes, :focus-visible
 * matches and the navy ring sits on the button through every scan. It reads as
 * damage rather than as focus, so this one control does without it. */
.btn--manual:focus-visible { outline: 0; }

.btn--primary { background: var(--post-red); color: #fff; }
.btn--primary:hover:not(:disabled) { background: var(--post-red-dark); }
.btn--outline { background: var(--card); color: var(--ink); box-shadow: inset 0 0 0 1px var(--line); }
.btn--outline:hover { background: var(--bg); }
.btn--ghost { background: var(--bg); color: var(--ink-soft); box-shadow: inset 0 0 0 1px var(--line); }
.btn--ghost:hover { background: var(--line-soft); color: var(--ink); }
.btn--block { width: 100%; }

/* --- dialogs -------------------------------------------------------------- */

/* Manual entry and every error the customer can see. Native <dialog> so the
 * top layer, the backdrop, focus trapping and Esc come from the browser rather
 * than from us — and so nothing can render above the print output by accident,
 * which the print block below closes off for good. */
.modal {
  width: min(560px, 92vw);
  max-height: 88dvh;
  padding: 30px;
  border: 0;
  border-radius: var(--radius-xl);
  background: var(--card);
  color: var(--ink);
  box-shadow: var(--shadow-float);
  overflow: auto;
}

.modal::backdrop { background: rgb(16 24 40 / 58%); }

.modal__dismiss { display: flex; justify-content: flex-end; margin: -12px -12px 0 0; }
.modal__close {
  width: 38px;
  height: 38px;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--muted);
  font-size: 26px;
  line-height: 1;
  cursor: pointer;
}
.modal__close:hover { background: var(--line-soft); color: var(--ink); }

.modal__icon { margin-bottom: 14px; color: var(--post-red); }
.modal__icon .icon { width: 46px; height: 46px; }

.modal__title { font-size: 24px; font-weight: 700; letter-spacing: -0.01em; }
.modal__lead { margin-top: 8px; color: var(--ink-soft); }
.modal__meta {
  margin-top: 12px;
  padding: 9px 14px;
  border-radius: var(--radius);
  background: var(--bg);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: .05em;
}

/* A way out, offered under the message rather than beside it. Tinted like the
 * branch pill because it is the same kind of thing: not the failure, but what
 * to do about it. */
.modal__hint {
  margin-top: 12px;
  padding: 10px 14px;
  border-radius: var(--radius);
  background: var(--post-red-soft);
  color: var(--post-red);
  font-size: 15px;
  font-weight: 600;
  line-height: 1.4;
}
.modal__hint[hidden] { display: none; }

.modal__form { margin-top: 20px; }

/* Set large and in a tabular face: this is read back digit by digit against a
 * printed number, often by someone who has already misread it once. */
.modal__input {
  width: 100%;
  padding: 15px 18px;
  border: 2px solid var(--line);
  border-radius: var(--radius);
  font: inherit;
  font-size: 24px;
  font-weight: 700;
  letter-spacing: .08em;
  text-align: center;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
}
.modal__input:focus-visible { outline: 0; border-color: var(--post-red); }

.modal__error {
  margin-top: 12px;
  padding: 12px 16px;
  border-radius: var(--radius);
  background: var(--danger-soft);
  color: var(--danger);
  font-weight: 600;
}

.modal__actions {
  margin-top: 22px;
  display: flex;
  justify-content: flex-end;
  gap: 12px;
}
.modal__actions--stack { flex-direction: column-reverse; }
.modal__actions--stack .btn { width: 100%; }

/* --- reading / searching banner ------------------------------------------- */

/* Sits over the columns rather than inside one of them, because it has to be
 * readable across the counter and it must not disturb the layout it covers —
 * a slow scan would otherwise reflow the label preview twice per scan.
 * pointer-events: none so it can never swallow a tap on the buttons under it. */
.progress {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  z-index: 30;
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 20px 34px;
  border-radius: 999px;
  background: var(--ink);
  color: #fff;
  font-size: 20px;
  font-weight: 600;
  box-shadow: var(--shadow-float);
  pointer-events: none;
}

.progress__spinner {
  width: 24px;
  height: 24px;
  flex: none;
  border-radius: 50%;
  border: 3px solid rgb(255 255 255 / 28%);
  border-top-color: #fff;
  animation: progress-spin .7s linear infinite;
}

@keyframes progress-spin { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
  .progress__spinner { animation-duration: 2.4s; }
}

/* --- full-screen cover ---------------------------------------------------- */

/* Only ever visible when the window is not already filling the display. Covers
 * everything rather than sitting in a corner, because its whole job is to
 * collect the one tap the Fullscreen API insists on before it will do anything
 * — a small button would be tapped by staff and ignored by customers, which is
 * the wrong way round. */
.kiosk-cover {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: grid;
  place-items: center;
  padding: 24px;
  background: var(--card);
  cursor: pointer;
  text-align: center;
}

.kiosk-cover__inner { display: grid; justify-items: center; gap: 10px; }
.kiosk-cover__mark { margin-bottom: 14px; }
.kiosk-cover__title {
  font-size: clamp(24px, 4vh, 34px);
  font-weight: 700;
  letter-spacing: -0.02em;
}
.kiosk-cover__hint { font-size: 15px; color: var(--muted); }

/* --- sign-out ------------------------------------------------------------- */

/* Narrower than the tracking-number field, and not tabular: this is a password,
 * not a number being read back digit by digit off a barcode. */
.modal--logout { width: min(420px, 92vw); }
.modal__input--password {
  font-size: 20px;
  letter-spacing: 0.12em;
  font-variant-numeric: normal;
  text-transform: none;
}

/* --- auto-clear toast ----------------------------------------------------- */

/* One pill for both countdowns — after a print, and before an idle wipe — so
 * there is a single place that says what the station is about to do, and a
 * single control that stops it.
 *
 * In flow rather than floating: pinned over the bottom of the screen it landed
 * on the print buttons, and a reprint control sitting on top of the print
 * control is exactly the wrong thing to hand someone who has just been told
 * the printer might have failed. Taking up real space costs the grid ~56px,
 * which the label preview gives back by re-fitting. */
/* Floats over the columns, pinned to the bottom of the kiosk, rather than
 * sitting in the flow: the pill appears and disappears twice per customer, and
 * in the flow each appearance shoved the panels and the label preview up the
 * screen. Absolute inside .station-kiosk, so it still tracks the padding of
 * the screen it belongs to. */
.kiosk-toast {
  position: absolute;
  left: 50%;
  bottom: 10px;
  transform: translateX(-50%);
  z-index: 40;
  max-width: min(100%, 760px);
  display: flex;
  align-items: center;
  gap: 18px;
  padding: 12px 14px 12px 20px;
  border-radius: 999px;
  background: var(--ink);
  color: #fff;
  box-shadow: var(--shadow-float);
}

.kiosk-toast[data-mode="printed"] { background: var(--success); }

/* White disc so the mark reads as a status badge rather than as one more line
 * of the sentence next to it. */
.kiosk-toast__icon {
  flex: none;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: #fff;
  color: var(--ink);
}
.kiosk-toast[data-mode="printed"] .kiosk-toast__icon { color: var(--success); }
.kiosk-toast__icon svg { width: 26px; height: 26px; }

.kiosk-toast__copy {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}

.kiosk-toast__title {
  font-size: 19px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.kiosk-toast__hint {
  font-size: 14px;
  font-weight: 500;
  color: rgb(255 255 255 / 85%);
}
.kiosk-toast__hint[hidden] { display: none; }

/* Outlined rather than filled: the pill is already the loud thing on screen,
 * and a solid button on a solid pill loses its edge. */
.btn--toast {
  flex: none;
  min-height: 44px;
  padding: 0 20px;
  gap: 10px;
  font-size: 16px;
  background: transparent;
  color: #fff;
  box-shadow: inset 0 0 0 1.5px rgb(255 255 255 / 70%);
}
.btn--toast:hover { background: rgb(255 255 255 / 16%); }
.btn--toast svg { width: 20px; height: 20px; }

/* --- the label itself ----------------------------------------------------- */

/* Sized in mm so the on-screen preview is the same shape as the paper. */
.label-sheet {
  /* Landscape. The addresses are what needed the room: split across two 75mm
   * columns a Thai address wrapped into six short lines, where the full width
   * holds it in two. */
  width: 150mm;
  height: 100mm;
  background: #fff;
  color: #000;
  border: 2px solid #000;
  font-family: "Sarabun", "Thonburi", "Noto Sans Thai", sans-serif;
  font-size: 3.1mm;
  line-height: 1.3;

  display: grid;
  /* head, from, to, foot. The recipient takes the slack, so a long address
   * grows into the middle of the sheet instead of off the bottom of it. */
  grid-template-rows: auto auto 1fr auto;
}

.label-sheet__head {
  display: grid;
  /* Measured, not guessed: the widest service name the counter prints,
   * "ลงทะเบียนในประเทศ", needs 37.6mm on one line in Sarabun, and the middle
   * cell only had 36.5mm — so it broke in two. The barcode cell cannot give
   * any width up (68mm is what a scannable Code 128 of 13 characters needs),
   * so the room comes from the wordmark. */
  grid-template-columns: 36mm 1fr 68mm;
  border-bottom: 2px solid #000;
}

.label-sheet__brand { padding: 2.5mm; display: grid; place-items: center; }
/* The label's brand cell is narrow, so the wordmark must not wrap. */
/* The wordmark is set to fit the cell that is left after the service name and
 * the barcode have taken what they need — it is the only one of the three that
 * can give ground without costing information. Measured at 30mm of content in
 * 31mm of room; it must not wrap, so it has to fit by size. */
.brand--label { gap: 1.4mm; }
.brand--label .brand__words { white-space: nowrap; }
.brand--label .brand__mark { width: 12.5mm; height: 6.3mm; }
.brand--label .brand__th { font-size: 2.9mm; }
.brand--label .brand__en { font-size: 1.75mm; }

.label-sheet__service {
  padding: 2.5mm 2mm;
  border-left: 2px solid #000;
  display: flex;
  flex-direction: column;
  justify-content: center;
  text-align: center;
}
.label-sheet__service-th {
  font-size: 4.2mm;
  font-weight: 700;
  line-height: 1.15;
  text-wrap: balance;
}
.label-sheet__service-en { font-size: 2.5mm; letter-spacing: .05em; }

/* Barcode block, top right. Sized so the bars fill the cell — this is the one
 * mark on the sheet a machine has to read, so it gets the width. */
.label-sheet__code {
  padding: 2mm 3mm;
  border-left: 2px solid #000;
  display: flex;
  flex-direction: column;
  justify-content: center;
  text-align: center;
}
.label-sheet__code svg { width: 100%; height: 15mm; }
.label-sheet__tracking {
  margin-top: 1mm;
  font-size: 4.6mm;
  font-weight: 700;
  letter-spacing: .06em;
  font-variant-numeric: tabular-nums;
}

/* --- the two address blocks --------------------------------------------- */

/* The sender row sizes to its content (grid `auto`) while the recipient takes
 * the slack — so without a ceiling, a long sender address grows into exactly
 * the space a long recipient address needs, and the two get squeezed hardest
 * at the same moment. Measured on the sheet, the sender was taking 31.5mm and
 * leaving the recipient 28mm.
 *
 * The two are not equal in importance. The recipient address is what decides
 * whether the parcel arrives; the sender address is where it goes back to if
 * it does not. So the sender is capped and set smaller, and the recipient
 * keeps a guaranteed ~34mm of the 60mm the two rows share. */
.label-sheet__party--from { max-height: 26.5mm; }

.label-sheet__party {
  padding: 2.5mm 3mm;
  min-width: 0;
  min-height: 0;
  border-bottom: 2px solid #000;
  overflow: hidden;
}

.label-sheet__party-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 3mm;
}

.label-sheet__party-title {
  font-size: 2.6mm;
  font-weight: 700;
  text-transform: uppercase;
}

.label-sheet__party-phone {
  font-size: 3.1mm;
  font-weight: 700;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* --- room for what hangs below the baseline -------------------------------
 *
 * Every text box on this sheet is a -webkit-line-clamp box with
 * overflow: hidden, so its height is exactly line-height x lines and anything
 * outside that is not overflowed, it is cut.
 *
 * Sarabun wants 1.30em a line (ascent 1068 + descent 232 per 1000) and the
 * line heights below are deliberately tighter, which is fine for the lines
 * themselves. It is not fine for the last one: สระอุ and สระอู reach 0.338em
 * under the baseline, ฐ 0.316, ญ 0.269 — all past the descent the font
 * declares — so that ink lands outside the box and the vowel, or the tail of
 * ญ, is sliced in half. It reads as a broken glyph, and it is on paper.
 *
 * Room needed below = 0.756em - line-height/2, which is where each
 * padding-bottom comes from. Padding, not a taller line-height: overflow clips
 * at the padding edge, so this is one allowance at the foot of the box rather
 * than extra space under every line — 0.15em once instead of 0.14em four
 * times. Clearing it with line-height alone would take 1.51, which no row here
 * has the millimetres for.
 *
 * The top has the same problem, smaller. ไม้ไต่คู้ reaches 1.031em over the
 * baseline against an ascent of 1.068, so 0.037em of slack — and any
 * line-height under 1.226 spends it and starts shaving the tops of ไ ใ โ and
 * the tone marks. The tighter steps below are under that, so they carry a
 * padding-top of (1.300 - line-height)/2 - 0.037 as well.
 * ------------------------------------------------------------------------- */

/* Clamped for the same reason as the address: a merchant-typed company name
 * can be arbitrarily long, and it must not push the address off the sticker. */
.label-sheet__party-name {
  margin-top: 1mm;
  font-weight: 700;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 1;
  line-clamp: 1;
  padding-bottom: 0.12em;
  overflow: hidden;
}
.label-sheet__party-name--strong {
  font-size: 4.4mm;
  line-height: 1.2;
  padding-bottom: 0.17em;
  padding-top: 0.02em;
}

/* The address is one paragraph, not a line per component: PLC's free-text
 * address is a single blob anyway, and letting it flow uses the width instead
 * of stacking short lines. Clamped so a merchant who pasted three paragraphs
 * cannot push the reference row off the paper. */
/* The steps station_address_density picks between. Line height tightens along
 * with the type, because the constraint is the block's height in millimetres,
 * not its line count: shrinking the font alone buys more characters per line
 * but the extra lines still do not fit, and the row clips one mid-glyph — a
 * worse result than the ellipsis it was meant to avoid. Each step below is
 * sized so clamp x line-height stays inside the room the block actually has,
 * measured on the rendered sheet. */
/* Weight 500 rather than 400 is for the paper, not the screen: the vowels are
 * a hairline at this size, and a 203dpi thermal head renders a hairline as a
 * dotted guess. It costs nothing in layout — a heavier cut is a fraction wider
 * per glyph, not a taller line. */
.label-sheet__party-address {
  margin-top: 0.6mm;
  overflow-wrap: anywhere;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  font-weight: 500;
  font-size: 2.85mm;
  line-height: 1.24;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  padding-bottom: 0.15em;
  overflow: hidden;
}
.label-sheet__party-address.is-long {
  font-size: 2.55mm;
  line-height: 1.18;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  padding-bottom: 0.18em;
  padding-top: 0.03em;
}
.label-sheet__party-address.is-dense {
  font-size: 2.2mm;
  line-height: 1.13;
  -webkit-line-clamp: 4;
  line-clamp: 4;
  padding-bottom: 0.20em;
  padding-top: 0.05em;
}

.label-sheet__party-address--strong {
  font-size: 3.6mm;
  line-height: 1.3;
  -webkit-line-clamp: 4;
  line-clamp: 4;
  padding-bottom: 0.12em;
}
.label-sheet__party-address--strong.is-long {
  font-size: 3.15mm;
  line-height: 1.25;
  -webkit-line-clamp: 4;
  line-clamp: 4;
  padding-bottom: 0.15em;
}
.label-sheet__party-address--strong.is-dense {
  font-size: 2.5mm;
  line-height: 1.18;
  -webkit-line-clamp: 5;
  line-clamp: 5;
  padding-bottom: 0.18em;
  padding-top: 0.03em;
}

/* Bounded for the same reason the sender block is: the recipient row takes the
 * slack (1fr), so anything that grows here shrinks the address that decides
 * whether the parcel arrives — and the recipient's own clamp is sized to the
 * room it has, so it starts slicing lines in half the moment that room moves.
 * The note gives way instead; see .label-sheet__reference. */
.label-sheet__foot { padding: 2mm 3mm; max-height: 14mm; overflow: hidden; }
/* min-height holds the row open when there is no reference to print, so an
 * empty value leaves a writable blank instead of collapsing the footer and
 * changing the height of every other row on the sheet. */
.label-sheet__reference {
  margin-top: 0.6mm;
  min-height: 5mm;
  font-size: 3.8mm;
  font-weight: 700;
  overflow-wrap: anywhere;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 1;
  line-clamp: 1;
  padding-bottom: 0.12em;
  overflow: hidden;
}

/* The dense steps here and above each gave back a fraction of a millimetre to
 * pay for the padding: they apply only past ~344 characters of address, where
 * the type is already stepping down, and the alternative was the recipient row
 * running out of millimetres and clipping its last line.
 *
 * A merchant can type a paragraph into the contents field. Rather than let it
 * push the footer into the recipient's space, it steps down a size and takes a
 * second line inside the same 14mm — the note is worth keeping whole, but not
 * at the cost of the address. */
.label-sheet__reference.is-long,
.label-sheet__reference.is-dense {
  font-size: 2.5mm;
  line-height: 1.2;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  padding-bottom: 0.17em;
  padding-top: 0.02em;
}

/* --- print ---------------------------------------------------------------- */

/* The counter's label stock is 150x100mm landscape, the same size the sheet is
 * drawn at, so nothing scales between the sheet and the paper. */
/* --- Login ---------------------------------------------------------------
 *
 * Its own screen, not a dialog over the station: nothing behind it is loaded
 * yet. Centred in the viewport and allowed to scroll, because unlike the kiosk
 * this one is also read on a laptop while the terminal is being set up.
 */
body.login {
  min-height: 100vh;
  background: var(--bg);
}

.login-main {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 22px;
  padding: 32px 20px;
}

.login-card {
  width: 100%;
  max-width: 420px;
  padding: 34px 32px 32px;
  border-radius: var(--radius-xl);
  background: var(--card);
  box-shadow: var(--shadow-float);
  text-align: center;
}

.login-card__brand {
  display: flex;
  justify-content: center;
  margin-bottom: 22px;
}

.login-card__title {
  font-size: 26px;
  font-weight: 700;
  letter-spacing: -0.01em;
}

.login-card__lead {
  margin-top: 8px;
  color: var(--ink-soft);
}

.login-card__error {
  margin-top: 18px;
  padding: 12px 16px;
  border-radius: var(--radius);
  background: var(--danger-soft);
  color: var(--danger);
  font-weight: 600;
  text-align: start;
}

.login-card__code {
  margin-top: 18px;
  padding: 14px 16px;
  border-radius: var(--radius);
  background: var(--line-soft);
  color: var(--ink);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 14px;
  line-height: 1.7;
  text-align: start;
}

.login-form {
  margin-top: 26px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.login-field {
  display: flex;
  flex-direction: column;
  gap: 7px;
  text-align: start;
}

.login-field__label {
  font-size: 14px;
  font-weight: 600;
  color: var(--ink-soft);
}

.login-field__input {
  width: 100%;
  min-height: var(--tap);
  padding: 0 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--card);
  color: var(--ink);
  font: inherit;
  font-size: 17px;
}

.login-field__input:focus-visible {
  outline: 0;
  border-color: var(--post-red);
  box-shadow: 0 0 0 3px var(--post-red-soft);
}

/* The reveal button sits inside the field rather than beside it, so the field
 * keeps the full width of the card and the row does not reflow when the icon
 * swaps. Logical padding, because the rest of this file uses start/end. */
.login-field__control { position: relative; display: flex; }
.login-field__input--with-action { padding-inline-end: 50px; }

.login-field__reveal {
  position: absolute;
  inset-inline-end: 4px;
  inset-block: 4px;
  width: 42px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 9px;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
}

.login-field__reveal:hover { background: var(--line-soft); color: var(--ink-soft); }
.login-field__reveal:focus-visible { outline: 3px solid var(--post-navy); outline-offset: 1px; }
.login-field__reveal[aria-pressed="true"] { color: var(--post-red); }
.login-field__reveal .icon { width: 21px; height: 21px; }

.login-form .btn { margin-top: 6px; }

/* Sits in the station header next to the language switch. */
.station-logout {
  min-height: 36px;
  padding: 0 14px;
  border: 0;
  border-radius: 999px;
  background: var(--bg);
  color: var(--ink-soft);
  box-shadow: inset 0 0 0 1px var(--line);
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
}

.station-logout:hover { background: var(--line-soft); color: var(--ink); }
.station-logout:focus-visible { outline: 3px solid var(--post-navy); outline-offset: 2px; }

@page { size: 150mm 100mm; margin: 0; }

@media print {
  /* Only the label goes on paper. Everything is hidden, then the label and the
   * chain of ancestors that carry it are shown again — hiding by ancestor
   * alone would hide the label with them. */
  body * { visibility: hidden !important; }

  .label-sheet, .label-sheet * { visibility: visible !important; }

  /* An open <dialog> renders in the top layer, which the visibility rule above
   * cannot be trusted to cover, so it is struck out by display as well. The
   * controller also closes every dialog before calling window.print() — this is
   * the second lock on the same door, because a dialog reaching the paper would
   * ruin a sticker without anyone seeing it happen on screen. */
  dialog,
  dialog::backdrop,
  .kiosk-cover,
  .progress,
  .kiosk-toast,
  .station-top,
  .mock-banner,
  .station-alert,
  .panel__header,
  .preview-note,
  .print-state,
  .preview-actions,
  .scan-state,
  .scan-hero,
  .details,
  .panel__title { display: none !important; }

  html, body {
    margin: 0 !important;
    padding: 0 !important;
    height: auto !important;
    overflow: visible !important;
    background: #fff !important;
  }

  .station, .station-main, .station-kiosk, .station-grid,
  .station-grid__col, .panel, .preview-stage,
  .preview-stage__box, .preview-stage__fit {
    display: block !important;
    margin: 0 !important;
    padding: 0 !important;
    border: 0 !important;
    background: transparent !important;
    box-shadow: none !important;
    min-height: 0 !important;
    height: auto !important;
    width: auto !important;
    overflow: visible !important;
  }

  /* The on-screen fit is a visual shrink only. Undone here — both the scale
   * and the box JS sized to match it — or the sticker would come out at
   * whatever fraction of 150x100mm the window happened to allow. */
  .preview-stage__fit { transform: none !important; }
  .preview-stage__box { width: auto !important; height: auto !important; }

  .label-sheet {
    position: absolute;
    top: 0;
    left: 0;
    width: 150mm;
    border-width: 0.4mm;
    /* Reversed-out blocks and the barcode must not be dropped by the
     * browser's "background graphics off" default. */
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }

}

/* --- narrower windows ----------------------------------------------------- */

/* Below this the screen is no longer a counter kiosk — it is someone checking
 * the station from a laptop or a tablet. Stacking the columns is not enough on
 * its own: the no-scroll lock has to come off too, or the content simply does
 * not fit and becomes unreachable. The preview leads, because it is the thing
 * being checked. */
@media (max-width: 1180px) {
  html, body { height: auto; overflow: auto; }

  .station { height: auto; min-height: 100dvh; overflow: visible; }
  .station-main { overflow: visible; padding: 16px; }
  .station-grid { grid-template-columns: 1fr; }
  .station-grid__col--preview { order: -1; }

  .panel--details,
  .panel--preview,
  .panel--scan.panel--idle { flex: none; overflow: visible; }

  .preview-stage { min-height: 60vh; }
  .kiosk-toast { align-self: stretch; justify-content: space-between; }
}

/* Standing warning while the station is answering from the mock lookup. Loud
 * on purpose: the label it renders is print-ready and must never be mistaken
 * for a real shipment. */
.mock-banner {
  flex: none;
  margin-bottom: 14px;
  padding: 12px 20px;
  border-radius: var(--radius);
  background: #7f1d1d;
  color: #fff;
  font-weight: 600;
  text-align: center;
}

/* --- narrow windows: let the bar wrap ------------------------------------- */

/* The bar's nine items in one unwrapping row need about 1000px. Between that
 * and the phone block below sits every tablet in portrait, so the wrap has to
 * start here rather than at 720 — an iPad at 768 was overflowing by a few
 * pixels, which is all it takes to make the page scroll sideways. Only the
 * wrap changes at this size; nothing is dropped, because there is still room
 * for the clock and the support number on a second row. */
@media screen and (max-width: 1000px) {
  .station-top { flex-wrap: wrap; gap: 10px 16px; }
}

/* --- phones --------------------------------------------------------------- */

/* Below this the station is being read on a phone, not worked at. The bar is
 * the thing that has to give way: it was one unwrapping row of nine items with
 * a hard floor near 950px, and that floor was what made the whole page scroll
 * sideways. It wraps now, in a fixed order — brand and the two controls on the
 * first row, then the title, then the branch pill. The support number and the
 * clock are dropped: they are the only two things on the bar that are not part
 * of doing the job, and at 360px the row is a pixel or two short even without
 * them.
 *
 * Everything here is appended at the end of the file on purpose. The file has
 * base rules sitting after the 1180px block, so source order at equal
 * specificity is what lets these win without a single !important.
 *
 * `screen and` is not decoration either: the print block resets .preview-stage
 * but has no aspect-ratio reset, so a bare media query would leak the 3:2 box
 * below into the label stock. */
@media screen and (max-width: 720px) {
  .station-top {
    flex-wrap: wrap;
    gap: 8px 12px;
    padding: 10px 14px;
  }

  /* Decoration only, and the wrap already separates the groups they divided. */
  .station-top__divider,
  .station-top__sep { display: none; }

  .station-brand { order: 1; }
  .brand--bar .brand__mark { width: 50px; height: 25px; }
  .brand__th { font-size: 17px; }

  .station-top__meta { order: 2; margin-left: auto; gap: 10px; }

  /* The 240px basis is the wrap point: title and branch share a row on a
   * tablet and separate on a phone, without a breakpoint to say so. */
  .station-top__title {
    order: 3;
    flex: 1 1 240px;
    min-width: 0;
    font-size: 17px;
  }

  .station-top__branch {
    order: 4;
    flex: 0 1 auto;
    font-size: 12.5px;
    padding: 4px 10px;
  }

  .station-top__support,
  .station-clock { display: none; }

  .locale-switch__option { padding: 7px 12px; }
  .station-logout { padding: 0 12px; font-size: 13px; }

  .station-main { padding: 12px; }
  .station-grid,
  .station-grid__col { gap: 14px; }
  .panel { padding: 16px; }

  .station-alert,
  .mock-banner { padding: 10px 14px; font-size: 14px; }

  .details__row { grid-template-columns: 104px 1fr; gap: 10px; }

  /* The clamp only ever consulted viewport height; min() adds the width axis
   * without disturbing what it already did on a short counter screen. */
  .scan-hero__mark { width: min(clamp(128px, 20vh, 208px), 46vw); }

  /* 3:2 is the sheet's own 150x100mm ratio, so the stage becomes exactly the
   * box label_fit_controller measures against. It replaces the 60vh floor from
   * the block above, which left a hand's width of empty paper under the label. */
  .preview-stage { min-height: 0; aspect-ratio: 3 / 2; padding: 8px; }

  /* Print sits on top, matching the modals. */
  .preview-actions { flex-direction: column-reverse; gap: 10px; }
  .preview-actions .btn { width: 100%; }

  /* The same shape .modal__actions--stack opts into, applied to all of them. */
  .modal__actions { flex-direction: column-reverse; }
  .modal__actions .btn { width: 100%; }

  /* Once the line wraps a 999px pill curves into its own text, so the radius
   * has to come down with the padding. */
  .progress {
    max-width: calc(100% - 24px);
    gap: 12px;
    padding: 14px 20px;
    border-radius: var(--radius-lg);
    font-size: 16px;
  }

  /* Stretched between two insets instead of centred — translateX(-50%) would
   * shove half of the widened pill off the screen. And once it wraps it stops
   * being a pill: at three rows a 999px radius curves right through the icon
   * and the button, so the corners have to come in with it. */
  .kiosk-toast {
    left: 8px;
    right: 8px;
    transform: none;
    max-width: none;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: 10px 12px;
    padding: 14px;
    border-radius: var(--radius-lg);
  }
  .btn--toast { flex: 1 1 100%; padding: 0 14px; }
}

@media screen and (max-width: 420px) {
  /* Measured, not guessed: at 360 the brand and the two controls came to 339
   * against 332 of usable row, so the bar spilled onto a third line. Every
   * value below is shaved to buy those seven pixels back and keep the bar at
   * two rows — a three-row header costs a fifth of the screen. */
  .station-top { gap: 8px 10px; }

  .brand--bar .brand__mark { width: 44px; height: 22px; }
  .brand__th { font-size: 15px; }
  .brand__en { font-size: 9px; }

  .station-top__title { flex: 1 1 100%; font-size: 16px; }

  .station-top__meta { gap: 10px; }
  .locale-switch__option { padding: 7px 9px; }
  .station-logout { padding: 0 10px; font-size: 12.5px; }

  /* A 104px label column leaves a Thai address about eight characters wide, so
   * the term goes above its value rather than beside it. */
  .details__row { grid-template-columns: 1fr; gap: 2px; padding: 8px 0; }
  .details__row dt { font-size: 13px; }

  .btn { padding: 0 16px; }

  .modal { padding: 20px; }
  .modal__title { font-size: 20px; }
  .modal__input { padding: 13px 14px; font-size: 20px; letter-spacing: .05em; }

  .progress { padding: 12px 16px; font-size: 15px; }

  .kiosk-toast__icon { width: 36px; height: 36px; }
  .kiosk-toast__icon svg { width: 22px; height: 22px; }
  .kiosk-toast__title { font-size: 16px; }
}
