/* ═══════════════════════════════════════════════════════════════════════
   CONDUIT ADMIN — design system
   ─────────────────────────────────────────────────────────────────────
   PALETTE SWAP  →  edit the "Raw palette" + "Alpha tints" + "Gradients"
                    sections inside :root. Nothing else should need
                    changing for a full reskin.

   LOGO SWAP     →  admin/static/  holds two variants:
                      logo.png        dark logo (on light surfaces)
                      logo-light.png  light logo (on dark rail bg)
                    Both <img> tags live in admin/templates/layout.html
                    — search for "brand-logo" to find them.
   ═══════════════════════════════════════════════════════════════════════ */

:root {

  /* ── Raw palette ─────────────────────────────────────────────────────
     EDIT ONLY THIS BLOCK to change the full theme.
     Every other :root entry is derived from these values.            */

  /* Surfaces */
  --bg:         #f4f5ee;
  --bg-2:       #fafbf4;
  --panel:      #ffffff;
  --panel-2:    #f2f4e8;

  /* Borders */
  --line:       #e0e2d3;
  --line-soft:  #ebeddf;

  /* Ink (text) */
  --ink:        #282c1a;
  --ink-2:      #565d41;
  --ink-3:      #8b9075;

  /* Brand scale — darkest → lightest */
  --brand-dk:   #424d1d;
  --brand:      #4f5a26;
  --brand-2:    #5e6b2c;
  --brand-lt:   #8a9a52;
  --brand-pale: #eef1e1;

  /* Status */
  --good:       #4d7c3a;
  --warn:       #b07d18;
  --bad:        #b1432d;


  /* ── Alpha tints ─────────────────────────────────────────────────────
     When you swap the palette above, update the RGB values here to
     match your new brand / ink / status hues.                        */

  /* Brand transparency scale */
  --brand-glow:     rgba(95,107,44,.30);   /* timeline dot glow, nav indicator glow */
  --brand-ring:     rgba(95,107,44,.18);   /* focus ring on inputs / dropdowns */
  --brand-hover:    rgba(95,107,44,.06);   /* table row hover tint */
  --brand-pill-bg:  rgba(95,107,44,.13);   /* role pill background */
  --brand-pill-bd:  rgba(95,107,44,.32);   /* role pill border */

  /* Ink transparency scale */
  --ink-scrim:      rgba(40,44,26,.40);    /* modal backdrop + mobile nav scrim */
  --ink-hover:      rgba(40,44,26,.06);    /* nav item hover background */
  --ink-ghost:      rgba(40,44,26,.05);    /* ghost button hover background */

  /* Good (green) */
  --good-pill-bg:   rgba(77,124,58,.12);
  --good-pill-bd:   rgba(77,124,58,.30);
  --good-banner-bg: rgba(77,124,58,.10);
  --good-banner-bd: rgba(77,124,58,.32);

  /* Warn (amber) */
  --warn-pill-bg:   rgba(176,125,24,.13);
  --warn-pill-bd:   rgba(176,125,24,.30);

  /* Bad (red) */
  --bad-pill-bg:    rgba(177,67,45,.10);
  --bad-pill-bd:    rgba(177,67,45,.30);
  --bad-btn-bg:     rgba(177,67,45,.07);
  --bad-btn-bd:     rgba(177,67,45,.32);
  --bad-btn-hover:  rgba(177,67,45,.14);
  --bad-banner-bg:  rgba(177,67,45,.08);
  --bad-banner-bd:  rgba(177,67,45,.30);

  /* Neutral (muted ink-3) */
  --neutral-pill-bg: rgba(139,144,117,.14);
  --neutral-pill-bd: rgba(139,144,117,.30);

  /* Misc */
  --btn-dk-shadow: rgba(20,26,7,.50);    /* primary button drop shadow */
  --btn-dk-hover:  rgba(20,26,7,.55);    /* primary button hover shadow */
  --qr-shadow:     rgba(50,55,25,.50);   /* QR frame drop shadow */
  --topbar-blur-bg: rgba(244,245,238,.88); /* mobile topbar frosted glass */
  --spin-ring:      rgba(255,255,255,.45); /* loading spinner partial ring */


  /* ── Gradients ───────────────────────────────────────────────────────
     All gradient definitions in one place. Swap stops here;
     component rules reference var(--grad-*) only.                    */

  /* Page background: two soft radial blooms over the base surface */
  --grad-body:
    radial-gradient(1200px 600px at 82% -10%, rgba(138,154,82,.20), transparent 60%),
    radial-gradient(900px 520px at -10% 110%, rgba(95,107,44,.12), transparent 55%),
    var(--bg);

  /* Flat panel / card surface — subtle light-to-off-white wash */
  --grad-panel:        linear-gradient(180deg, var(--panel), var(--bg-2));
  /* Sidebar rail */
  --grad-rail:         linear-gradient(185deg, var(--panel) 0%, var(--bg-2) 100%);
  /* "View as merchant" lens banner */
  --grad-viewas:       linear-gradient(90deg, rgba(95,107,44,.16), rgba(95,107,44,.05));
  /* Primary action button fill */
  --grad-btn-primary:  linear-gradient(180deg, #2a360f, #161c07);
  /* HTMX bar loading indicator */
  --grad-bar-loader:   linear-gradient(90deg, transparent, var(--brand-2), transparent);


  /* ── Shadows ─────────────────────────────────────────────────────────*/
  --shadow:    0 1px 0 rgba(255,255,255,.7) inset, 0 8px 24px -16px rgba(50,55,25,.45);
  --shadow-lg: 0 22px 54px -24px rgba(50,55,25,.5);


  /* ── Shape & motion ──────────────────────────────────────────────────*/
  --radius:    14px;
  --radius-sm: 9px;
  --ease:      cubic-bezier(.2,.7,.2,1);
  --t:         .22s var(--ease);


  /* ── Layout ──────────────────────────────────────────────────────────*/
  --rail: 248px;
}

/* ─────────────────────────────────────────────────────────────────────
   Everything below this line is palette-agnostic.
   Do not hardcode color values here — use var(--*) tokens only.
   ───────────────────────────────────────────────────────────────────── */

* { box-sizing: border-box; }

/* Inline icons scale with their text and sit on the baseline; nav/buttons override below. */
.ic { width: 1.05em; height: 1.05em; flex: 0 0 auto; vertical-align: -.16em; }
h1 .ic, h2 .ic, h3 .ic, legend .ic, .crumbs .ic, .section-head .ic { margin-right: .15em; }

html, body { height: 100%; }

body {
  margin: 0;
  font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  color: var(--ink);
  background: var(--grad-body);
  background-attachment: fixed;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  line-height: 1.5;
}

code, .mono, .code { font-family: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, Consolas, monospace; }

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

h1,h2,h3 { margin: 0 0 .2em; line-height: 1.15; letter-spacing: -.02em; font-weight: 650; color: var(--ink); }
h1 { font-size: 1.55rem; }
h2 { font-size: 1.15rem; }
h3 { font-size: .98rem; }
p { margin: .4em 0; }
.muted { color: var(--ink-3); }
.dim   { color: var(--ink-2); }

/* ---------- Brand mark ---------- */
.brand { display: flex; align-items: center; gap: 11px; text-decoration: none; }
.brand:hover { text-decoration: none; }
.brand-logo { height: 30px; width: auto; display: block; }
.brand-logo.sm { height: 24px; }

/* ============================================================= AUTH ===== */
.auth-wrap { min-height: 100dvh; display: grid; place-items: center; padding: 24px; }
.auth-card {
  width: 100%; max-width: 400px; background: var(--grad-panel);
  border: 1px solid var(--line); border-radius: var(--radius); padding: 34px 30px;
  box-shadow: var(--shadow-lg); animation: rise .5s var(--ease) both;
}
.auth-card .brand { margin-bottom: 22px; }
.auth-card h1 { font-size: 1.3rem; }
.auth-sub { color: var(--ink-3); margin: 0 0 22px; font-size: .92rem; }
.auth-foot { margin-top: 18px; font-size: .88rem; display: flex; justify-content: space-between; }

/* ============================================================= SHELL ==== */
.app { display: grid; grid-template-columns: var(--rail) 1fr; min-height: 100dvh; }

.rail {
  position: sticky; top: 0; align-self: start; height: 100dvh;
  display: flex; flex-direction: column; gap: 4px;
  padding: 20px 14px; border-right: 1px solid var(--line);
  background: var(--grad-rail);
  color: var(--ink);
}
.rail .brand { padding: 4px 8px 18px; }
.rail nav { display: flex; flex-direction: column; gap: 2px; }
.navlink {
  display: flex; align-items: center; gap: 11px; padding: 11px 12px; min-height: 42px;
  border-radius: 10px; color: var(--ink-2); font-weight: 540; font-size: .92rem;
  transition: background var(--t), color var(--t), transform var(--t); position: relative;
}
.navlink .ic { width: 18px; height: 18px; opacity: .8; }
.navlink:hover { background: var(--ink-hover); color: var(--ink); text-decoration: none; }
.navlink.active { background: var(--brand-pale); color: var(--brand); font-weight: 620; }
.navlink.active .ic { opacity: 1; }
.navlink.active::before {
  content: ""; position: absolute; left: -14px; top: 8px; bottom: 8px; width: 3px;
  border-radius: 0 3px 3px 0; background: var(--brand-2); box-shadow: 0 0 10px var(--brand-glow);
}
.rail .spacer { flex: 1; }
.rail .who {
  border-top: 1px solid var(--line); padding-top: 12px; margin-top: 8px;
  font-size: .82rem; color: var(--ink-3);
}
.rail .who .email { color: var(--ink); font-weight: 560; word-break: break-all; }
.rail .who .btn.ghost { color: var(--ink-2); border-color: var(--line); background: transparent; }
.rail .who .btn.ghost:hover { background: var(--ink-ghost); border-color: var(--ink-3); }

.main { min-width: 0; display: flex; flex-direction: column; }
/* "View as merchant" lens banner — sits below the topbar, above content. */
.viewas-bar {
  display: flex; align-items: center; gap: 10px; padding: 11px clamp(16px, 2.4vw, 26px);
  background: var(--grad-viewas);
  border-bottom: 1px solid var(--line); color: var(--ink); font-size: .9rem; flex-wrap: wrap;
}
.viewas-bar .ic { color: var(--brand); }
/* Topbar is mobile-only — desktop relies on the rail + each page's own heading. */
.topbar { display: none; }
.topbar .grow { flex: 1; }
/* Fluid horizontal padding: tighter on small viewports, generous on wide ones, but
   capped so content never feels lost on ultrawide displays. */
.content { padding: clamp(18px, 2.4vw, 30px); max-width: 1180px; width: 100%; margin: 0 auto; min-width: 0;
           container-type: inline-size; container-name: content; }

/* ---------- responsive shell ----------
   The rail is a fixed-width grid column that "eats" horizontal space, so the usable
   content width is always (viewport − rail). We override the --rail token inside media
   queries rather than rewriting the grid, so the off-canvas drawer logic below is untouched.

   Tablet-landscape / small-laptop (861–1100): shrink the rail so the content column
   isn't cramped while the persistent nav is still shown. */
@media (min-width: 861px) and (max-width: 1100px) {
  :root { --rail: 212px; }
}

/* ---------- mobile nav ---------- */
.scrim { display: none; }
/* Phones + tablet-portrait (≤860): rail collapses to an off-canvas drawer toggled by
   body.nav-open; the mobile topbar (hamburger + small logo) takes over. */
@media (max-width: 860px) {
  .app { grid-template-columns: 1fr; }
  .rail {
    position: fixed; z-index: 60; left: 0; top: 0; width: min(270px, 84vw); height: 100dvh;
    transform: translateX(-102%); transition: transform .28s var(--ease); box-shadow: var(--shadow-lg);
    overflow-y: auto;
  }
  body.nav-open .rail { transform: translateX(0); }
  body.nav-open .scrim {
    display: block; position: fixed; inset: 0; z-index: 50; background: var(--ink-scrim);
    animation: fade .2s var(--ease) both;
  }
  .topbar {
    display: flex; align-items: center; gap: 14px; position: sticky; top: 0; z-index: 20;
    padding: 11px 16px; border-bottom: 1px solid var(--line);
    background: var(--topbar-blur-bg); backdrop-filter: blur(12px);
  }
  .topbar .menu-btn {
    display: inline-grid; place-items: center; width: 40px; height: 40px;
    border-radius: 10px; border: 1px solid var(--line); background: var(--panel); color: var(--ink);
    cursor: pointer;
  }
}

/* ============================================================= CARDS ==== */
.card {
  background: var(--grad-panel);
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: 20px; box-shadow: var(--shadow);
}
.card + .card { margin-top: 18px; }
.card h2 { display: flex; align-items: center; flex-wrap: wrap; gap: 9px; min-width: 0; }
.card h2 > * { min-width: 0; }

/* ---------- modals ----------
   The new-merchant / new-workstation / new-admin <dialog>s are sized inline (width:92vw,
   max-width:…). Here we cap their height and let them scroll so a tall form never overflows
   a short or landscape phone, and centre them in the viewport. */
dialog.card {
  margin: auto; max-height: min(90dvh, 760px); overflow: auto;
  inset: 0;
  /* Become a container so field-rows inside the dialog size to the dialog, not the page. */
  container-type: inline-size; container-name: content;
}
dialog.card::backdrop { background: var(--ink-scrim); }
.card-grid { display: grid; gap: 18px; }
/* Two big form columns (merchant detail identity+payment, workstation detail config+activity,
   QR + device facts) only split when the *content area itself* — not the viewport — is wide
   enough that neither column gets squished. Keying off the .content container width (rather
   than viewport) means the rail eating ~210–248px no longer cramps the columns: single column
   on tablet/small-laptop, two columns once there's genuinely room. */
@container content (min-width: 780px) {
  .card-grid.cols-2 { grid-template-columns: 1fr 1fr; }
}
/* Fallback for engines without container query support: split at a viewport width that
   guarantees a roomy content column even with the widest (248px) rail visible. */
@supports not (container-type: inline-size) {
  @media (min-width: 1040px) { .card-grid.cols-2 { grid-template-columns: 1fr 1fr; } }
}

/* Page headers wrap so a long title + a trailing action button (New merchant/admin) stack
   on narrow screens instead of overflowing. */
.section-head { display: flex; align-items: center; gap: 12px; margin-bottom: 18px; flex-wrap: wrap; }
.section-head h1 { margin: 0; min-width: 0; overflow-wrap: anywhere; }
.section-head .grow { flex: 1; min-width: 0; }
.section-head .btn { flex: 0 0 auto; }

/* ---------- stat tiles ---------- */
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px,1fr)); gap: 14px; }
.stat {
  background: var(--grad-panel);
  border: 1px solid var(--line); border-radius: var(--radius); padding: 18px 18px 16px;
  position: relative; overflow: hidden; transition: transform var(--t), border-color var(--t), box-shadow var(--t);
}
.stat:hover { transform: translateY(-2px); border-color: var(--brand-lt); box-shadow: var(--shadow); }
.stat .k { font-size: .78rem; text-transform: uppercase; letter-spacing: .07em; color: var(--ink-3); }
.stat .v { font-size: 2rem; font-weight: 700; letter-spacing: -.03em; margin-top: 4px; color: var(--ink); }
.stat.accent .v { color: var(--brand); }
.stat.good .v { color: var(--good); }
.stat.warn .v { color: var(--warn); }

