/* WhiteFang — styles for the merchant + discovery pages (/merchants, /map).
   Builds on tokens.css; assumes base.css and components.css are loaded first. */

/* ── Layout ────────────────────────────────────────────────────────────────*/
.wf-shell { min-height: 70vh; padding: var(--sp-12) 0 var(--sp-20); }
.wf-narrow { max-width: 640px; margin: 0 auto; }
.wf-wide   { max-width: 1100px; margin: 0 auto; }

.wf-page-head { margin-bottom: var(--sp-8); }
.wf-page-head h1 { font-size: var(--fs-h1); letter-spacing: var(--ls-heading); margin-bottom: var(--sp-3); }
.wf-page-head p  { color: var(--text-secondary); line-height: var(--lh-body); }

/* ── Cards ─────────────────────────────────────────────────────────────────*/
.wf-card {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--sp-6);
  box-shadow: var(--shadow-card);
}
.wf-card + .wf-card { margin-top: var(--sp-5); }
/* Nudges sit in the same stack as the cards and keep the same rhythm. */
.wf-card + .dash-nudge,
.dash-nudge + .dash-nudge,
.dash-nudge + .wf-card { margin-top: var(--sp-5); }
.wf-card-title {
  font-size: var(--fs-h4); font-weight: 700; margin-bottom: var(--sp-2);
  display: flex; align-items: center; gap: var(--sp-2);
}
.wf-card-sub { color: var(--text-tertiary); font-size: var(--fs-body-sm); line-height: var(--lh-body); }

/* ── Forms ─────────────────────────────────────────────────────────────────*/
.wf-field { margin-bottom: var(--sp-5); }
.wf-field label {
  display: block; font-size: var(--fs-body-sm); font-weight: 600;
  color: var(--text-secondary); margin-bottom: var(--sp-2);
}
.wf-field .wf-hint {
  font-size: var(--fs-body-sm); color: var(--text-tertiary);
  margin-top: var(--sp-2); line-height: 1.5;
}
.wf-field input[type=text], .wf-field input[type=email], .wf-field input[type=url],
.wf-field input[type=tel], .wf-field input[type=number], .wf-field input[type=search],
.wf-field select, .wf-field textarea {
  width: 100%;
  background: var(--bg-input);
  border: 1px solid var(--border-med);
  border-radius: var(--radius);
  color: var(--text-primary);
  padding: 11px 13px;
  font-size: var(--fs-body);
  transition: border-color var(--duration) var(--ease-out);
}
.wf-field input:focus, .wf-field select:focus, .wf-field textarea:focus {
  outline: none; border-color: var(--accent-text); box-shadow: 0 0 0 3px var(--accent-dim);
}
.wf-field textarea { min-height: 96px; resize: vertical; line-height: var(--lh-body); }
.wf-field input::placeholder, .wf-field textarea::placeholder { color: var(--text-tertiary); }
.wf-field select option { background: var(--bg-card); color: var(--text-primary); }
.wf-field--invalid input, .wf-field--invalid select, .wf-field--invalid textarea { border-color: var(--status-danger); }
.wf-error { color: var(--ink-red); font-size: var(--fs-body-sm); margin-top: var(--sp-2); }
.wf-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-4); }
.wf-row--3 { grid-template-columns: 2fr 1fr 1fr; }
@media (max-width: 640px) { .wf-row, .wf-row--3 { grid-template-columns: 1fr; } }

.wf-charcount { float: right; font-weight: 400; color: var(--text-tertiary); }

/* ── Address picker ────────────────────────────────────────────────────────*/
.wf-addr-wrap { position: relative; }
.wf-addr-results {
  position: absolute; z-index: 40; left: 0; right: 0; top: calc(100% + 4px);
  background: var(--bg-elevated); border: 1px solid var(--border-med);
  border-radius: var(--radius); box-shadow: var(--shadow-dropdown);
  max-height: 280px; overflow-y: auto;
}
.wf-addr-results:empty { display: none; }
.wf-addr-item {
  display: block; width: 100%; text-align: left;
  padding: 10px 13px; font-size: var(--fs-body-sm); color: var(--text-secondary);
  border-bottom: 1px solid var(--border-sub); cursor: pointer; line-height: 1.45;
}
.wf-addr-item:last-child { border-bottom: 0; }
.wf-addr-item:hover, .wf-addr-item:focus { background: var(--surface-2); color: var(--text-primary); outline: none; }
.wf-addr-status { font-size: var(--fs-body-sm); color: var(--text-tertiary); margin-top: var(--sp-2); }

