/* ============================================================
   Biomate — components
   Every touch target is at least var(--tap). Every colour pairing
   here was checked against WCAG AA before it was written.
   ============================================================ */

/* ---------------- top bar ---------------- */
.topbar {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px var(--pad) 8px;
  min-height: 60px;
}
.topbar .display {
  flex: 1;
  text-align: center;
  font-size: clamp(1.05rem, 5.2vw, 1.55rem);
  line-height: 1.12;
}
.topbar--left .display { text-align: left; }

.iconbtn {
  width: var(--tap); height: var(--tap);
  display: grid; place-items: center;
  border-radius: 50%;
  color: var(--brand-text);
  flex-shrink: 0;
}
.iconbtn--ring { border: 1.5px solid var(--brand-text); width: 34px; height: 34px; }
.iconbtn:active { transform: scale(.94); }

/* ---------------- the inbox button ----------------
   Lives in the Messages topbar. The count sits on the corner of the
   bell rather than beside it, because at 375px the topbar already
   carries a back button, a title and an avatar. */
.bellbtn { position: relative; }
.bellbtn__n {
  position: absolute;
  top: -5px; right: -5px;
  min-width: 17px;
  height: 17px;
  padding: 0 4px;
  border-radius: var(--r-pill);
  background: var(--brand-ink);
  color: var(--on-brand);
  font-size: 10px;
  font-weight: 800;
  line-height: 17px;
  text-align: center;
  box-shadow: 0 0 0 2px var(--bg);
}

.avatar {
  width: 38px; height: 38px;
  border-radius: 50%;
  object-fit: cover;
  background: var(--cream);
  flex-shrink: 0;
}
.avatar--lg { width: 56px; height: 56px; }

/* overlapping cluster — the "+N more" device from reference 24.
   Here it carries "unique people you've hiked with". */
.avstack { display: flex; align-items: center; }
.avstack .avatar { margin-left: -12px; border: 2px solid var(--surface); }
.avstack .avatar:first-child { margin-left: 0; }
.avstack__more {
  margin-left: 8px;
  font-size: var(--t-xs);
  font-weight: 600;
  color: var(--ink-soft);
}

/* ---------------- buttons ---------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: var(--tap);
  padding: 12px 22px;
  border-radius: var(--r-pill);
  font-size: var(--t-md);
  font-weight: 600;
  transition: transform var(--fast) var(--ease-out), background var(--fast);
}
.btn:active { transform: scale(.97); }

/* brand-ink, not brand — white text needs 4.5:1 and only this one has it */
.btn--primary { background: var(--brand-ink); color: var(--on-brand); }
.btn--ghost   { background: var(--surface); color: var(--ink); border: 1px solid var(--hairline); box-shadow: var(--lift-1); }
.btn--block   { width: 100%; }
.btn[disabled] { opacity: .45; cursor: not-allowed; }

/* ---------------- chips ---------------- */
.chips {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  padding: 4px var(--pad) 12px;
  scrollbar-width: none;
}
.chips::-webkit-scrollbar { display: none; }

.chip {
  flex: 0 0 auto;
  min-height: 36px;
  padding: 8px 16px;
  border-radius: var(--r-pill);
  border: 1px solid var(--hairline);
  background: var(--surface);
  color: var(--ink);
  font-size: var(--t-sm);
  white-space: nowrap;
  transition: background var(--fast), color var(--fast), border-color var(--fast);
}
.chip[aria-pressed="true"] {
  background: var(--brand-ink);
  color: var(--on-brand);
  border-color: var(--brand-ink);
}

/* ---------------- cards ---------------- */
.card {
  background: linear-gradient(160deg, var(--peach-0), var(--peach-1));
  border-radius: var(--r-lg);
  padding: 16px;
  box-shadow: var(--lift-1);
}

.row {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  text-align: left;
  padding: 12px 14px;
  border-radius: var(--r-lg);
  background: linear-gradient(160deg, var(--peach-0), var(--peach-1));
  box-shadow: var(--lift-1);
  transition: transform var(--fast) var(--ease-out);
}
.row:active { transform: scale(.985); }
.row__body { flex: 1; min-width: 0; }
.row__title { font-weight: 700; font-size: var(--t-md); }
/* two lines that must not run together — display:block on the pair.
   This exact bug has appeared four times across these projects. */
