/*
  AgencyLadder — component primitives
  DESIGN-SYSTEM.md §5 (components), §5.1 (the two governing rules), §7 (elevation),
  §8 (iconography — no <svg>, everything is a Unicode glyph or a CSS-drawn shape).

  Two rules enforced everywhere below, on purpose, not by convention:
    - One accent per card. Exactly one lime-filled element per card or panel — always the
      bid action. Everything else competing for the same click is a 1px outline.
    - No new hues. New states come from --text / --border / --decay weight and position,
      never a new color. Red (--decay) is never decorative, never a second accent.

  No media queries in this file. Every component either has one fixed spec (buttons, pills,
  inputs — the source design doesn't vary these by viewport) or uses the fluid tokens from
  tokens.css. Where a card/row layout needs to reflow, it's flex-wrap + flex:N 1 basis +
  min-width, per DESIGN-SYSTEM §4.
*/

/* =========================================================================
   5.4 Buttons
   ========================================================================= */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-8);
  border-radius: var(--radius-btn);
  font-family: var(--font-sans);
  line-height: 1;
  white-space: nowrap;
  min-height: 44px; /* tap target floor, §5.1 */
  font-variant-numeric: tabular-nums;
  transition: background-color .15s ease, border-color .15s ease, color .15s ease,
    transform .15s ease;
}

/* Primary — lime, "takes money". Row-size default; add .btn-lg for hero-size CTAs
   (>=15px type gets the larger ledge, per §7). */
.btn-primary {
  font-weight: 700;
  font-size: var(--fs-btn-primary-sm);
  color: var(--accent-ink);
  background: var(--accent);
  padding: 13px 16px;
  box-shadow: var(--ledge-sm);
}

.btn-primary:hover:not(:disabled) {
  background: var(--accent-hover);
  transform: translateY(-1px);
  /* Do NOT animate the shadow (§6 "Hover motion") — the static ledge is what makes the
     translateY read as a physical key press-back. */
}

.btn-primary:active:not(:disabled) {
  transform: translateY(0);
}

.btn-primary.btn-lg {
  font-size: var(--fs-btn-primary-lg);
  padding: 15px 20px;
  box-shadow: var(--ledge-lg);
}

.btn-primary:disabled {
  opacity: .5;
  cursor: default;
  /* Real disabled state: opacity only, fill and ledge stay — never grey it out, never drop
     the shadow, and it must not accept clicks (§9 in the prototype spec: "nothing is truly
     disabled in the prototype" is the bug to not repeat). */
  pointer-events: none;
}

.btn-primary:disabled:hover {
  background: var(--accent);
  transform: none;
}

/* Secondary — 1px outline, "sends a buyer away". Never filled. */
.btn-secondary {
  font-weight: 600;
  font-size: var(--fs-btn-secondary-sm);
  color: var(--text);
  background: transparent;
  border: 1px solid var(--border-loud);
  padding: 12px 15px;
}

.btn-secondary:hover:not(:disabled) {
  border-color: var(--text);
  background: var(--inset);
}

.btn-secondary.btn-lg {
  font-size: var(--fs-btn-secondary-lg);
  padding: 14px 18px;
}

.btn-secondary:disabled {
  opacity: .5;
  cursor: default;
  pointer-events: none;
}

/* Destructive — same shape family as secondary, red ink. Danger only, never decorative. */
.btn-destructive {
  font-weight: 600;
  font-size: var(--fs-btn-secondary-sm);
  color: var(--decay);
  background: transparent;
  border: 1px solid rgba(255, 92, 77, .34);
  padding: 12px 15px;
}

.btn-destructive:hover:not(:disabled) {
  background: rgba(255, 92, 77, .09);
}

.btn-destructive:disabled {
  opacity: .4;
  cursor: default;
  pointer-events: none;
}

/* =========================================================================
   5.2 / 5.3 Card & row surfaces, incl. the leader rail
   ========================================================================= */