/* ── Logo picker ───────────────────────────────────────────────────────────*/
.wf-logo-row { display: flex; align-items: center; gap: var(--sp-4); }
.wf-logo-preview {
  width: 72px; height: 72px; border-radius: var(--radius-md); flex-shrink: 0;
  background: var(--surface-2); border: 1px solid var(--border-med);
  object-fit: cover; display: block;
}
.wf-logo-actions { display: flex; flex-wrap: wrap; gap: var(--sp-2); }

/* ── Steps ─────────────────────────────────────────────────────────────────*/
/* ── The join flow's own bar ─────────────────────────────────────────────────
   /merchants/join carries no site header and no footer: it is a form a shop is
   part-way through, and a nav bar full of other places to be is an invitation
   to abandon it. This is the whole of the chrome — the mark, which goes back to
   /merchants, and one way out beside it. */
.join-bar {
  background: var(--bg-dark);
  border-bottom: 1px solid var(--border);
}
.join-bar-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  min-height: 56px;
}
.join-bar-exit {
  font-size: var(--fs-body-sm);
  font-weight: 600;
  color: var(--text-tertiary);
  text-decoration: none;
}
.join-bar-exit:hover { color: var(--text-primary); }
.join-bar-exit:focus-visible { outline: var(--focus-ring); outline-offset: 3px; }

/* ── The dashboard app bar ─────────────────────────────────────────────────
   /merchants/dashboard is an app, not a page of the site, so it does not take
   the site header or the footer. What the merchant needs from chrome is the
   state of their listing and a way out — everything else is behind the account
   button, including the legal links and the theme switch the footer used to
   carry. */