.row__title, .row__sub { display: block; }
.row__sub {
  font-size: var(--t-sm);
  color: var(--ink-soft);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.stack { display: flex; flex-direction: column; gap: 10px; padding: 0 var(--pad); }

/* ---------------- the swipe deck ----------------
   Three nested layers, because the drag and the sheen both want
   `transform` and whichever writes last wins:
     .deck__card  → JS writes the drag transform here
     .deck__face  → CSS owns the artwork
   ------------------------------------------------ */
.deck {
  position: relative;
  margin: 0 var(--pad);
  height: min(62vh, 460px);
  perspective: 1000px;
}

.deck__card {
  position: absolute;
  inset: 0;
  border-radius: var(--r-xl);
  will-change: transform;
  touch-action: none;              /* the card drags; the page doesn't */
  cursor: grab;
}
.deck__card:active { cursor: grabbing; }

/* A stack needs far bigger offsets than feel right — 6-8px vanishes
   entirely behind the top card. Learned on the foil portfolio.

   ⚠️ And the offsets must be measured, not eyeballed: translateY(14px)
   with scale(.94) put the back cards' bottom edges at 641px and the top
   card's at 641px — the shrink pulled the bottom up by exactly as much
   as the translate pushed it down, so the stack was invisible while
   every value looked reasonable in the stylesheet.
   transform-origin: bottom pins the shrink to the bottom edge, so the
   translate is the only thing moving it and the offset is real. */
.deck__card[data-depth="1"],
.deck__card[data-depth="2"] { transform-origin: bottom center; }
.deck__card[data-depth="1"] { transform: translateY(16px) scale(.955) rotate(-1.6deg); }
.deck__card[data-depth="2"] { transform: translateY(32px) scale(.91)  rotate(1.4deg); }
.deck__card[data-depth="3"] { opacity: 0; pointer-events: none; }

/* The back cards were showing the DARK bottom of their own artwork, so
   the stack read as a black ledge under the top card rather than as
   paper behind paper. Veil them toward the surface colour — they only
   need to say "there are more", not to be legible. */
.deck__card[data-depth="1"] .deck__face::after,
.deck__card[data-depth="2"] .deck__face::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--surface);
  pointer-events: none;
}
.deck__card[data-depth="1"] .deck__face::after { opacity: .58; }
.deck__card[data-depth="2"] .deck__face::after { opacity: .78; }

.deck__face {
  position: absolute;
  inset: 0;
  border-radius: var(--r-xl);
  overflow: hidden;
  background: var(--cream);
  box-shadow: var(--lift-3);
}
.deck__photo { width: 100%; height: 100%; object-fit: cover; }

/* empty-state artwork, drawn — a brand-new group has no favourite
   photo yet, and a missing image must never break a screen */
.deck__placeholder {
  width: 100%; height: 100%;
  display: grid; place-items: center;
  background:
    radial-gradient(circle at 30% 25%, var(--amber) 0%, transparent 45%),
    linear-gradient(160deg, var(--brand-soft), var(--brand-text));
}

.deck__scrim {
  position: absolute;
  inset: auto 0 0 0;
  padding: 20px 18px 18px;
  background: var(--scrim);
  color: var(--on-photo);
  /* belt and braces: a photo can always surprise you */
  text-shadow: 0 1px 3px rgba(0,0,0,.55);
}
/* clamped to two lines: a long title used to eat half the card and
   push the bio out of the scrim */