.card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  padding: 14px;
  position: relative;
  overflow: hidden; /* required — the rail is an absolute child */
  transition: box-shadow .45s ease, border-color .45s ease, transform .45s ease;
}

/* Desktop row variant — same colors, wider padding, no transform in the transition
   (rows don't lift; only mobile cards do, per §5.3). */
.row {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  padding: 16px 18px;
  position: relative;
  overflow: hidden;
  display: grid;
  grid-template-columns: 64px 1fr 132px 116px 232px;
  gap: var(--space-16);
  align-items: center;
  transition: box-shadow .45s ease, border-color .45s ease;
}

/* Leader state — emphasised border + the 3px white rail, an absolutely positioned child,
   never a border trick (§4 "Borders"). */
.card.is-leader,
.row.is-leader {
  border-color: var(--border-loud);
}

.leader-rail {
  position: absolute;
  inset: 0 auto 0 0;
  width: 3px;
  background: var(--medal-gold);
}

/* The re-rank flash — the signature interaction: snap to the flash ring on entry
   (no separate enter-transition is specified — §5.2's own transition line, inherited
   from .card/.row above, is what eases it back out), hold ~600ms, then ease back over
   that same box-shadow/border-color transition. Never move a row without this; suppress
   entirely while a modal/dropdown is open. */
.card.is-flashing,
.row.is-flashing {
  border-color: var(--accent);
  box-shadow: var(--shadow-flash);
}

/* Decayed-listing variant — red border only, never a red fill (red is never decorative). */
.card.is-decayed,
.row.is-decayed {
  border-color: var(--decayed-card-border);
}

.card-footer {
  margin-top: 13px;
  padding-top: 12px;
  border-top: 1px solid var(--border);
  display: flex;
  align-items: center;
  gap: var(--space-8);
  flex-wrap: wrap;
}

/* =========================================================================
   5.11 Logo / avatar
   ========================================================================= */

.avatar {
  width: 26px;
  height: 26px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-loud);
  flex: none;
  overflow: hidden;
}

.avatar-placeholder {
  width: 100%;
  height: 100%;
  background: repeating-linear-gradient(135deg, #26262A 0 4px, #1B1B1E 4px 8px);
}

.avatar-failed {
  width: 100%;
  height: 100%;
  background: var(--border);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 13px;
  color: var(--text);
}

/* =========================================================================
   5.5 Pills / chips
   ========================================================================= */

.pill {
  display: inline-flex;
  align-items: center;
  gap: var(--space-6);
  font-family: var(--font-mono);
  font-weight: 600;
  font-size: var(--fs-kicker);
  letter-spacing: .08em;
  text-transform: uppercase;
  border-radius: var(--radius-pill);
  padding: 8px 10px;
  line-height: 1;
  white-space: nowrap;
}

/* Status pill — "RANK 01 · HOLDING" */
.pill-status {
  background: var(--status-pill-fill);
  border: 1px solid var(--border-loud);
  color: var(--text);
}

/* Decay pill — "-2%/DAY" on its own, but the board row's "−$17 tomorrow" variant is genuinely
   mixed-case content (verified against the running prototype, 2026-08-21) — the base .pill's
   forced uppercase was clipping that to "TOMORROW". Content decides its own case here. */
.pill-decay {
  background: var(--decay-pill-fill);
  border: 1px solid var(--decay-pill-border);
  color: var(--decay);
  text-transform: none;
}

/* Plain meta chip — category / country / "N clicks" on a card footer */
.pill-meta {
  background: var(--card);
  border: 1px solid var(--border-loud);
  color: var(--text-muted);
  text-transform: none;
  font-size: var(--fs-meta);
  letter-spacing: normal;
  font-weight: 500;
}

/* Filter chip — pill-shaped, radius 22px. Inactive is an outline; active is the ONE
   lime-filled control in the filter row (a filter row is not a card, so this doesn't
   violate one-accent-per-card, but never pair it with another filled element nearby). */
.chip {
  display: inline-flex;
  align-items: center;
  border-radius: var(--radius-chip);
  padding: 14px 15px;
  font-family: var(--font-sans);
  font-weight: 600;
  font-size: 13.5px;
  line-height: 1;
  background: var(--card);
  border: 1px solid var(--border-loud);
  color: var(--text);
  min-height: 44px;
  transition: background-color .15s ease, border-color .15s ease, color .15s ease;
}

.chip:hover:not(.is-active):not(:disabled) {
  border-color: var(--text);
}

.chip.is-active {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}

/* Delta chip — Mono 600 13px. Up is lime, down is decay red. The glyphs are the
   Unicode triangles from §8, never an SVG icon. */
.delta {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-4);
  font-family: var(--font-mono);
  font-weight: 600;
  font-size: var(--fs-delta);
  line-height: 1;
}