.dash-bar {
  position: sticky;
  top: 0;
  z-index: 60;
  background: var(--bg-dark);
  border-bottom: 1px solid var(--border);
}
.dash-bar-inner {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  min-height: 56px;
  max-width: var(--container-max);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.dash-logo { display: flex; align-items: center; flex-shrink: 0; }
/* The <img> also carries .nav-logo-img, which is where the dark-mode inversion
   lives (components.css). Without it the wordmark stays dark-on-dark and only
   the wolf reads. This rule is more specific, so the bar keeps its own height. */
.dash-logo img { height: 24px; width: auto; display: block; }
.dash-logo:focus-visible { outline: var(--focus-ring); outline-offset: 4px; border-radius: 4px; }

/* The four tabs, between the logo and the controls. Hidden until there is a
   listing: before that the dashboard is one setup form and the other three
   would save into nothing. On a phone they scroll sideways rather than wrap,
   because a bar that grows to two rows pushes the page down every time. */
.dash-tabs {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
  min-width: 0;
  margin-left: var(--sp-6);
  overflow-x: auto;
  scrollbar-width: none;
}
.dash-tabs::-webkit-scrollbar { display: none; }
.dash-tabs[hidden] { display: none; }
.dash-tab {
  padding: 6px 10px;
  border-radius: var(--radius-sm);
  font-size: var(--fs-body-sm);
  font-weight: 600;
  color: var(--text-tertiary);
  text-decoration: none;
  white-space: nowrap;
}
.dash-tab:hover { color: var(--text-primary); background: var(--surface-2); }
.dash-tab:focus-visible { outline: var(--focus-ring); outline-offset: 2px; }
.dash-tab.is-active { color: var(--text-primary); background: var(--surface-2); }

/* Everything on the right of the bar, pushed there by the logo's auto margin
   so a tab row can sit between them without this changing. */
.dash-bar-right {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-left: auto;
  flex-shrink: 0;
}

/* ── Bar menus: status and account ─────────────────────────────────────────*/
.dash-menu { position: relative; }

.dash-status-btn,
.dash-avatar {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  padding: 3px 8px 3px 4px;
  cursor: pointer;
  color: var(--text-secondary);
  font: inherit;
}
.dash-status-btn:hover,
.dash-avatar:hover { border-color: var(--border-med); }
.dash-status-btn:focus-visible,
.dash-avatar:focus-visible { outline: var(--focus-ring); outline-offset: 2px; }
.dash-caret { flex-shrink: 0; transition: transform var(--duration) var(--ease-out); }
.dash-status-btn[aria-expanded="true"] .dash-caret { transform: rotate(180deg); }

.dash-avatar {
  justify-content: center;
  width: 32px; height: 32px;
  padding: 0;
  border-color: var(--border-med);
  background: var(--surface-2);
  font-size: var(--fs-body-sm);
  font-weight: 700;
  letter-spacing: .02em;
  color: var(--text-primary);
}
.dash-avatar #account-initials { display: flex; align-items: center; }

/* The popovers. One rule, two placements. */
/* Anchored to its button's right edge, so it opens inward.

   It used to open rightward from left: 0, and every button that owns one is at
   the right-hand end of the bar — so a 232px menu ran off the side of the
   screen with half its words past the edge. Worst on a phone, but not only
   there: the status menu was still spilling 130px at 900px wide. */
.dash-pop {
  position: absolute;
  top: calc(100% + 8px);
  left: auto;
  right: 0;
  z-index: 70;
  min-width: 232px;
  max-width: min(300px, calc(100vw - var(--sp-8)));
  padding: var(--sp-2);
  background: var(--bg-elevated);
  border: 1px solid var(--border-med);
  border-radius: var(--radius-lg);
  box-shadow: var(--elev-3);
}
/* The account menu carried this to opt into what is now the default. Kept so
   the markup still names something, and so re-adding a left-hand menu has to
   think about which way its popover opens. */
.dash-pop--end { left: auto; right: 0; }
.dash-pop[hidden] { display: none; }

.dash-pop-head {
  padding: var(--sp-2) var(--sp-3) var(--sp-3);
  margin: 0;
  font-size: var(--fs-body-sm);
  color: var(--text-tertiary);
  word-break: break-all;
  border-bottom: 1px solid var(--border);
  margin-bottom: var(--sp-2);
}
.dash-pop-note {
  padding: var(--sp-2) var(--sp-3) var(--sp-3);
  margin: 0 0 var(--sp-2);
  font-size: var(--fs-body-sm);
  line-height: 1.6;
  color: var(--text-tertiary);
  border-bottom: 1px solid var(--border);
}
.dash-pop-item {
  display: block;
  width: 100%;
  padding: var(--sp-2) var(--sp-3);
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-secondary);
  font: inherit;
  font-size: var(--fs-body-sm);
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}
.dash-pop-item:hover { background: var(--surface-2); color: var(--text-primary); }
.dash-pop-item:focus-visible { outline: var(--focus-ring); outline-offset: -2px; }
.dash-pop-item[hidden] { display: none; }
/* The theme switch keeps its label on the left and its glyph on the right. */
.dash-pop-item--split { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); }
.dash-pop-sep { height: 1px; margin: var(--sp-2) 0; background: var(--border); }
.dash-pop-sep[hidden] { display: none; }

/* The bar is sticky, so the shell below it starts under it rather than behind
   it — there is no --nav-height offset here to undo. */
.dash-bar + .wf-shell { padding-top: var(--sp-10); }

@media (max-width: 760px) {
  /* The tabs take a row of their own.

     They were between the logo and the controls, sharing one row with Redeem,
     the status pill and the account button. min-width: 0 plus overflow-x meant
     a full row squeezed them to a few pixels of "Traction" rather than
     scrolling them — four sections of the dashboard reduced to a sliver. A
     scroller needs width before it can scroll.

     The row is full-bleed so the first tab starts at the gutter and the last
     one can run off the edge, which is what says there is more to the side. */
  .dash-bar-inner { gap: var(--sp-2); flex-wrap: wrap; }
  .dash-tabs {
    order: 3;
    flex: 1 0 100%;
    margin-left: 0;
    margin-inline: calc(var(--gutter) * -1);
    padding-inline: var(--gutter);
    padding-bottom: var(--sp-2);
    gap: 2px;
  }
  .dash-logo { order: 0; }
  .dash-bar-right { order: 1; }

}

@media (max-width: 560px) {
  /* The status pill loses its words and keeps its dot; the label is in the
     popover it opens anyway. */
  .dash-status-btn .wf-pill { font-size: 0; gap: 0; padding-inline: 7px; }
  .dash-pop { max-width: calc(100vw - var(--sp-6)); }
}