/* ============================================================= TABLES === */
.table-wrap { overflow-x: auto; border-radius: var(--radius); }
table.grid { width: 100%; border-collapse: collapse; font-size: .92rem; }
table.grid th {
  text-align: left; font-weight: 600; color: var(--ink-3); font-size: .76rem;
  text-transform: uppercase; letter-spacing: .06em; padding: 10px 14px; border-bottom: 1px solid var(--line);
}
table.grid td { padding: 13px 14px; border-bottom: 1px solid var(--line-soft); vertical-align: middle; }
table.grid tbody tr { transition: background var(--t); }
table.grid tbody tr:hover { background: var(--brand-hover); }
table.grid tbody tr:last-child td { border-bottom: 0; }
.row-link { cursor: pointer; }

/* Long, unbreakable strings (emails, device IDs, user-agents) must never force overflow. */
table.grid td { overflow-wrap: anywhere; }
table.grid td .mono { overflow-wrap: anywhere; }

@media (max-width: 680px) {
  table.grid, table.grid thead, table.grid tbody, table.grid th, table.grid td, table.grid tr { display: block; }
  table.grid thead { display: none; }
  table.grid tr {
    background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius-sm);
    margin-bottom: 12px; padding: 6px 4px;
  }
  table.grid td { border: 0; padding: 8px 14px; display: flex; justify-content: space-between; gap: 16px; }
  /* The value side of each stacked row gets a min-width:0 so it can shrink/wrap instead of
     pushing the card wider; the data-label cap keeps a sensible flex split. */
  table.grid td > * { min-width: 0; }
  table.grid td::before {
    content: attr(data-label); color: var(--ink-3); font-size: .76rem; text-transform: uppercase;
    letter-spacing: .05em; font-weight: 600; align-self: center; flex: 0 0 auto;
  }
  /* The session "Client" column is given a desktop max-width inline; drop it when stacked so
     the user-agent string can use the full card width and wrap. */
  table.grid td[style*="max-width"] { max-width: none !important; }
  table.grid td:empty { display: none; }
}