.delta-up {
  color: var(--accent);
}

.delta-down {
  color: var(--decay);
}

/* =========================================================================
   5.6 Rank indicator — pure typography + the leader rail. No badge component,
   no medals, no gold. Always zero-padded via String(rank).padStart(2,"0").
   ========================================================================= */

.rank {
  font-family: var(--font-mono);
  font-weight: 700;
  letter-spacing: var(--ls-rank);
}

.rank-card {
  font-size: var(--fs-rank-card);
  line-height: .88;
  color: var(--text-faint);
}

.rank-rail {
  font-size: var(--fs-rank-rail);
  line-height: 1;
  color: var(--text);
}

/* Podium ramp — gold, silver, bronze, then the original faint grey from rank 4 down.
   Colour lives on the NUMERAL only; see --medal-* in tokens.css for why it is scoped that tightly.
   .is-rank-2-3 is kept because public/css/preview.html still renders the pre-podium reference. */
.rank.is-rank-1 { color: var(--medal-gold); }
.rank.is-rank-2 { color: var(--medal-silver); }
.rank.is-rank-3 { color: var(--medal-bronze); }
.rank.is-rank-2-3 { color: var(--text-muted); }
.rank.is-rank-4-plus { color: var(--text-faint); }

/* =========================================================================
   5.8 Form inputs
   ========================================================================= */

.field-group {
  display: flex;
  flex-direction: column;
  gap: var(--space-9);
}

.field-label-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
}

.field-label {
  font-family: var(--font-sans);
  font-weight: 600;
  font-size: var(--fs-form-label);
  color: var(--text);
}

.field-counter {
  font-family: var(--font-mono);
  font-size: var(--fs-meta);
  color: var(--text-dim);
}

.input,
.textarea,
.select {
  font-family: var(--font-sans);
  font-weight: 500;
  font-size: var(--fs-form-value);
  line-height: 1;
  color: var(--text);
  background: var(--card);
  border: 1px solid var(--border-loud);
  border-radius: var(--radius-input);
  padding: 15px 14px;
  width: 100%;
  transition: border-color .15s ease;
}

.textarea {
  line-height: var(--lh-body);
  resize: vertical;
  min-height: 88px;
}

/* Focus — border color change ONLY. No glow, no outline ring. */
.input:focus,
.textarea:focus,
.select:focus {
  border-color: var(--accent);
  outline: none;
}

.field-helper {
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: var(--fs-form-helper);
  line-height: 1.4;
  color: var(--text-dim);
}

/* Error — border AND a message slot. Never border-only (§5.8). */
.input.is-error,
.textarea.is-error,
.select.is-error {
  border: 1px solid rgba(255, 92, 77, .5);
}

.field-error {
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: var(--fs-form-helper);
  line-height: 1.4;
  color: var(--decay);
}

/* Rejected-input shake — 300ms, referenced by the motion registry (§6) */
@keyframes shake {
  10%, 90% { transform: translateX(-1px); }
  20%, 80% { transform: translateX(2px); }
  30%, 50%, 70% { transform: translateX(-4px); }
  40%, 60% { transform: translateX(4px); }
}

.is-shaking {
  animation: shake .3s;
}