/* The step change scrolls here. scroll-margin keeps the bar clear of the
   sticky header, which would otherwise sit on top of it. */
.wf-steps {
  display: flex;
  gap: var(--sp-2);
  margin-bottom: var(--sp-8);
  scroll-margin-top: var(--sp-5);
}
.wf-step {
  flex: 1; height: 4px; border-radius: 2px; background: var(--surface-2);
  transition: background var(--duration) var(--ease-out);
}
.wf-step.is-done { background: var(--accent); }
.wf-step.is-current { background: var(--accent); opacity: .55; }

/* ── Stats / traction ──────────────────────────────────────────────────────*/
/* ── The offers list ───────────────────────────────────────────────────────
   A row per offer, with the headline one first. Actions sit beside the line
   rather than under it, because a shop with four offers reading four stacked
   button rows is a wall. */
.offer-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-3); }
.offer-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-4); flex-wrap: wrap;
  padding: var(--sp-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--bg-dark);
}
.offer-row.is-paused { opacity: .62; }
.offer-row-main { min-width: 0; flex: 1 1 18rem; }
.offer-row-line { margin: 0; font-size: var(--fs-body-sm); font-weight: 600; color: var(--text-primary); }
.offer-row-meta { margin: var(--sp-1) 0 0; font-size: var(--fs-body-sm); color: var(--text-tertiary); }
.offer-row-acts { display: flex; gap: var(--sp-2); flex-shrink: 0; flex-wrap: wrap; }

/* Redeem lives in the bar: used a few times a day, on a phone, standing at a
   counter. Kept out of the phone collapse that thins the status pill, because
   the phone is exactly where it matters. */
.dash-redeem { flex-shrink: 0; }

/* A checkbox with a paragraph under its label — the switch and the reason it
   exists, rather than a bare word the merchant has to guess at. */
.wf-check { display: flex; gap: var(--sp-3); align-items: flex-start; cursor: pointer; }
.wf-check input { margin-top: 3px; flex-shrink: 0; width: 16px; height: 16px; accent-color: var(--accent); }
.wf-check > span { display: flex; flex-direction: column; gap: var(--sp-1); }
.wf-check strong { font-size: var(--fs-body-sm); color: var(--text-primary); font-weight: 600; }
.wf-check .wf-hint { margin: 0; }

/* A nudge is not a card. Both of these used to be full .wf-card blocks under
   the analytics, which made a paid add-on and a referral ask look like parts of
   the product rather than asides to it. One row, one action, quieter ground. */
.dash-nudge {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-5); flex-wrap: wrap;
  padding: var(--sp-4) var(--sp-5);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--bg-dark);
}
.dash-nudge[hidden] { display: none; }
.dash-nudge--quiet { background: transparent; }
.dash-nudge-body { min-width: 0; flex: 1 1 22rem; }
.dash-nudge-title { margin: 0; font-size: var(--fs-body-sm); font-weight: 700; color: var(--text-primary); }
.dash-nudge-text { margin: var(--sp-1) 0 0; font-size: var(--fs-body-sm); color: var(--text-tertiary); line-height: 1.6; }
.dash-nudge-acts { display: flex; align-items: center; gap: var(--sp-2); flex-shrink: 0; }
.dash-nudge-x {
  display: grid; place-items: center;
  width: 30px; height: 30px; padding: 0;
  border: none; border-radius: var(--radius-sm);
  background: transparent; color: var(--text-tertiary); cursor: pointer;
}
.dash-nudge-x:hover { background: var(--surface-2); color: var(--text-primary); }
.dash-nudge-x:focus-visible { outline: var(--focus-ring); outline-offset: 2px; }

/* ── Traction ──────────────────────────────────────────────────────────────
   Four readings of the same events. None of them carries meaning in its hue:
   there is one series per chart and one colour throughout, so identity comes
   from the heading above each and never from the paint. */

/* The funnel. Bars share one scale — the top of the funnel — because the point
   is how much falls away between steps. */