/* ============================================================= PILLS ==== */
.pill {
  display: inline-flex; align-items: center; gap: 6px; padding: 3px 10px; border-radius: 999px;
  font-size: .76rem; font-weight: 600; border: 1px solid transparent; white-space: nowrap;
}
.pill .dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; }
.pill.on   { color: var(--good); background: var(--good-pill-bg);    border-color: var(--good-pill-bd); }
.pill.off  { color: var(--ink-3); background: var(--neutral-pill-bg); border-color: var(--neutral-pill-bd); }
.pill.pend { color: var(--warn); background: var(--warn-pill-bg);    border-color: var(--warn-pill-bd); }
.pill.role { color: var(--brand); background: var(--brand-pill-bg);  border-color: var(--brand-pill-bd); }
.pill.bad  { color: var(--bad);  background: var(--bad-pill-bg);     border-color: var(--bad-pill-bd); }

.tag-code {
  display: inline-block; max-width: 100%; overflow-wrap: anywhere;
  font-family: ui-monospace, Menlo, monospace; font-size: .86rem; letter-spacing: .02em;
  background: var(--brand-pale); border: 1px solid var(--line); padding: 3px 9px; border-radius: 7px; color: var(--brand);
}

/* ============================================================= FORMS ==== */
form .field { margin-bottom: 15px; }
label { display: block; font-size: .82rem; color: var(--ink-2); margin-bottom: 6px; font-weight: 560; }
.req::after { content: " *"; color: var(--bad); }
input, select, textarea {
  width: 100%; padding: 11px 13px; border-radius: var(--radius-sm);
  background: var(--bg-2); border: 1px solid var(--line); color: var(--ink);
  font: inherit; font-size: .94rem; transition: border-color var(--t), box-shadow var(--t), background var(--t);
}
input:hover, select:hover, textarea:hover { border-color: var(--brand-lt); }
input:focus, select:focus, textarea:focus {
  outline: none; border-color: var(--brand-2); background: var(--panel);
  box-shadow: 0 0 0 3px var(--brand-ring);
}
input::placeholder { color: var(--ink-3); }
/* Field rows go multi-column based on the .content container width so that forms nested
   inside a half-width .card-grid column don't split into squished 2/3-up fields. The
   thresholds account for the worst case (a form sharing a two-column grid). */