.deck__name {
  font-size: clamp(1.15rem, 5.2vw, 1.5rem);
  font-weight: 700;
  letter-spacing: -.02em;
  line-height: 1.12;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.deck__line { display: flex; align-items: center; gap: 6px; font-size: var(--t-sm); opacity: .95; margin-top: 2px; }
.deck__bio {
  font-size: var(--t-sm);
  margin-top: 10px;
  opacity: .92;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.deck__tags { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 10px; }
.deck__tag {
  font-size: var(--t-xs);
  padding: 4px 10px;
  border-radius: var(--r-pill);
  background: rgba(255,255,255,.22);
  backdrop-filter: blur(6px);
  color: var(--on-photo);
}

.deck__badge {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: var(--t-xs); font-weight: 600;
  padding: 5px 11px; margin-top: 8px;
  border-radius: var(--r-pill);
  background: rgba(0,0,0,.42);
  backdrop-filter: blur(6px);
  color: var(--on-photo);
}

/* the YES / NOPE stamps that fade in as you drag */
.deck__verdict {
  position: absolute;
  top: 26px;
  padding: 8px 16px;
  border-radius: var(--r-sm);
  border: 3px solid currentColor;
  font-weight: 800;
  font-size: var(--t-lg);
  letter-spacing: .08em;
  opacity: 0;
  pointer-events: none;
}
.deck__verdict--yes  { left: 22px;  color: #1E9E5A; transform: rotate(-12deg); }
.deck__verdict--nope { right: 22px; color: #D64545; transform: rotate(12deg); }

/* the accessible path — these are not decoration, they are how the
   deck is used without a pointer */
.deck__actions {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 28px;
  padding: 22px 0 6px;
}
.deck__act {
  width: 62px; height: 62px;
  border-radius: 50%;
  display: grid; place-items: center;
  box-shadow: var(--lift-2);
  transition: transform var(--fast) var(--ease-spring);
}
.deck__act:active { transform: scale(.9); }
.deck__act--no  { background: var(--surface); color: #D64545; border: 1px solid var(--hairline); }
.deck__act--yes { background: var(--brand-ink); color: var(--on-brand); }

.deck__empty {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  text-align: center;
  padding: 24px;
  color: var(--ink-soft);
}

/* ---------------- bottom nav ---------------- */
/* ---------------- start a group ----------------
   Absolutely positioned INSIDE the nav, which is `position: fixed` on a
   phone — so it anchors to the pill and cannot drift, and being out of
   flow it does not disturb the nav's five equal columns. */
.fab {
  position: absolute;
  right: 0;
  bottom: calc(100% + 12px);
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 12px 18px;
  border-radius: var(--r-pill);
  /* --brand-ink, not --brand: white on #C14E27 is 4.79:1 and clears AA,
     white on #D2552A is 4.14:1 and does not */
  background: var(--brand-ink);
  color: var(--on-brand);
  font-weight: 700;
  font-size: var(--t-sm);
  text-decoration: none;
  white-space: nowrap;
  box-shadow: var(--lift-2);
}
.fab[hidden] { display: none; }
.fab:hover { filter: brightness(1.06); }
.fab:active { transform: scale(.96); }
.fab__ic { display: grid; place-items: center; }

/* On a narrow phone the label competes with the nav beneath it, so the
   button collapses to its icon and keeps its name for assistive tech
   through the anchor's aria-label. */
@media (max-width: 380px) {
  .fab__t { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
  .fab { padding: 14px; }
}

@media (prefers-reduced-motion: reduce) {
  .fab:active { transform: none; }
}

.nav {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(10px + var(--safe-b));
  width: min(420px, calc(100% - 24px));
  height: var(--nav-h);
  display: grid;
  /* four, not five — Photoscan was removed and a phantom column
     would leave the icons drifting left of centre */
  grid-template-columns: repeat(4, minmax(0, 1fr));
  align-items: center;
  background: linear-gradient(160deg, var(--peach-0), var(--peach-1));
  border-radius: var(--r-pill);
  box-shadow: var(--lift-2);
  z-index: 60;
}
.nav__item {
  position: relative;      /* the unread badge anchors to this */
  height: 100%;
  display: grid; place-items: center;
  color: var(--brand-text);
  border-radius: var(--r-pill);
}
.nav__item[aria-current="page"] { color: var(--brand-ink); }
.nav__item[aria-current="page"] svg { fill: currentColor; }
.nav__item:active { transform: scale(.9); }

/* ---------------- unread count ----------------
   The number, not a bare dot. "You have something" and "you have
   eleven things" are different enough to act on differently, and the
   digit costs the same 18 pixels the dot would have.

   ⚠️ --brand-ink + --on-brand rather than brand + white. That pair
   is the audited one: white on the lifted dark-mode terracotta is
   3.40:1, and this is small text, so it needs the token that flips
   to a dark ground in dark mode instead. */
.nav__badge {
  position: absolute;
  top: 8px;
  left: 50%;
  margin-left: 4px;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: var(--r-pill);
  background: var(--brand-ink);
  color: var(--on-brand);
  font-size: 11px;
  font-weight: 800;
  line-height: 18px;
  text-align: center;
  /* against the peach nav so it reads as ON the icon, not behind it */
  box-shadow: 0 0 0 2px var(--peach-1);
  pointer-events: none;
}

/* In the rail there is a label to sit beside, so it stops floating
   over the icon and becomes an ordinary trailing count. */
@media (min-width: 900px) {
  .nav__badge {
    position: static;
    margin-left: auto;
    box-shadow: none;
  }
  .nav__item[aria-current="page"] .nav__badge {
    background: var(--on-brand);
    color: var(--brand-ink);
  }
}

/* ---------------- toast ---------------- */
.toast {
  position: fixed;
  left: 50%;
  bottom: calc(var(--nav-h) + var(--safe-b) + 20px);
  transform: translate(-50%, 12px);
  background: var(--ink);
  color: var(--bg);
  padding: 12px 20px;
  border-radius: var(--r-pill);
  font-size: var(--t-sm);
  box-shadow: var(--lift-2);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--med) var(--ease-out), transform var(--med) var(--ease-out);
  z-index: 90;
}
.toast[data-show="1"] { opacity: 1; transform: translate(-50%, 0); }


/* ---------------- top bar ----------------
   Sticky, because the streak and level are ambient state — you should
   be able to see them from any screen without going to look. */
.appbar {
  position: sticky;
  top: 0;
  z-index: 70;
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 52px;
  padding: 8px var(--pad);
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--hairline);
}

.appbar__brand {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: var(--tap);
  flex-shrink: 0;
}
.appbar__word {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 900;
  font-size: 1.15rem;
  letter-spacing: -.03em;
}

/* the connection state: a dot, not a sentence. The full text lives in
   aria-label, so it is still announced — it just stops eating a row. */
.appbar__dot {
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--forest);
  flex-shrink: 0;
}
.appbar__dot--warn { background: var(--amber); }

/* ---------------- the account control in the top bar ----------------
   Filled, where the level and streak chips are outlined, so it reads as
   the one ACTION up there rather than a third status pill. */
.acctbtn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 34px;
  padding: 0 14px;
  border-radius: var(--r-pill);
  background: var(--brand-ink);
  color: var(--on-brand);
  font-size: var(--t-xs);
  font-weight: 700;
  white-space: nowrap;
  flex-shrink: 0;
}
.acctbtn:hover { filter: brightness(1.06); }
.acctbtn:active { transform: scale(.96); }

/* once you are in it is identification, not an action — so it drops to
   a quiet circle and gives its width back to the chips */
.acctbtn--in {
  width: 34px;
  padding: 0;
  background: var(--cream);
  border: 1px solid var(--hairline);
  color: var(--brand-text);
}
.acctbtn--in:hover { filter: none; border-color: var(--brand); }

/* Four things do not fit across 375px. The wordmark goes first: the
   mark beside it already says where you are, and it is the only one of
   the four that is pure decoration at this width. */
@media (max-width: 420px) {
  .appbar__word { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .acctbtn:active { transform: none; }
}

/* the reason a card is in front of you, on the card itself */
.deck__why {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  align-self: flex-start;
  margin-top: 8px;
  padding: 5px 10px;
  border-radius: var(--r-pill);
  background: rgba(255, 255, 255, .18);
  color: var(--on-photo);
  font-size: var(--t-xs);
  font-weight: 700;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  /* the scrim underneath varies with the photo, so the text carries its
     own shadow rather than trusting the blur alone */
  text-shadow: 0 1px 3px rgba(0, 0, 0, .55);
}
.deck__why svg { flex-shrink: 0; }

.chipstat {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 34px;
  padding: 0 12px;
  border-radius: var(--r-pill);
  background: var(--cream);
  border: 1px solid var(--hairline);
  font-size: var(--t-xs);
  font-weight: 700;
  white-space: nowrap;
  flex-shrink: 0;
}
.chipstat--level .chipstat__lv { color: var(--brand-text); }
.chipstat--level .chipstat__name { color: var(--ink-soft); font-weight: 600; }
/* the level NAME is the first thing to go when space is tight — the
   number carries the meaning on its own */
@media (max-width: 400px) { .chipstat--level .chipstat__name { display: none; } }

.chipstat--streak { color: var(--ink-faint); }
.chipstat--streak.is-lit { color: var(--brand-ink); background: #FDECE2; border-color: transparent; }
.chipstat--streak b { font-variant-numeric: tabular-nums; }

/* ---------------- other people's level + streak ----------------
   The "how experienced, and are they still turning up" signal, small
   enough to sit beside a name in a list. */
.pbadge { display: inline-flex; align-items: center; gap: 6px; margin-left: 6px; vertical-align: middle; }
.pbadge__lv {
  font-size: 10px;
  font-weight: 800;
  letter-spacing: .02em;
  padding: 2px 7px;
  border-radius: var(--r-pill);
  background: var(--brand-ink);
  color: var(--on-brand);
}
.pbadge__streak {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  font-size: 10px;
  font-weight: 700;
  color: var(--brand-ink);
}
/* on a photo scrim the badge needs its own contrast, not the page's */
.deck__scrim .pbadge__streak { color: var(--on-photo); }

.deck__host {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: var(--t-xs);
  margin-top: 6px;
  opacity: .95;
}

/* an avatar you can open. The button adds no box of its own — the
   avatar IS the target — but it still needs a real focus ring. */
.avstack__btn, .msg__who-btn { display: block; border-radius: 50%; flex-shrink: 0; }
.avstack__btn { margin-left: -12px; }
.avstack__btn:first-child { margin-left: 0; }
.avstack__btn .avatar { margin-left: 0; }
.avstack__btn:active, .msg__who-btn:active { transform: scale(.92); }
.avatar--xl { width: 76px; height: 76px; }

/* ---------------- leaving a group ----------------
   Mounted twice: quietly in the chat header, and under the one
   action on the hike page. Right-aligned because it is never the
   reason anyone opened either screen — but full size, not a tiny
   link, because the confirm that follows it has to be hit
   accurately and 44px is the floor everywhere else in this app.

   No new colour is invented for "destructive". The palette already
   contains a deep red that passes on light and lifts on dark
   (--brand-text), and using the brand's own serious tone reads as
   part of the app rather than as a warning bolted on from a kit. */
/* Left aligned, per Aufan. It used to sit right, which put it directly
   under the primary action and read as a second, competing button. On
   the left it lines up with the page's own text column and reads as a
   footnote to the page rather than a choice being offered. */
.leavebar {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  flex-wrap: wrap;
  gap: 10px;
  padding: 0 var(--pad) 12px;
}
/* on the hike page it is a section like any other, not a header strip */
.leavebar--block { padding: 4px var(--pad) 18px; }

.leavebar .btn { font-size: var(--t-sm); padding: 10px 16px; }

/* The question replaces the button in place and takes a whole line of
   its own. Sharing the line was tried first: at 375px the question,
   "Stay" and "Leave" do not fit, so the two answers were split across
   two rows — which is the one arrangement a yes/no question must never
   have. Question above, both answers together below. */
.leavebar__q {
  flex: 1 1 100%;
  min-width: 0;
  font-size: var(--t-sm);
  color: var(--ink);
  text-align: left;
}

.leavebar__note {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  flex: 1 1 100%;
  min-width: 0;
  font-size: var(--t-xs);
  color: var(--ink-soft);
  text-align: left;
}
.leavebar__ic { color: var(--ink-faint); flex-shrink: 0; line-height: 0; margin-top: 1px; }

/* Aufan looked at the outlined version and said "still not red" — and
   he was right: red text on a white pill reads as ordinary until you
   are already reading it. A wash behind it makes the colour the first
   thing you see, while staying outlined rather than filled so it never
   competes with the primary action above it.
   Measured: --brand-text on --danger-wash is 6.0:1 light, 6.34:1 dark. */
.btn--leave {
  background: var(--danger-wash);
  color: var(--brand-text);
  border: 1.5px solid var(--brand-text);
  box-shadow: none;
}
.btn--leave:hover { filter: brightness(0.97); }

/* ---------------- read-what-I-point-at ----------------
   ::highlight paints a Range without wrapping it in an element, so the
   sentence being spoken can be marked without mutating a screen the
   router may re-render underneath it — and without destroying a text
   selection the reader already made.

   Colour, not weight or size: changing either would reflow the text
   under the pointer and move the sentence away from the cursor that
   selected it. */
::highlight(readaloud) {
  background: var(--read-wash);
  color: var(--ink);
}