.funnel { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-5); }
.funnel-row {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--sp-4); margin-bottom: var(--sp-2);
}
.funnel-label { font-size: var(--fs-body-sm); font-weight: 600; color: var(--text-primary); }
.funnel-num {
  display: flex; align-items: baseline; gap: var(--sp-3);
  font-size: var(--fs-body); font-weight: 700;
  font-variant-numeric: tabular-nums; color: var(--text-primary);
}
.funnel-rate { font-size: var(--fs-body-sm); font-weight: 500; color: var(--text-tertiary); }
.funnel-track { height: 10px; border-radius: 5px; background: var(--surface-2); overflow: hidden; }
.funnel-bar { height: 100%; border-radius: 5px; background: var(--accent); }
.funnel-note { margin: var(--sp-2) 0 0; font-size: var(--fs-body-sm); color: var(--text-tertiary); }

/* Day by day. Three figures, each on its own scale. */
.trend-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--sp-4); flex-wrap: wrap; margin-bottom: var(--sp-6);
}
/* The offer / branch pickers above the traction cards. Selects rather than a
   segmented control: a shop can have a dozen offers, and a dozen pills is a
   wall. Wraps to one per line on a phone. */
.dash-filters { display: flex; flex-wrap: wrap; gap: var(--sp-3); margin-bottom: var(--sp-6); }
.dash-filter { display: inline-flex; align-items: center; gap: var(--sp-2); }
.dash-filter > span {
  font-size: var(--fs-body-sm); color: var(--text-tertiary); font-weight: 600;
}
.dash-filter select {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text-primary);
  font: inherit;
  font-size: var(--fs-body-sm);
  padding: var(--sp-2) var(--sp-3);
  max-width: 18rem;
}
.dash-filter select:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.seg { display: inline-flex; padding: 2px; border-radius: var(--radius-pill); background: var(--surface-2); }
.seg-btn {
  border: none; background: transparent; cursor: pointer;
  padding: 5px 12px; border-radius: var(--radius-pill);
  font: inherit; font-size: var(--fs-body-sm); font-weight: 600;
  color: var(--text-tertiary);
}
.seg-btn:hover { color: var(--text-primary); }
.seg-btn.is-on { background: var(--bg-elevated); color: var(--text-primary); }
.seg-btn:focus-visible { outline: var(--focus-ring); outline-offset: 2px; }

.trends { display: flex; flex-direction: column; gap: var(--sp-8); }
.trend-cap {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--sp-4); margin-bottom: var(--sp-2);
}
.trend-title { font-size: var(--fs-body-sm); font-weight: 600; color: var(--text-secondary); }
.trend-total {
  font-size: var(--fs-body); font-weight: 700;
  font-variant-numeric: tabular-nums; color: var(--text-primary);
}
/* The whole plot is the hit area: a 2px stroke is not something anyone can
   point at, least of all on a phone. */
.trend-plot { position: relative; touch-action: pan-y; }
.trend-plot svg { display: block; width: 100%; height: 96px; overflow: visible; }
.trend-base { stroke: var(--border); stroke-width: 1; vector-effect: non-scaling-stroke; }
.trend-area { fill: var(--accent); opacity: .12; }
.trend-line {
  fill: none; stroke: var(--accent); stroke-width: 2;
  stroke-linejoin: round; stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}
.trend-end { fill: var(--accent); stroke: var(--bg-card); stroke-width: 2; vector-effect: non-scaling-stroke; }
.trend-cross { stroke: var(--text-tertiary); stroke-width: 1; stroke-dasharray: 3 3; vector-effect: non-scaling-stroke; }
.trend-tip {
  position: absolute; top: -2px; transform: translateX(-50%);
  padding: 3px 8px; border-radius: var(--radius-sm);
  background: var(--bg-elevated); border: 1px solid var(--border-med);
  font-size: var(--fs-body-sm); font-variant-numeric: tabular-nums;
  color: var(--text-primary); white-space: nowrap; pointer-events: none;
}
.trend-axis {
  display: flex; justify-content: space-between;
  margin-top: var(--sp-2);
  font-size: var(--fs-body-sm); color: var(--text-tertiary);
}
.trend-empty { font-size: var(--fs-body-sm); color: var(--text-tertiary); }

/* The same figures as a table, because "how many on Saturday" is not a
   question a shape answers. */