.field-row { display: grid; gap: 14px; }
@container content (min-width: 620px) {
  .field-row.two { grid-template-columns: 1fr 1fr; }
  .field-row.three { grid-template-columns: 1fr 1fr 1fr; }
}
/* Fallback when container queries are unavailable: keep the original viewport behaviour. */
@supports not (container-type: inline-size) {
  @media (min-width: 560px) { .field-row.two { grid-template-columns: 1fr 1fr; } .field-row.three { grid-template-columns: 1fr 1fr 1fr; } }
}
.hint { font-size: .78rem; color: var(--ink-3); margin-top: 5px; }
fieldset { border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 16px; margin: 0 0 16px; }
legend { padding: 0 8px; color: var(--ink-2); font-size: .82rem; font-weight: 600; text-transform: uppercase; letter-spacing: .05em; }

.secret-row { display: flex; gap: 8px; align-items: stretch; }
.secret-row input { flex: 1; }

/* ============================================================= BUTTONS == */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 10px 16px; border-radius: var(--radius-sm); border: 1px solid var(--line);
  background: var(--panel); color: var(--ink); font: inherit; font-weight: 580; font-size: .9rem;
  cursor: pointer; transition: transform var(--t), background var(--t), border-color var(--t), box-shadow var(--t);
  white-space: nowrap; text-decoration: none;
}
.btn:hover { background: var(--brand-pale); border-color: var(--brand-lt); text-decoration: none; }
.btn:active { transform: translateY(1px); }
.btn .ic { width: 16px; height: 16px; }
.btn.primary {
  background: var(--grad-btn-primary); border-color: transparent; color: var(--panel);
  box-shadow: 0 8px 20px -10px var(--btn-dk-shadow);
}
.btn.primary:hover { filter: brightness(1.25); box-shadow: 0 10px 24px -8px var(--btn-dk-hover); }
.btn.ghost { background: transparent; }
.btn.danger { color: var(--bad); border-color: var(--bad-btn-bd); background: var(--bad-btn-bg); }
.btn.danger:hover { background: var(--bad-btn-hover); }
.btn.sm { padding: 7px 11px; font-size: .82rem; }
.btn.block { width: 100%; }
.btn:disabled { opacity: .55; cursor: not-allowed; }
.btn-row { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }

