/* ---------------------------------------------------------------------------
   style.css — deliberately plain. Everything themeable lives in the
   :root block below (colors, fonts, radius) so swapping in a real logo
   and brand palette later is a find-and-replace, not a rewrite.
--------------------------------------------------------------------------- */

:root{
  --bg: #faf8f3;
  --bg-raised: #ffffff;
  --ink: #17140f;
  --ink-soft: #55503f;
  --ink-faint: #8c8574;
  --line: #e3dccb;
  --brand-tan: #d3b191;      /* sampled straight from the PAWNALOG logo */
  --brand-tan-bg: #f4e9db;
  --accent: #74614f;         /* darkened logo tan — used for buttons/links (contrast) */
  --accent-bg: #f4e9db;
  --good: #3c7a52;
  --good-bg: #e4efe4;
  --bad: #a8432b;
  --bad-bg: #f6e4de;
  --radius: 10px;
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --font-display: Georgia, "Liberation Serif", "Times New Roman", serif;
  --font-mono: ui-monospace, "SFMono-Regular", Menlo, monospace;
  --header-h: 56px;
  --nav-h: 60px;
}

@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]){
    --bg: #181613;
    --bg-raised: #221f1a;
    --ink: #f1ece0;
    --ink-soft: #c4bcab;
    --ink-faint: #8c8574;
    --line: #3a352a;
    --brand-tan: #d3b191;
    --brand-tan-bg: #332a1e;
    --accent: #d3b191;
    --accent-bg: #332a1e;
    --good: #7ec293;
    --good-bg: #1f2c22;
    --bad: #e18567;
    --bad-bg: #2f1f1a;
  }
}

/* Explicit "Dark" choice from the Appearance setting on the Store page —
   applies regardless of the device's own OS setting, unlike the
   prefers-color-scheme block above which only kicks in when the OS is
   already set to dark. Same palette either way. */
:root[data-theme="dark"]{
  --bg: #181613;
  --bg-raised: #221f1a;
  --ink: #f1ece0;
  --ink-soft: #c4bcab;
  --ink-faint: #8c8574;
  --line: #3a352a;
  --brand-tan: #d3b191;
  --brand-tan-bg: #332a1e;
  --accent: #d3b191;
  --accent-bg: #332a1e;
  --good: #7ec293;
  --good-bg: #1f2c22;
  --bad: #e18567;
  --bad-bg: #2f1f1a;
}

*{ box-sizing: border-box; }
html, body{ height: 100%; }
/* Stops the browser's own "rubber-band" bounce past the top/bottom of the
   page (most noticeable on iPhone) — without this, scrolling past either
   edge briefly slides the whole page (topbar included) instead of just
   stopping, since sticky elements can't hold still through it. */
html{ overscroll-behavior-y: none; }
body{
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  overscroll-behavior-y: none;
}
button, input, select, textarea{ font-family: inherit; font-size: 1rem; }
textarea{ overflow-wrap: break-word; word-break: break-word; }
a{ color: var(--accent); }

#app{ min-height: 100%; display: flex; flex-direction: column; }
.screen{ flex: 1; display: flex; flex-direction: column; }

/* Three real grid columns rather than a two-item flex row — that's what
   lets the logo sit in a column of its own, dead center in the bar, instead
   of "centered" only by coincidence when the left and right content happen
   to be the same width. minmax(0,1fr) on the outer two columns (rather than
   a bare 1fr) is what actually lets them shrink below their content's
   natural size — a plain 1fr/auto grid track refuses to shrink past its
   content's min size, which is exactly what defeats the title's ellipsis
   truncation below. Equal 1fr on BOTH sides (not just the left) is what
   keeps the center column visually centered even when the title text and
   the icon row are very different widths. */