.dash-table { width: 100%; border-collapse: collapse; font-size: var(--fs-body-sm); }
.dash-table th, .dash-table td { padding: var(--sp-3) var(--sp-4); text-align: left; }
.dash-table thead th {
  font-size: var(--fs-body-sm); font-weight: 600; color: var(--text-tertiary);
  border-bottom: 1px solid var(--border-med); white-space: nowrap;
}
.dash-table tbody th { font-weight: 600; color: var(--text-secondary); white-space: nowrap; }
.dash-table tbody td { color: var(--text-secondary); }
.dash-table tbody tr + tr th,
.dash-table tbody tr + tr td { border-top: 1px solid var(--border); }
.dash-table .num { text-align: right; font-variant-numeric: tabular-nums; }
/* A cell that says "not shared" or "—": present, deliberately quiet. Not an
   error state, so it does not take the warning colour. */
.dash-muted { color: var(--text-tertiary); }

@media (prefers-reduced-motion: reduce) {
  .funnel-bar { transition: none; }
}

.wf-stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--sp-3); }
@media (max-width: 720px) { .wf-stats { grid-template-columns: repeat(2, 1fr); } }
.wf-stat {
  background: var(--surface-1); border: 1px solid var(--border);
  border-radius: var(--radius-md); padding: var(--sp-4);
}
.wf-stat-value { font-size: 1.6rem; font-weight: 800; letter-spacing: -0.02em; color: var(--text-primary); }
.wf-stat-label {
  font-size: var(--fs-label); text-transform: uppercase; letter-spacing: var(--ls-label);
  color: var(--text-tertiary); margin-top: 4px; font-weight: 700;
}

/* ── Status pill ───────────────────────────────────────────────────────────*/
.wf-pill {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 11px; border-radius: var(--radius-pill);
  font-size: var(--fs-body-sm); font-weight: 700;
}
.wf-pill--live   { background: var(--status-success-dim); color: var(--ink-green); border: 1px solid var(--status-success-border); }
.wf-pill--paused { background: var(--status-warning-dim); color: var(--ink-gold); border: 1px solid var(--status-warning-border); }
.wf-pill--blocked{ background: var(--status-danger-dim); color: var(--ink-red); border: 1px solid var(--status-danger-border); }
.wf-pill--pending{ background: var(--surface-2); color: var(--text-secondary); border: 1px solid var(--border-med); }
.wf-dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; }

/* ── Empty / loading states ────────────────────────────────────────────────*/
.wf-empty { text-align: center; padding: var(--sp-16) var(--sp-6); }
.wf-empty-icon { font-size: 40px; margin-bottom: var(--sp-4); }
.wf-empty-title { font-size: var(--fs-h2); margin-bottom: var(--sp-3); }
.wf-empty-body { color: var(--text-secondary); max-width: 46ch; margin: 0 auto; line-height: var(--lh-body); }
.wf-spinner {
  width: 22px; height: 22px; border-radius: 50%;
  border: 2px solid var(--surface-3); border-top-color: var(--accent-text);
  animation: wf-spin .7s linear infinite; margin: 0 auto;
}
@keyframes wf-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .wf-spinner { animation-duration: 2s; } }

/* ── Toasts ────────────────────────────────────────────────────────────────*/
.wf-toast-host {
  position: fixed; left: 50%; bottom: 24px; transform: translateX(-50%);
  z-index: 9000; display: flex; flex-direction: column; gap: 8px; align-items: center;
  pointer-events: none; width: min(92vw, 420px);
}
.wf-toast {
  background: var(--bg-elevated); border: 1px solid var(--border-med);
  border-radius: var(--radius); box-shadow: var(--shadow-dropdown);
  padding: 11px 16px; font-size: var(--fs-body-sm); color: var(--text-primary);
  transition: opacity var(--duration) var(--ease-out), transform var(--duration) var(--ease-out);
}
.wf-toast--error   { border-color: var(--status-danger-border); }
.wf-toast--success { border-color: var(--status-success-border); }
.wf-toast.is-out { opacity: 0; transform: translateY(6px); }