/* On touch devices, give the compact .sm buttons (table row actions, pager, filter) a
   comfortable tap height without bloating the dense desktop UI. */
@media (pointer: coarse) {
  .btn.sm { min-height: 40px; }
}

/* ---------- HTMX loading indicator ---------- */
.htmx-indicator { opacity: 0; transition: opacity var(--t); }
.htmx-request .htmx-indicator, .htmx-request.htmx-indicator { opacity: 1; }
.btn.htmx-request { pointer-events: none; opacity: .8; }
.spin { width: 15px; height: 15px; border: 2px solid var(--spin-ring); border-top-color: var(--panel); border-radius: 50%; animation: spin .7s linear infinite; }
.bar-loader { height: 2px; background: var(--grad-bar-loader); background-size: 40% 100%; animation: slide 1s var(--ease) infinite; border-radius: 2px; }

/* ============================================================= QR ======= */
.qr-panel { display: flex; flex-direction: column; align-items: center; gap: 12px; text-align: center; min-width: 0; }
.qr-frame {
  background: var(--panel); padding: 14px; border-radius: var(--radius-sm); border: 1px solid var(--line);
  box-shadow: 0 14px 36px -18px var(--qr-shadow); animation: pop .4s var(--ease) both;
  width: max-content; max-width: 100%;
}
/* QR is a square that scales down to fit its column/card (min of 200px or available width
   minus the frame padding) — keeps it from overflowing the panel on narrow screens. */