.topbar{
  height: var(--header-h);
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  gap: 8px;
  padding: 0 14px;
  border-bottom: 1px solid var(--line);
  background: var(--bg-raised);
  /* Was `position: sticky; top: 0;` — correct per spec and worked fine in
     testing, but Firefox has a long-standing bug where a sticky element
     that's a DIRECT CHILD of a `display:flex` container (#app is exactly
     that) can fail to stick at all, silently falling back to normal
     in-flow positioning — so it scrolled away with everything else
     instead of staying put. `position: fixed` sidesteps the whole
     flex-item-sticky interaction: it's positioned against the viewport
     itself, not against #app's flex layout, so there's no ambiguity left
     for any browser to get wrong. See .container's padding-top below —
     taking the topbar out of normal flow means the content underneath
     needs its own reserved space instead of the topbar naturally pushing
     it down. */
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 5;
}
/* The left-hand group (back button/store pic + title block) is the one
   side allowed to shrink — min-width:0 overrides a flex/grid item's default
   "never shrink below my content's natural width," which is what lets the
   ellipsis rules below actually kick in instead of just overflowing (or,
   pre-grid, shoving the rest of the bar around). */
.topbar-left{ display: flex; align-items: center; min-width: 0; }
.topbar-title{ min-width: 0; overflow: hidden; }
.topbar h1{
  font-family: var(--font-display); font-size: 1.1rem; margin: 0; font-weight: 700;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.topbar-store-name{
  font-size: 0.7rem; color: var(--accent); font-weight: 600; line-height: 1.2;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* On a phone-width screen, the title column has real competition for space
   (the centered PAWNALOG logo always claims its own fixed-width column, no
   matter how narrow the bar gets), so a long store name hits the single-
   line ellipsis above much sooner than it would on a wider screen. There's
   plenty of room to grow DOWN instead of sideways, though, so below 480px
   the store name is allowed to wrap onto a second line rather than
   truncating after one — --header-h grows to match (it's what both
   .topbar's own height AND .container's top padding are keyed off, so
   content underneath the now-taller bar still starts in the right place).
   -webkit-line-clamp caps it at 2 lines with its own ellipsis if a name is
   ever long enough to need a third even at two lines' worth of width —
   belt and suspenders alongside the 40-character store-name limit itself.
   ui.js's fitStoreNameToTwoLines() (run from wireChrome, after every
   render) is what actually keeps most long names OFF that ellipsis in the
   first place: it nudges this font-size down, only as far as needed, until
   a name that would've needed a third line fits fully across these two —
   a normal, shorter name never gets touched and stays at the size below. */
@media (max-width: 480px){
  :root{ --header-h: 74px; }
  .topbar-store-name{
    white-space: normal;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
  }
}
/* The center column's actual content — sized to its own column (grid's
   "auto" track just fits it), so it never competes for space with the
   title or the icon row on either side of it. */
.topbar-logo{ height: 28px; display: block; }
/* The icon row (bell/refresh/logout) gets the mirror-image column to the
   title's — its own min-width:0 flexible track — but unlike the title text,
   these buttons don't truncate, so justify-self:end keeps them flush
   against the right edge and lets them push left into their column's
   leftover space (rather than stretching or centering) if it's ever
   narrower than the title side needs. */
.topbar-right{ display: flex; align-items: center; justify-self: end; min-width: 0; }
.topbar .store-pic{
  width: 30px; height: 30px; min-width: 30px; border-radius: 50%;
  background: var(--accent-bg); object-fit: cover; flex-shrink: 0;
  display: inline-block; vertical-align: middle; margin-right: 8px;
}
.icon-btn{
  background: none; border: none; color: var(--ink);
  font-size: 1.3rem; padding: 6px 8px; cursor: pointer; line-height: 1;
}

/* Top/bottom padding now has to reserve real space for the topbar and
   bottom-nav (see their comments above) — now that both are `position:
   fixed` instead of `sticky`, they're out of the normal document flow
   entirely, so nothing pushes this content down from the topbar or stops
   it from sliding under the bottom-nav on its own anymore; this padding is
   what does that job instead. Applied unconditionally (even on the rare
   screen that skips bottomNav(), like Capture) rather than conditionally —
   worst case there's a bit of extra blank space at the bottom of one of
   those, which is harmless, versus the alternative of content actually
   hiding behind a bar that IS present. */
.container{ max-width: 480px; margin: 0 auto; width: 100%; padding: calc(var(--header-h) + 18px) 16px calc(var(--nav-h) + 32px); flex: 1; }
.container.wide{ max-width: 720px; }

/* min-height is set twice on purpose: 100vh first as a fallback for
   browsers that don't understand 100dvh (dvh silently no-ops there,
   leaving the vh value in effect), then 100dvh to override it everywhere
   else. Plain 100vh on mobile Safari measures the tallest the page COULD
   be with the address bar hidden, not the actually-visible area on load
   — so a card centered with align-items:center sits noticeably lower/
   smaller-looking than the visible viewport suggests, exactly the "looks
   small on my iPhone" effect. 100dvh tracks the real visible viewport
   instead, address bar and all. */
.center-screen{ display: flex; align-items: center; justify-content: center; min-height: 100vh; min-height: 100dvh; padding: 24px 16px; }
.card{
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 28px 26px;
  width: 100%;
  max-width: 440px;
}
.card h1{ font-family: var(--font-display); font-size: 1.5rem; margin: 0 0 6px; }
.card .sub{ color: var(--ink-soft); margin: 0 0 20px; font-size: 0.96rem; }

/* Wordmark above the auth-card heading (login/signup/install) — one class
   with size modifiers instead of a one-off inline style per view, so the
   mobile breakpoint below can bump every size of it at once instead of
   needing to know each view's own inline px value. Base sizes here match
   what each view used inline before this was pulled out (44/36/32px). */
.auth-logo{ display: block; margin: 0 auto 16px; height: 44px; }
.auth-logo.sub-logo{ height: 36px; margin-bottom: 14px; }
.auth-logo.mini-logo{ height: 32px; margin-bottom: 14px; }

/* Real mobile pass for the auth screens (login/signup/forgot/accept/
   install's pre-login card, and blockedHours' access-request card —
   anything using .center-screen). Two attempts before this one just made
   the card's own numbers bigger (padding, font-size) and it kept reading
   as "still small" — because the actual problem was never the card's
   internal sizing, it was that a card with visible edges, sitting in the
   middle of a MUCH taller phone screen, always reads as a small floating
   box no matter how much its own padding/fonts grow — see the screenshots:
   the card got bigger each time and the empty space around it just grew
   to match. The fix that actually addresses that: on a phone-width
   screen, the "card" stops being an inset box altogether and becomes the
   full screen itself (edge to edge, no border, no radius, no gap of the
   page's darker --bg showing around it) — its content is then centered
   within that full height via flexbox. There's no separate box left to
   look small; the screen itself is the login form, the way an actual
   native app screen looks. Scoped to ".center-screen .card" (not a bare
   ".card" or ".field") on purpose — .card and .field are both used
   elsewhere (store.js's plan cards, ordinary in-app forms) that should
   NOT change here. */
@media (max-width: 640px){
  .center-screen{
    padding: 0;
    align-items: stretch;
  }
  .center-screen .card{
    max-width: none;
    min-height: 100vh;
    min-height: 100dvh;
    display: flex;
    flex-direction: column;
    justify-content: center;
    border: none;
    border-radius: 0;
    padding: 56px 28px;
  }
  .center-screen .card h1{ font-size: 1.85rem; }
  .center-screen .card .sub{ font-size: 1.05rem; margin-bottom: 26px; }
  .center-screen .card .field{ margin-bottom: 18px; }
  .center-screen .card .field label{ font-size: 0.92rem; margin-bottom: 7px; }
  .center-screen .card .field input,
  .center-screen .card .field select,
  .center-screen .card .field textarea{
    padding: 14px 14px; font-size: 1rem; border-radius: 10px;
  }
  .center-screen .card .btn{ padding: 16px; font-size: 1.05rem; border-radius: 10px; }
  .center-screen .card .hint-text{ font-size: 0.95rem; }
  .auth-logo{ height: 64px; }
  .auth-logo.sub-logo{ height: 52px; }
  .auth-logo.mini-logo{ height: 46px; }
}

.field{ margin-bottom: 14px; }
.field label{ display: block; font-size: 0.82rem; font-weight: 600; color: var(--ink-soft); margin-bottom: 5px; }
.field input, .field select, .field textarea{
  width: 100%; padding: 10px 12px; border: 1px solid var(--line);
  border-radius: 8px; background: var(--bg); color: var(--ink);
}
.field textarea{ resize: vertical; min-height: 70px; overflow-wrap: break-word; word-break: break-word; }
.honeypot{ position: absolute; left: -9999px; width: 1px; height: 1px; opacity: 0; }

/* Dual-handle range slider (Store hours) — two native <input type=range>
   elements stacked on the same track via absolute positioning, each with
   its own visible track hidden (pointer-events:none on the input itself,
   re-enabled only on the round thumb) so both handles stay independently
   draggable without one stealing clicks meant for the other. */
.dual-range{ position: relative; height: 26px; margin: 2px 0 4px; }
.dual-range-track{
  position: absolute; top: 50%; left: 0; right: 0; height: 4px;
  background: var(--line); border-radius: 2px; transform: translateY(-50%);
}
.dual-range-fill{
  position: absolute; top: 50%; height: 4px; background: var(--accent);
  border-radius: 2px; transform: translateY(-50%);
}
.dual-range input[type="range"]{
  position: absolute; top: 0; left: 0; width: 100%; height: 26px; margin: 0;
  -webkit-appearance: none; appearance: none; background: transparent; pointer-events: none;
}
.dual-range input[type="range"]::-webkit-slider-runnable-track{ background: transparent; height: 26px; }
.dual-range input[type="range"]::-moz-range-track{ background: transparent; height: 26px; border: none; }
.dual-range input[type="range"]::-webkit-slider-thumb{
  -webkit-appearance: none; pointer-events: auto; width: 18px; height: 18px; margin-top: 4px;
  border-radius: 50%; background: var(--accent); border: 2px solid var(--bg-raised);
  box-shadow: 0 1px 3px rgba(0,0,0,0.35); cursor: pointer;
}
.dual-range input[type="range"]::-moz-range-thumb{
  pointer-events: auto; width: 18px; height: 18px; border-radius: 50%;
  background: var(--accent); border: 2px solid var(--bg-raised);
  box-shadow: 0 1px 3px rgba(0,0,0,0.35); cursor: pointer;
}

.btn{
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 11px 16px; border-radius: 8px; border: 1px solid var(--line);
  background: var(--bg-raised); color: var(--ink); font-weight: 600;
  cursor: pointer; width: 100%; text-align: center; text-decoration: none;
}
.btn:disabled{ opacity: 0.5; cursor: not-allowed; }
.btn.primary{ background: var(--accent); border-color: var(--accent); color: #fff; }
.btn.danger{ background: var(--bad-bg); border-color: var(--bad); color: var(--bad); }
.btn.ghost{ background: transparent; border-color: transparent; }
.btn.small{ width: auto; padding: 7px 12px; font-size: 0.85rem; }
.btn-row{ display: flex; gap: 10px; }
.btn-row .btn{ flex: 1; }

.error-text{ color: var(--bad); font-size: 0.85rem; margin: 6px 0 0; }
.hint-text{ color: var(--ink-faint); font-size: 0.82rem; margin: 6px 0 0; }
.link-btn{ background: none; border: none; color: var(--accent); font-weight: 600; cursor: pointer; padding: 0; }

.tag{
  display: inline-block; font-size: 0.72rem; font-weight: 700; letter-spacing: 0.03em;
  text-transform: uppercase; padding: 3px 8px; border-radius: 999px;
  background: var(--accent-bg); color: var(--accent);
}
.tag.good{ background: var(--good-bg); color: var(--good); }
.tag.bad{ background: var(--bad-bg); color: var(--bad); }
.tag.faint{ background: var(--line); color: var(--ink-soft); }

.list-row{
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 14px; border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--bg-raised); margin-bottom: 10px;
}
.list-row .meta{ font-size: 0.82rem; color: var(--ink-faint); margin-top: 3px; }
/* Activity Log rows: description text can run to two or three lines (a long
   role name, a long list of ticket numbers, ...) — align-items:flex-start
   (instead of .list-row's default center) keeps the timestamp pinned to the
   top-right corner instead of drifting to the vertical middle of the whole
   row, which used to land it awkwardly beside a wrapped middle line. */
.log-row{ align-items: flex-start; }

.section-title{
  font-size: 0.78rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--ink-faint); margin: 22px 0 10px;
}
.section-title:first-child{ margin-top: 0; }