/* ── Discovery map (the home screen) ───────────────────────────────────────*/
.wf-map-shell {
  display: grid; grid-template-columns: 380px 1fr;
  height: calc(100vh - var(--nav-height));
  min-height: 560px;
}
@media (max-width: 900px) {
  .wf-map-shell { grid-template-columns: 1fr; height: auto; }
  .wf-map-canvas { height: 52vh; min-height: 320px; }
}
.wf-map-side {
  border-right: 1px solid var(--border);
  background: var(--bg-dark);
  display: flex; flex-direction: column; overflow: hidden;
}
.wf-map-canvas { background: var(--bg-deepest); }
#wf-map { width: 100%; height: 100%; min-height: 320px; }

.wf-side-head { padding: var(--sp-5); border-bottom: 1px solid var(--border); }
.wf-side-list { overflow-y: auto; flex: 1; padding: var(--sp-3); }


/* Merchant row in the list */
.wf-merchant {
  display: flex; gap: var(--sp-3); align-items: center; width: 100%; text-align: left;
  padding: var(--sp-3); border-radius: var(--radius-md); cursor: pointer;
  border: 1px solid transparent; transition: background var(--duration) var(--ease-out);
}
.wf-merchant:hover, .wf-merchant:focus-visible {
  background: var(--surface-2); border-color: var(--border); outline: none;
}
.wf-merchant.is-active { background: var(--accent-dim); border-color: var(--accent-border); }
.wf-merchant-logo {
  width: 46px; height: 46px; border-radius: var(--radius); flex-shrink: 0;
  object-fit: cover; background: var(--surface-2); border: 1px solid var(--border);
}
.wf-merchant-name { font-weight: 700; font-size: var(--fs-body); color: var(--text-primary); }
.wf-merchant-sub  { font-size: var(--fs-body-sm); color: var(--text-tertiary); margin-top: 1px; }
.wf-merchant-credit {
  margin-left: auto; flex-shrink: 0; font-weight: 800; font-size: var(--fs-body-sm);
  color: var(--accent-text); background: var(--accent-dim); border: 1px solid var(--accent-border);
  border-radius: var(--radius-pill); padding: 3px 10px;
}

/* Map pin showing the merchant's logo */
.wf-map-pin {
  width: 46px; height: 46px; border-radius: 50%; overflow: hidden;
  border: 2px solid var(--accent); background: var(--bg-card);
  box-shadow: 0 4px 14px rgba(0,0,0,.45); cursor: pointer;
  transition: transform var(--duration) var(--ease-out);
}
.wf-map-pin img { width: 100%; height: 100%; object-fit: cover; display: block; }
.wf-map-pin:hover { transform: scale(1.09); }
.wf-map-pin-badge {
  margin-top: 3px; background: rgba(8,14,28,.92); border: 1px solid var(--accent-border);
  border-radius: var(--radius-pill); padding: 2px 8px; font-size: 10px; font-weight: 800;
  color: var(--accent-text); white-space: nowrap; text-align: center;
}

/* ── QR card ───────────────────────────────────────────────────────────────*/
.wf-qr-card {
  background: #fff; color: #0A1628; border-radius: var(--radius-lg);
  padding: 28px; text-align: center; max-width: 380px; margin: 0 auto;
}
.wf-qr-card canvas, .wf-qr-card img, .wf-qr-card svg {
  width: 220px; height: 220px; display: block; margin: 0 auto 16px;
}
.wf-qr-title { font-size: 20px; font-weight: 800; letter-spacing: -0.01em; margin-bottom: 6px; }
.wf-qr-sub   { font-size: 13px; color: #5A7899; line-height: 1.5; }
.wf-qr-code  {
  font-family: var(--font-mono); font-size: 20px; font-weight: 700; letter-spacing: .12em;
  margin-top: 14px; padding: 8px; background: #F0F4FA; border-radius: 8px;
}
.wf-qr-url { font-size: 11px; color: #7A9DBD; margin-top: 10px; word-break: break-all; }

@media print {
  .site-header, .site-footer, .wf-noprint { display: none !important; }
  body { background: #fff !important; }
  .wf-qr-card { border: 1px solid #ccc; page-break-inside: avoid; }
}

/* ── /admin — the merchant approval queue ───────────────────────────────────
   Staff only, and it looks it: a dense table rather than cards, because the
   job is scanning twenty rows for the two that are waiting. Everything here
   is built from the same tokens as the rest of the app, so it inherits both
   themes without a second palette. */
.admin-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  margin-bottom: var(--sp-4);
}

.admin-counts {
  color: var(--text-secondary);
  font-size: var(--fs-body-sm);
}

.admin-counts strong { color: var(--text-primary); }

.admin-bar-right {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3);
}

.admin-filters {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
}

/* The table scrolls sideways inside its own box rather than taking the page
   with it — seven columns do not fit a phone and never will. */
.admin-table-wrap {
  overflow-x: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface-1);
}

.admin-table {
  width: 100%;
  min-width: 720px;
  border-collapse: collapse;
  font-size: var(--fs-body-sm);
}

.admin-table th,
.admin-table td {
  padding: var(--sp-3) var(--sp-4);
  text-align: left;
  vertical-align: middle;
  border-bottom: 1px solid var(--border-sub);
}

.admin-table thead th {
  font-size: var(--fs-label);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-tertiary);
  background: var(--surface-2);
  white-space: nowrap;
}