.qr-frame img, .qr-frame svg { display: block; width: 200px; height: auto; aspect-ratio: 1 / 1; max-width: min(200px, 70vw); }
.qr-code { font-size: 1.4rem; font-weight: 700; letter-spacing: .04em; color: var(--brand); font-family: ui-monospace, Menlo, monospace; overflow-wrap: anywhere; max-width: 100%; }

/* ============================================================= TOASTS === */
#toasts { position: fixed; right: 18px; bottom: 18px; z-index: 200; display: flex; flex-direction: column; gap: 10px; max-width: 360px; }
.toast {
  background: var(--panel); border: 1px solid var(--line); border-left: 3px solid var(--brand-2);
  border-radius: 10px; padding: 12px 15px; box-shadow: var(--shadow-lg);
  animation: toastIn .35s var(--ease) both; font-size: .9rem; color: var(--ink);
}
.toast.good { border-left-color: var(--good); }
.toast.bad  { border-left-color: var(--bad); }
.toast.fade-out { animation: toastOut .3s var(--ease) forwards; }

/* ---------- misc ---------- */
.empty { text-align: center; padding: 46px 20px; color: var(--ink-3); }
.empty .big { font-size: 2.4rem; opacity: .45; margin-bottom: 8px; }
.banner { padding: 11px 14px; border-radius: var(--radius-sm); font-size: .9rem; margin-bottom: 16px; border: 1px solid; }
.banner.err { color: var(--bad);  background: var(--bad-banner-bg);  border-color: var(--bad-banner-bd); }
.banner.ok  { color: var(--good); background: var(--good-banner-bg); border-color: var(--good-banner-bd); }
.crumbs { display: flex; align-items: center; flex-wrap: wrap; gap: 4px 8px; font-size: .86rem; color: var(--ink-3); margin-bottom: 14px; overflow-wrap: anywhere; }
.crumbs a { color: var(--ink-2); }
.kv { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 8px 18px; font-size: .9rem; align-items: center; }
.kv dt { color: var(--ink-3); }
.kv dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }
.timeline { list-style: none; margin: 0; padding: 0; }
.timeline li { position: relative; padding: 0 0 16px 22px; border-left: 1px solid var(--line); }
.timeline li:last-child { border-left-color: transparent; padding-bottom: 0; }
.timeline li::before { content: ""; position: absolute; left: -4px; top: 4px; width: 7px; height: 7px; border-radius: 50%; background: var(--brand-2); box-shadow: 0 0 8px var(--brand-glow); }
.timeline .t-type { font-weight: 600; font-size: .88rem; color: var(--ink); }
.timeline .t-meta { color: var(--ink-3); font-size: .8rem; }
.timeline a.t-link { display: block; color: inherit; text-decoration: none; margin: -5px -8px; padding: 5px 8px; border-radius: 8px; transition: background var(--t); }
.timeline a.t-link:hover { background: var(--brand-pale); text-decoration: none; }
.timeline a.t-link::after { content: "›"; float: right; color: var(--ink-3); opacity: 0; transition: opacity var(--t); }
.timeline a.t-link:hover::after { opacity: 1; }