/* =========================================================================
   5.13 Toggle
   ========================================================================= */

.toggle-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-14);
}

.toggle {
  appearance: none;
  position: relative;
  width: 54px;
  height: 31px;
  border-radius: 999px;
  background: var(--border-loud);
  flex: none;
  transition: background-color var(--dur-toggle) ease;
}

.toggle::after {
  content: "";
  position: absolute;
  top: 4px;
  left: 4px;
  width: 23px;
  height: 23px;
  border-radius: var(--radius-full);
  background: var(--text);
  transition: left var(--dur-toggle) ease;
}

.toggle:checked {
  background: var(--accent);
}

.toggle:checked::after {
  left: calc(100% - 23px - 4px);
}

/* =========================================================================
   5.10 Progress meter — fill is ALWAYS #F4F4F5, never lime. Lime is reserved
   for actions, and a progress meter is not an action.
   ========================================================================= */

.meter {
  height: 6px;
  border-radius: var(--radius-meter);
  background: var(--border);
  overflow: hidden;
}

.meter-fill {
  height: 100%;
  background: var(--text);
  transition: width var(--dur-progress) ease;
}

/* =========================================================================
   5.9 Toast — bottom-right, one at a time, never for errors.
   ========================================================================= */

.toast {
  position: fixed;
  right: 16px;
  bottom: 18px;
  z-index: 40;
  display: flex;
  align-items: center;
  gap: var(--space-11);
  background: var(--inset);
  border: 1px solid var(--border-loud);
  border-radius: var(--radius-card);
  padding: 13px 14px;
  box-shadow: var(--shadow-toast);
  animation: rise var(--dur-rise) ease-out;
  max-width: calc(100vw - 32px);
  font-size: var(--fs-form-helper);
  color: var(--text);
}

/* =========================================================================
   5.12 Skeleton row — five rows, descending opacity, pulsing. Never a spinner.
   ========================================================================= */

.skeleton-row {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  padding: 14px;
  display: flex;
  flex-direction: column;
  gap: var(--space-10);
}

.skeleton-bar {
  height: 14px;
  border-radius: var(--radius-micro);
  background: linear-gradient(90deg, var(--border) 25%, var(--border-loud) 37%, var(--border) 63%);
  background-size: 400% 100%;
  animation: swp var(--dur-skeleton-pulse) ease-in-out infinite;
}

.skeleton-row:nth-child(1) { opacity: 1; }
.skeleton-row:nth-child(2) { opacity: .85; }
.skeleton-row:nth-child(3) { opacity: .7; }
.skeleton-row:nth-child(4) { opacity: .55; }
.skeleton-row:nth-child(5) { opacity: .4; }

/* =========================================================================
   5.14 Empty slot — dashed border, always names the price.
   ========================================================================= */

.empty-slot {
  border: 1px dashed var(--border-loud);
  border-radius: var(--radius-card);
  padding: 16px;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-7);
}

.empty-slot .pill,
.empty-slot .kicker {
  align-self: center;
}

.kicker {
  font-family: var(--font-mono);
  font-weight: 600;
  font-size: var(--fs-kicker);
  letter-spacing: var(--ls-kicker);
  text-transform: uppercase;
  color: var(--text-dim);
}

.empty-slot-headline {
  font-family: var(--font-sans);
  font-weight: 700;
  font-size: var(--fs-heading);
  color: var(--text);
}

.empty-slot-copy {
  font-family: var(--font-sans);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--text-muted);
  max-width: 420px;
}

/* =========================================================================
   Live dot — CSS-drawn, no SVG (§8)
   ========================================================================= */

.live-dot {
  display: inline-block;
  width: 7px;
  height: 7px;
  border-radius: var(--radius-full);
  background: var(--accent);
  animation: pls 2s ease-in-out infinite;
}

/* =========================================================================
   Layout helpers referenced by preview.html and future UI tickets — no
   media queries, flex-wrap + basis + min-width per §4.
   ========================================================================= */

.flex-wrap-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-10);
}