.toolbar{ display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 14px; }
.toolbar select, .toolbar input{
  padding: 8px 10px; border: 1px solid var(--line); border-radius: 8px;
  background: var(--bg-raised); color: var(--ink); font-size: 0.85rem;
}

.empty-state{ text-align: center; color: var(--ink-faint); padding: 40px 20px; }

.photo-grid{ display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: 8px; margin: 12px 0; }
.photo-grid img{ width: 100%; aspect-ratio: 1; object-fit: cover; border-radius: 8px; border: 1px solid var(--line); }
.photo-cell{ position: relative; }
.photo-cell .remove-x{
  position: absolute; top: 4px; right: 4px; background: rgba(20,19,17,0.7); color: #fff;
  border: none; border-radius: 50%; width: 26px; height: 26px; font-size: 1rem; cursor: pointer;
  /* Without these, the × glyph sat off-center inside the round button —
     a plain <button> lays its text out like inline content (subject to the
     browser's default line-height and the glyph's own vertical metrics),
     not centered in the box the way flex centering guarantees on both
     axes regardless of font quirks. padding:0 + line-height:1 stop those
     defaults from nudging it off-center in the first place. */
  display: flex; align-items: center; justify-content: center; padding: 0; line-height: 1;
}

.camera-wrap{ position: relative; background: #000; border-radius: var(--radius); overflow: hidden; }
.camera-wrap video{ width: 100%; display: block; max-height: 60vh; background: #000; }
.camera-controls{ display: flex; justify-content: center; gap: 16px; padding: 16px 0; }
.shutter-btn{
  width: 68px; height: 68px; border-radius: 50%; border: 4px solid var(--ink);
  background: var(--bg-raised); cursor: pointer;
}

.note-row{ padding: 10px 0; border-bottom: 1px solid var(--line); }
.note-row:last-child{ border-bottom: none; }
.note-row .author{ font-weight: 700; font-size: 0.85rem; }
.note-row .ts{ color: var(--ink-faint); font-size: 0.75rem; margin-left: 6px; }
.note-row p{ margin: 4px 0 0; overflow-wrap: break-word; word-break: break-word; white-space: pre-wrap; }

.bottom-nav{
  /* Same fixed-instead-of-sticky fix as .topbar above, and for the same
     reason (a direct flex child of #app, which is where Firefox's sticky
     bug bites) — see that comment for the full explanation. */
  position: fixed; bottom: 0; left: 0; right: 0; z-index: 5; display: flex; border-top: 1px solid var(--line);
  background: var(--bg-raised); height: var(--nav-h);
}
.bottom-nav a{
  flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-decoration: none; color: var(--ink-faint); font-size: 0.72rem; gap: 2px;
}
.bottom-nav a.active{ color: var(--accent); font-weight: 700; }
.bottom-nav .nav-icon{ font-size: 1.15rem; }

/* A small floating panel docked just above bottom-nav, for an action that
   needs to stay reachable without scrolling past a long, growing list above
   it (today: activity.js's Owner-only "Clean up old log history" — see its
   own comment). Anchored with `bottom` only, no `top`, so opening the
   <details> inside grows the panel upward from the tab bar rather than the
   tab bar (or anything below this panel) ever moving. z-index sits just
   under .bottom-nav's so the tab bar always stays on top of it, never the
   other way around. */
.docked-panel{
  position: fixed; left: 0; right: 0; bottom: var(--nav-h); z-index: 4;
  background: var(--bg-raised); border-top: 1px solid var(--line);
  box-shadow: 0 -2px 10px rgba(20,18,14,0.08);
}
.docked-panel-inner{ max-width: 480px; margin: 0 auto; padding: 10px 16px; }

.role-perms{ display: grid; gap: 8px; margin: 10px 0; }
.role-perms label{ display: flex; align-items: center; gap: 8px; font-size: 0.9rem; }

.role-card{
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--bg-raised); margin-bottom: 10px; overflow: hidden;
}
.role-card summary{
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 14px; cursor: pointer; list-style: none; font-weight: 600;
}
.role-card summary::-webkit-details-marker{ display: none; }
.role-card summary::marker{ content: ""; }
.role-card summary::after{
  content: "\25BE"; color: var(--ink-faint); font-weight: 400; margin-left: 8px;
  transition: transform 0.15s ease; flex-shrink: 0;
}
.role-card[open] summary::after{ transform: rotate(180deg); }
.role-card .role-card-body{ padding: 0 14px 14px; }

.avatar-upload{
  width: 72px; height: 72px; min-width: 72px; flex-shrink: 0; border-radius: 50%; background: var(--accent-bg);
  display: flex; align-items: center; justify-content: center; cursor: pointer;
  border: 1px dashed var(--line); overflow: hidden; margin-bottom: 14px;
  /* "Add photo" wraps onto two lines inside this small a circle — without
     these, each wrapped line stayed left-aligned even though the circle
     itself centers its content as a block, so it read as hugging the left
     edge instead of sitting centered and readable. No padding here on
     purpose — an actual photo needs to fill this circle edge-to-edge (via
     the width:100%/height:100%/object-fit:cover rule below), the same way
     it does in the topbar; padding would inset it and leave a ring of
     background showing, which looked like the photo was cut off. */
  text-align: center; font-size: 0.78rem; line-height: 1.25; color: var(--accent);
  font-weight: 600; text-wrap: balance;
}
.avatar-upload img{ width: 100%; height: 100%; object-fit: cover; }

/* Avatar cropper (see js/cropper.js) — the circle here is a 1:1 preview of
   what gets saved, so the image is positioned with plain left/top + width/
   height (set from JS as the user drags/zooms) rather than object-fit,
   which wouldn't let us track an arbitrary pan/zoom offset. */
.cropper-box{ max-width: 320px; }
.cropper-viewport{
  width: 240px; height: 240px; margin: 4px auto 0; border-radius: 50%;
  overflow: hidden; position: relative; background: var(--line);
  touch-action: none; cursor: grab; border: 1px solid var(--line);
}
.cropper-viewport:active{ cursor: grabbing; }
.cropper-viewport img{
  position: absolute; max-width: none; max-height: none;
  user-select: none; -webkit-user-drag: none; pointer-events: none;
}

.banner{
  padding: 12px 14px; border-radius: var(--radius); margin-bottom: 16px;
  font-size: 0.88rem; border: 1px solid var(--line);
}
.banner.note{ background: var(--accent-bg); border-color: var(--accent); }
.banner.good{ background: var(--good-bg); border-color: var(--good); }

/* In-app confirm/alert/prompt — see the comment above openModal() in ui.js
   for why these replace window.confirm/alert/prompt everywhere. */
.modal-overlay{
  position: fixed; inset: 0; background: rgba(20,18,14,0.55);
  display: flex; align-items: center; justify-content: center;
  padding: 20px; z-index: 100;
}
.modal-box{
  background: var(--bg-raised); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 20px; width: 100%; max-width: 380px;
  box-shadow: 0 12px 32px rgba(0,0,0,0.25);
}
.modal-box h2{ font-family: var(--font-display); font-size: 1.1rem; margin: 0 0 10px; }

/* Small self-dismissing "Saved." confirmation — see showToast() in ui.js.
   Sits above the modal overlay's z-index (100) since a save action inside
   an open modal should still be able to show one. */
.toast{
  position: fixed; left: 50%; bottom: 24px; transform: translateX(-50%) translateY(8px);
  background: var(--ink); color: var(--bg);
  padding: 10px 18px; border-radius: 999px; font-size: 0.88rem; font-weight: 600;
  box-shadow: 0 6px 20px rgba(0,0,0,0.25);
  opacity: 0; transition: opacity 0.2s ease, transform 0.2s ease;
  z-index: 200; pointer-events: none; max-width: calc(100vw - 32px); text-align: center;
}
.toast.show{ opacity: 1; transform: translateX(-50%) translateY(0); }