/* ============================================================= ANIM ===== */
@keyframes rise   { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }
@keyframes fade   { from { opacity: 0; } to { opacity: 1; } }
@keyframes pop    { from { opacity: 0; transform: scale(.92); } to { opacity: 1; transform: none; } }
@keyframes spin   { to { transform: rotate(360deg); } }
@keyframes slide  { from { background-position: -40% 0; } to { background-position: 140% 0; } }
@keyframes toastIn  { from { opacity: 0; transform: translateY(12px) scale(.98); } to { opacity: 1; transform: none; } }
@keyframes toastOut { to { opacity: 0; transform: translateX(20px); } }

.htmx-added { animation: fade .3s var(--ease) both; }
.fragment-swap.htmx-swapping { opacity: 0; transition: opacity .12s var(--ease); }

/* View Transitions crossfade content swaps. The rail is identical across swaps, so the
   default root crossfade reads as the content gently fading/sliding into place. */
::view-transition-old(root) { animation: vtOut .15s var(--ease) both; }
::view-transition-new(root) { animation: vtIn .28s var(--ease) both; }
@keyframes vtOut { to { opacity: 0; } }
@keyframes vtIn  { from { opacity: 0; } }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .001ms !important; animation-iteration-count: 1 !important; transition-duration: .001ms !important; }
  ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation: none !important; }
}

/* --- pagination + merchant groups (workstations/merchants lists) --- */
/* Pager stays centered and wraps; Prev/Next get a comfortable touch height and never overflow.
   The empty <span> placeholders keep Prev/Next on the outer edges with the count centered. */
.pager { display: flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: 10px 14px; margin-top: 18px; }
.pager .page-info { color: var(--ink-3); font-size: .85rem; text-align: center; }
.pager .btn { min-height: 40px; }

.group { margin-bottom: 26px; }
/* group-head is merchant name + prefix tag + a .grow spacer + count. It must wrap: on narrow
   screens the count drops to the next line rather than squeezing or overflowing. min-width:0
   on the name link lets a long merchant name shrink/ellipsis instead of pushing siblings out. */
.group-head {
  display: flex; align-items: center; flex-wrap: wrap; gap: 6px 10px;
  padding: 0 2px 10px; margin-bottom: 12px;
  border-bottom: 1px solid var(--line);
}
.group-head .g-name { color: var(--ink); min-width: 0; overflow-wrap: anywhere; }
.group-head .grow { flex: 1 1 0; min-width: 0; }
.group-head .g-count { color: var(--ink-3); font-size: .82rem; flex: 0 0 auto; white-space: nowrap; }
.group .g-empty { color: var(--ink-3); font-size: .88rem; padding: 4px 2px 2px; }

/* ---------- Dropdown component ----------
   Custom replacement for native <select>. Sizes exactly like the form inputs
   (full width of its .field, same height/padding/radius/border/background/focus
   ring) so it never looks native or "weird sizes". JS lives in dropdown.js. */
.dropdown { position: relative; width: 100%; }