.min-w-0 {
  min-width: 0;
}

.ellipsis {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The bars inside a skeleton row. .skeleton-row itself already existed in this file, fully styled
   and never once rendered: the loading state was designed, built, and then not wired up, so the
   board was simply blank until the first fetch resolved. */
.skeleton-line {
  display: block;
  height: 11px;
  border-radius: 6px;
  background: linear-gradient(90deg, var(--border) 0%, var(--border-loud) 50%, var(--border) 100%);
  background-size: 200% 100%;
  animation: skeleton-sheen 1.4s ease-in-out infinite;
}

@keyframes skeleton-sheen {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

@media (prefers-reduced-motion: reduce) {
  .skeleton-line { animation: none; }
}

/* =========================================================================
   CONTACT MODAL
   -------------------------------------------------------------------------
   The reveal used to append rows into an inline <div> whose six class names
   had NO CSS anywhere in the repo. The result was exactly what it sounds
   like: label and value with no separator, run together into one unstyled
   line reading "Emailjoseph@ondutyops.comBookhttps://arbyn.app". KING JOE:
   "IS THIS NOT SUPPOSED TO OPEN A SMALL NICE GOOD POPUP? WHY THE FUCK IS IT
   JUST PLAIN TEXT STUPIDLY?"

   It is a modal now, not an inline panel, because revealing contact details
   is the single conversion event on a listing page: it is what the listing
   was bought for. An inline block that quietly expands below a button is the
   weakest possible presentation of the one thing that matters on the page.

   Reuses --modal-backdrop and --ease-rise from tokens.css rather than
   inventing a third scrim colour and a third easing curve.
   ========================================================================= */
.contact-modal-scrim {
  position: fixed;
  inset: 0;
  z-index: 60;
  background: var(--modal-backdrop);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  opacity: 0;
  transition: opacity .18s ease;
}
.contact-modal-scrim.is-open { opacity: 1; }

.contact-modal {
  width: min(420px, 100%);
  background: var(--card);
  border: 1px solid var(--border-loud);
  border-radius: var(--radius-card);
  padding: 22px;
  transform: translateY(8px) scale(.985);
  transition: transform .22s var(--ease-rise);
  max-height: calc(100vh - 40px);
  overflow-y: auto;
}
.contact-modal-scrim.is-open .contact-modal { transform: none; }

.contact-modal-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  margin: 0 0 4px;
}
.contact-modal-title {
  font: 700 19px/1.25 var(--font-sans);
  letter-spacing: -.02em;
  color: var(--text);
  margin: 0;
}
.contact-modal-sub {
  font: 400 13px/1.5 var(--font-sans);
  color: var(--text-dim);
  margin: 0 0 16px;
}
.contact-modal-close {
  flex: 0 0 auto;
  background: none;
  border: 0;
  color: var(--text-dim);
  font: 400 22px/1 var(--font-sans);
  cursor: pointer;
  padding: 0 2px;
}
.contact-modal-close:hover { color: var(--text); }

.contact-rows { display: flex; flex-direction: column; gap: 8px; }

/* Each row is its own tappable card, so a phone number and a booking link do
   not read as one paragraph. 44px min-height is the touch target floor. */
.contact-row {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-height: 44px;
  padding: 11px 13px;
  background: var(--inset);
  border: 1px solid var(--border);
  border-radius: 10px;
  text-decoration: none;
  transition: border-color .15s ease, background .15s ease;
}
a.contact-row:hover { border-color: var(--accent); background: var(--card); }

.contact-label {
  font: 600 10.5px/1 var(--font-mono);
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--text-dim);
}
.contact-value {
  font: 500 14.5px/1.4 var(--font-sans);
  color: var(--text);
  word-break: break-word;
}
a.contact-row .contact-value { color: var(--accent); }

.contact-note {
  font: 400 13px/1.55 var(--font-sans);
  color: var(--text-muted);
  margin: 14px 0 0;
  padding-top: 12px;
  border-top: 1px solid var(--border);
}