.admin-table tbody tr:last-child td { border-bottom: 0; }
.admin-table tbody tr:hover { background: var(--surface-2); }

.admin-shop { min-width: 200px; }

.admin-shop-name {
  display: block;
  font-weight: 600;
  color: var(--text-primary);
}

.admin-shop-sub {
  display: block;
  margin-top: 2px;
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  color: var(--text-tertiary);
}

.admin-act { text-align: right; white-space: nowrap; }
/* A pending row carries two buttons — Approve and Reject — so they need to be
   told apart by more than adjacency. */
.admin-act .btn + .btn { margin-left: var(--sp-2); }
.admin-empty { text-align: center; color: var(--text-tertiary); padding: var(--sp-8); }

/* State, said in a word and a colour. Never colour alone — the word carries
   it for anyone who cannot tell the two greens apart. */
.admin-tag {
  display: inline-block;
  padding: 2px var(--sp-2);
  border-radius: var(--radius-sm);
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.02em;
  white-space: nowrap;
  border: 1px solid var(--border-med);
  color: var(--text-secondary);
}

.admin-tag--ok   { color: var(--ink-green); border-color: var(--ink-green); }
.admin-tag--warn { color: var(--ink-gold);  border-color: var(--ink-gold); }
.admin-tag--bad  { color: var(--ink-red);   border-color: var(--ink-red); }
.admin-tag--mute { color: var(--text-tertiary); }

.admin-legend { margin-top: var(--sp-4); max-width: 58ch; }

.admin-gate { max-width: 34rem; }

.admin-signin-form {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  margin-top: var(--sp-4);
  align-items: flex-start;
}

.admin-signin-form .field-input { width: 100%; }

.admin-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  margin-top: var(--sp-4);
}

@media (max-width: 640px) {
  .admin-bar { align-items: flex-start; flex-direction: column; }
  .admin-bar-right { width: 100%; justify-content: space-between; }

  /* Seven columns will not fit a phone at any padding — tried it, the Approve
     button ends up off the edge and the shop name wraps three ways. So the
     table stops being a table here and each shop becomes a block, which is
     what the rest of the site does at this width anyway.

     The header row goes with it, which is why Deals and Joined go too: a bare
     "2" and a date with no column heading above them say nothing. The four
     things this page exists to show all stay. */
  .admin-table-wrap { overflow-x: visible; }
  .admin-table { min-width: 0; }
  .admin-table thead { display: none; }
  .admin-table, .admin-table tbody, .admin-table tr, .admin-table td {
    display: block;
    width: auto;
  }
  .admin-table tr {
    padding: var(--sp-4);
    border-bottom: 1px solid var(--border-sub);
  }
  .admin-table tbody tr:last-child { border-bottom: 0; }
  .admin-table td { border: 0; padding: 0; }

  /* Approval and Published are one thought, so they sit on one line. */
  .admin-table td:nth-child(3),
  .admin-table td:nth-child(4) {
    display: inline-block;
    margin: var(--sp-3) var(--sp-2) 0 0;
  }
  .admin-table td:nth-child(5),
  .admin-table td:nth-child(6) { display: none; }

  .admin-shop { min-width: 0; }
  .admin-act { text-align: left; margin-top: var(--sp-3); }
  .admin-act .btn + .btn { margin-left: var(--sp-3); }
  .admin-empty { padding: var(--sp-6); }
}