.dropdown-btn {
  /* Match input metrics: same padding, radius, border, background, font. */
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  width: 100%; padding: 11px 13px; border-radius: var(--radius-sm);
  background: var(--bg-2); border: 1px solid var(--line); color: var(--ink);
  font: inherit; font-size: .94rem; text-align: left; cursor: pointer;
  transition: border-color var(--t), box-shadow var(--t), background var(--t);
}
.dropdown-btn:hover { border-color: var(--brand-lt); }
.dropdown-btn:focus-visible,
.dropdown.open .dropdown-btn {
  outline: none; border-color: var(--brand-2); background: var(--panel);
  box-shadow: 0 0 0 3px var(--brand-ring);
}
.dropdown-value { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.dropdown-caret {
  flex: 0 0 auto; width: 18px; height: 18px; color: var(--ink-3);
  transition: transform var(--t);
}
.dropdown.open .dropdown-caret { transform: rotate(180deg); }

.dropdown-menu {
  position: absolute; left: 0; right: 0; top: calc(100% + 6px); z-index: 40;
  margin: 0; padding: 5px; list-style: none;
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius-sm);
  box-shadow: var(--shadow-lg);
  max-height: 260px; overflow-y: auto;
  animation: ddIn .14s var(--ease) both;
}
.dropdown-menu[hidden] { display: none; }
@keyframes ddIn { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) {
  .dropdown-menu { animation: none; }
  .dropdown-caret { transition: none; }
}

.dropdown-opt {
  padding: 9px 11px; border-radius: 7px; font-size: .92rem; color: var(--ink);
  cursor: pointer; outline: none; user-select: none;
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  transition: background var(--t), color var(--t);
}
.dropdown-opt:hover,
.dropdown-opt:focus { background: var(--brand-pale); }
.dropdown-opt[aria-selected="true"] {
  color: var(--brand); font-weight: 600; background: var(--brand-pale);
}
/* Check mark on the selected option. */
.dropdown-opt[aria-selected="true"]::after {
  content: ""; flex: 0 0 auto; width: 15px; height: 15px;
  background: currentColor;
  -webkit-mask: no-repeat center / contain url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12l5 5L20 7'/%3E%3C/svg%3E");
  mask: no-repeat center / contain url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12l5 5L20 7'/%3E%3C/svg%3E");
}

/* ── OPI request trace (trace.html) — standalone full-screen logs view ──── */
.trace-body { margin: 0; background: var(--bg); }
.trace-page { height: 100vh; display: flex; flex-direction: column; padding: 14px 18px; box-sizing: border-box; }
.trace-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 12px; }
.trace-head strong { font-size: 1.05rem; }
.trace-filters { display: flex; gap: 10px; flex-wrap: wrap; align-items: flex-end; }

/* Two-pane inspector that fills the remaining height: scrollable list left, detail right. */
.trace-split { flex: 1; min-height: 0; display: grid; grid-template-columns: minmax(0,1.1fr) minmax(0,1fr); gap: 16px; }
@media (max-width: 900px) { .trace-split { grid-template-columns: 1fr; } }
.trace-list { height: 100%; overflow: auto; border: 1px solid var(--line); border-radius: 12px; }
.trace-table { width: 100%; }
.trace-table thead th { position: sticky; top: 0; background: var(--panel); z-index: 1; }
.trace-table tbody tr { transition: background .12s; }
.trace-table tbody tr:hover { background: var(--brand-pale); }
.trace-uri { max-width: 280px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.trace-detail-pane { border: 1px solid var(--line); border-radius: 12px; padding: 16px; height: 100%; overflow: auto; }
.trace-detail-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.trace-section { margin-top: 16px; }
.trace-section-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 8px; }
.trace-headers { margin-bottom: 8px; }
.trace-headers summary { cursor: pointer; color: var(--ink-2); font-size: .85rem; }
.trace-headers table { margin-top: 6px; }

.trace-toggle { display: inline-flex; align-items: center; gap: 6px; margin-bottom: 8px; font-size: .82rem; color: var(--ink-2); cursor: pointer; }
.trace-pre {
  margin: 0; padding: 12px; border-radius: 10px; background: var(--bg-2);
  border: 1px solid var(--line-soft); overflow: auto; max-height: 46vh;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .8rem; line-height: 1.5;
  white-space: pre-wrap; word-break: break-word;
}
/* Pretty by default; the per-section "Show raw bytes" checkbox flips to the raw <pre>. */
.trace-pre[data-view="raw"] { display: none; }
.trace-section:has(.trace-raw-toggle:checked) .trace-pre[data-view="pretty"] { display: none; }
.trace-section:has(.trace-raw-toggle:checked) .trace-pre[data-view="raw"] { display: block; }
