/* ══════════════════════════════════════════════════════════════════
   Queendom Farm — the app.

   Built to be used standing in a field with one hand and a cold thumb, so:
   a fixed bottom bar within thumb reach, targets no smaller than 44px, and
   safe-area insets everywhere because this runs full-screen from the home
   screen and iOS will happily put the tab bar under the gesture indicator.

   The two rules the visual language has to carry, same as the board:
   a proposal must not look like a decision, and a rota must not look like a
   job that finishes.

   THE MOTION RULE. Everything eases on one curve and moves only `transform`
   and `opacity`. Mixing easings is what makes an app feel like a web page,
   and animating anything that hits layout drops a mid-range Android to 30fps
   — which on this app means a list of 31 missions juddering in somebody's
   cold hand. There is exactly one curve here, and a spring used only where a
   thumb is dragging something.
   ══════════════════════════════════════════════════════════════════ */

:root {
  --bg: #12100d;
  --bg-2: #1a1712;
  --panel: #1e1a15;
  --panel-2: #262019;
  --line: #342d23;
  --line-2: #453c2e;
  --ink: #f4efe5;
  --dim: #a99e8b;
  --faint: #7a705f;

  --life: #86b96f;
  --solar: #e3a95c;
  --now: #d76d5d;

  /* ─── the valley ────────────────────────────────────────────────
     Queendom Farm is in Hirschwang, in the valley the Kaiserbrunn spring
     falls through on its way to Vienna — 927 m down to 179 m. That valley
     already has a film about it (~/Projects/valley-of-water), and these two
     values are lifted straight out of its palette rather than picked to
     match it: the water it is named for, and the parchment of the 1873
     archive plates. Water marks anything that recurs — a rota is the farm's
     own version of a thing that keeps arriving — and parchment marks the
     guide's recorded words, which are this farm's archive. */
  --water: #bfeaff;
  --parchment: #f0e2cd;

  --r: 14px;
  --r-lg: 20px;
  --r-xl: 28px;
  --tab-h: 56px;
  --safe-b: env(safe-area-inset-bottom, 0px);
  --safe-t: env(safe-area-inset-top, 0px);

  /* the house curve, and the one spring a thumb is allowed to feel */
  --ease: cubic-bezier(.22,.61,.36,1);
  --spring: cubic-bezier(.32,.72,0,1);
  --dur-fast: .18s;
  --dur: .30s;
  --dur-slow: .5s;

  --shadow: 0 1px 2px rgba(0,0,0,.4), 0 6px 18px -8px rgba(0,0,0,.7);
  --shadow-up: 0 -8px 40px -12px rgba(0,0,0,.85);
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

/* Several things here set `display` (the tab bar, the placing banner) and a
   bare `display:flex` beats the UA stylesheet's rule for [hidden]. Without
   this the "line up the pin" banner is on screen permanently. */
[hidden] { display: none !important; }

html, body { height: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 15px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  -webkit-font-smoothing: antialiased;
  overscroll-behavior-y: none;
  /* The app is full-screen from the home screen; a rubber-band that reveals
     the browser's white ground under a near-black app is the single loudest
     "this is a web page" tell there is. */
  position: fixed; inset: 0;
  /* A column, so the app takes what the sign-in gate leaves rather than a
     flat 100%. See the note on #gate: this is what keeps the tab bar on
     screen no matter what the Hub's auth renders above it. */
  display: flex; flex-direction: column;
}

button, input, select, textarea { font: inherit; color: inherit; }
button { cursor: pointer; }

/* The ground under everything: a warm wash off the top-left, so the near
   black is lit rather than flat. Fixed, so it does not travel with scroll. */
body::before {
  content: ""; position: fixed; inset: 0; z-index: 0; pointer-events: none;
  background:
    radial-gradient(120% 70% at 12% -8%, rgba(227,169,92,.09), transparent 62%),
    radial-gradient(90% 60% at 92% 4%, rgba(134,185,111,.055), transparent 58%);
}

/* ─── shell ──────────────────────────────────────────────────────── */

/* ─── the front door ─────────────────────────────────────────────
   The Commons Hub's sign-in gate renders here and then empties itself. The
   node stays behind, and it used to keep its 24px of padding — 48px of empty
   box above an app that was still sizing itself to a flat `height: 100%`.
   Total: 48px taller than the screen, with the tab bar pushed exactly that
   far below the fold. The menu was not missing, it was underneath the phone.
   Two guards, because either alone is one assumption about somebody else's
   auth code: the empty gate takes no space, and the app takes what is left
   rather than all of it.

   🪤 AND THE GATE BRINGS NO CSS. commons.js writes its own markup and toggles
   a `.hidden` class fourteen times, but the rule that makes `.hidden` mean
   anything lives in the Commons Hub's stylesheet, which this app does not
   load. Without it every step of the passport — the known device, the email,
   the code, the keys — renders stacked on top of each other, all at once, as
   the first thing anybody sees.

   So the passport is dressed here, in its own language rather than the
   farm's: Instrument Serif for the display, JetBrains Mono for the eyebrows,
   the glass card, the pill button in solar gradient, the same house curve.
   These values are ported from commons-flowme/dist/assets/commons.css so the
   door looks the same whichever FlowBond space it is opening — the passport
   is one object, and an object that changes its face at every threshold is
   not a passport, it is a form. What is the farm's, and only the farm's, is
   the ground it stands on and the doorway above it. */

#gate, #doorway {
  /* The Passport's own palette, kept apart from the app's so neither can
     drift into the other. */
  --pp-night:    #060A07;
  --pp-glass:    rgba(18,28,21,.62);
  --pp-paper:    #EDE7D7;
  --pp-dim:      #B9C2B6;
  --pp-ash:      #7E8C81;
  --pp-solar:    #F5B95A;
  --pp-solar-hot:#FFDD97;
  --pp-line:     rgba(237,231,215,.11);
  --pp-line-2:   rgba(237,231,215,.2);
  --pp-serif: "Instrument Serif", "Times New Roman", serif;
  --pp-sans:  "Instrument Sans", system-ui, sans-serif;
  --pp-mono:  "JetBrains Mono", ui-monospace, monospace;
}

/* The ground the door stands on: the farm's warm dark, lifted toward the
   Hub's night so the glass card reads as the same material it does there. */
body:has(#gate:not(:empty))::before {
  background:
    radial-gradient(120% 80% at 50% -10%, rgba(245,185,90,.10), transparent 60%),
    radial-gradient(90% 60% at 50% 110%, rgba(111,217,162,.06), transparent 60%);
}

#doorway {
  flex: none; text-align: center;
  padding: max(6vh, 28px) 18px 0;
  font-family: var(--pp-sans);
  animation: rise var(--dur-slow) var(--ease) both;
}
#doorway[hidden] { display: none; }
#doorway .mark {
  width: 46px; height: 46px; margin: 0 auto 14px;
  display: grid; place-items: center; border-radius: 50%;
  border: 1px solid var(--pp-line-2);
  background: var(--pp-glass);
  backdrop-filter: blur(20px) saturate(1.15);
  -webkit-backdrop-filter: blur(20px) saturate(1.15);
}
#doorway .glyph { width: 22px; height: 22px; color: var(--pp-solar); display: block; }
#doorway .where {
  margin: 0; font-family: var(--pp-serif); font-weight: 400;
  font-size: 30px; line-height: 1.06; letter-spacing: -.012em;
  color: var(--pp-paper);
}
#doorway .place {
  margin: 7px 0 0; font-family: var(--pp-mono);
  font-size: .66rem; letter-spacing: .19em; text-transform: uppercase;
  color: var(--pp-ash);
}

#gate {
  flex: 1; min-height: 0; overflow-y: auto;
  display: grid; place-items: center;
  padding: 22px 18px calc(22px + var(--safe-b));
  font-family: var(--pp-sans);
  color: var(--pp-paper);
}
#gate:empty { display: none; padding: 0; }

/* The bug. Everything else here is dress; this line is the fix. */
#gate .hidden { display: none !important; }

#gate .card {
  width: 100%; max-width: 452px; margin: 0;
  position: relative;
  background: var(--pp-glass);
  backdrop-filter: blur(20px) saturate(1.15);
  -webkit-backdrop-filter: blur(20px) saturate(1.15);
  border: 1px solid var(--pp-line); border-radius: 16px;
  padding: 24px 26px;
  box-shadow: 0 24px 70px -30px rgba(0,0,0,.9);
}
/* The app's mission-card stripe has no business on a passport. */
#gate .card::before { display: none; }

/* `.rv` is the Hub's reveal: hidden until `.in` is added. Without the rule
   the card is simply visible, which is correct — but with it the door opens
   the way it does at the Hub. */
#gate .rv { opacity: 0; transform: translateY(18px);
            transition: opacity var(--dur-slow) var(--ease) var(--d, 0ms),
                        transform var(--dur-slow) var(--ease) var(--d, 0ms); }
#gate .rv.in { opacity: 1; transform: none; }

#gate .eyebrow {
  display: block; font-family: var(--pp-mono); font-size: .66rem;
  letter-spacing: .19em; text-transform: uppercase; color: var(--pp-ash);
}
.areahead { display: flex; align-items: center; gap: 9px; }
.areahead .ic { width: 20px; height: 20px; color: var(--life); }

#gate h2 {
  font-family: var(--pp-serif); font-weight: 400; font-size: 2rem;
  letter-spacing: -.012em; line-height: 1.06; text-wrap: balance;
  color: var(--pp-paper);
}
#gate .muted { color: var(--pp-dim); }
/* The Hub's note is a rule in the margin rather than a box. */
#gate .note {
  border-left: 1px solid var(--pp-line-2);
  padding: 3px 0 3px 16px; margin: 12px 0;
  font-size: .94rem; color: var(--pp-dim);
}
#gate .note b { color: var(--pp-paper); font-weight: 500; }

#gate .field { margin-bottom: 16px; }
#gate .field .eyebrow { margin-bottom: 7px; }
#gate input {
  width: 100%; background: rgba(6,10,7,.6); color: var(--pp-paper);
  border: 1px solid var(--pp-line); border-radius: 11px; padding: 12px 14px;
  font-family: inherit;
  font-size: 16px;   /* 16px or iOS zooms the whole page on focus */
  transition: border-color var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease);
}
#gate input:focus {
  outline: none; border-color: rgba(245,185,90,.55);
  box-shadow: 0 0 0 3px rgba(245,185,90,.12);
}
#gate input::placeholder { color: var(--pp-ash); }
#gate .mono { font-family: var(--pp-mono); }

#gate .btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  background: linear-gradient(180deg, var(--pp-solar-hot), var(--pp-solar));
  color: #1B1206; border: none;
  font-family: var(--pp-sans); font-weight: 600; font-size: .93rem;
  padding: 12px 24px; border-radius: 99px; min-height: 0;
  transition: transform var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease);
}
#gate .btn:active:not(:disabled) { transform: scale(.97); }
#gate .btn.wide  { width: 100%; }
#gate .btn.link  { background: none; border: none; color: var(--pp-ash);
                   padding: 4px 2px; font-size: .85rem; font-weight: 400;
                   text-decoration: underline; box-shadow: none; }
#gate .btn.ghost { background: transparent; color: var(--pp-paper);
                   border: 1px solid var(--pp-line-2); box-shadow: none; }
#gate .btn.small { padding: 8px 16px; font-size: .82rem; }

#gate .row { display: flex; gap: 10px; align-items: center; }
#gate .orline {
  display: flex; align-items: center; gap: 14px; margin: 22px 0 16px;
  color: var(--pp-ash); font-size: .78rem;
  letter-spacing: .14em; text-transform: uppercase;
}
#gate .orline::before, #gate .orline::after {
  content: ""; flex: 1; height: 1px; background: var(--pp-line);
}
/* Four ways in, two by two, rather than a row that wraps a lonely fourth. */
#gate .keys { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 8px; }
#gate .keys .btn { width: 100%; }
#gate .err { color: #F0937C; font-size: .87rem; margin-top: 12px; min-height: 1.2em; }

@media (min-width: 720px) {
  #doorway .where { font-size: 38px; }
}

.app { display: none; flex: 1; min-height: 0; flex-direction: column;
       position: relative; z-index: 1; }
.app.on { display: flex; }

/* ─── the header, which collapses ────────────────────────────────
   A large title that shrinks into a compact bar on scroll. The collapse is
   driven by a class JS toggles at a threshold, never by a scroll-linked
   style write — a per-frame layout write here is exactly the jank the
   motion rule exists to prevent. */

.bar {
  position: relative; z-index: 30; flex: none;
  padding: calc(var(--safe-t) + 16px) 18px 12px;
  background: linear-gradient(180deg, var(--bg) 60%, rgba(18,16,13,.86) 88%, rgba(18,16,13,0));
  display: grid; grid-template-columns: 1fr auto; align-items: center;
  gap: 2px 12px;
  transition: padding var(--dur) var(--ease);
}
.bar::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1px;
  background: var(--line); opacity: 0;
  transition: opacity var(--dur) var(--ease);
}
.bar.tight { padding-top: calc(var(--safe-t) + 9px); padding-bottom: 9px; }
.bar.tight::after { opacity: 1; }

.bar h1 {
  grid-column: 1; margin: 0; font-size: 30px; font-weight: 680;
  letter-spacing: -.028em; line-height: 1.12;
  transform-origin: left center;
  transition: transform var(--dur) var(--ease);
}
.bar.tight h1 { transform: scale(.62); }

.bar .sub {
  grid-column: 1; grid-row: 2; color: var(--faint); font-size: 12.5px;
  transform-origin: left top;
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.bar.tight .sub { opacity: 0; transform: translateY(-7px); }

/* The ember count is the one number that must never be hidden — it is what
   the whole board pays out in. It rides the collapse instead of leaving. */
.bar .embers {
  grid-column: 2; grid-row: 1 / span 2; align-self: center;
  color: var(--solar); font-size: 13px; font-weight: 640;
  font-variant-numeric: tabular-nums;
  display: flex; align-items: center; gap: 5px;
  padding: 5px 11px; border-radius: 99px;
  background: rgba(227,169,92,.10); border: 1px solid rgba(227,169,92,.22);
  transition: transform var(--dur) var(--spring);
}
.bar .embers.bump { transform: scale(1.16); }

.nosignal {
  display: none; flex: none; margin: 0;
  padding: 7px 18px; font-size: 12.5px; font-weight: 560;
  color: #f2d9b0; background: rgba(227,169,92,.13);
  border-bottom: 1px solid rgba(227,169,92,.24);
  animation: rise var(--dur) var(--ease) both;
}
body.offline .nosignal { display: block; }

/* Views stack; only one is on. The map keeps its DOM alive when hidden so
   MapLibre does not re-initialise (and re-download tiles) on every tab. */
.view { display: none; flex: 1; min-height: 0; position: relative; }
.view.on { display: block; }
.view.scroll {
  overflow-y: auto; -webkit-overflow-scrolling: touch;
  overscroll-behavior-y: contain;
  padding: 0 15px 30px;
}
.view.flush { padding: 0; }

/* The view that is arriving. Direction comes from which tab you left. */
.view.in-l { animation: slide-l var(--dur) var(--ease); }
.view.in-r { animation: slide-r var(--dur) var(--ease); }
@keyframes slide-l { from { opacity: 0; transform: translate3d(-16px,0,0); } }
@keyframes slide-r { from { opacity: 0; transform: translate3d(16px,0,0); } }

.wrap { max-width: 820px; margin: 0 auto; }

/* Reveal, don't animate: content arrives with opacity and a short lift.
   Staggered in tens — past ~240ms of delay it reads as lag, not rhythm. */
.rise { animation: rise var(--dur-slow) var(--ease) both; animation-delay: var(--d, 0ms); }
@keyframes rise { from { opacity: 0; transform: translate3d(0,10px,0); } }

/* ─── pull to refresh ────────────────────────────────────────────── */

.ptr {
  position: absolute; left: 50%; top: 0; z-index: 20;
  width: 30px; height: 30px; margin-left: -15px;
  border-radius: 50%; background: var(--panel-2);
  border: 1px solid var(--line-2); box-shadow: var(--shadow);
  display: grid; place-items: center;
  transform: translate3d(0, calc(var(--pull, 0px) - 42px), 0) rotate(var(--spin, 0deg));
  opacity: clamp(0, calc(var(--pull, 0px) / 46), 1);
  pointer-events: none;
}
.ptr::before {
  content: ""; width: 13px; height: 13px; border-radius: 50%;
  border: 2px solid var(--solar); border-top-color: transparent;
}
.ptr.settling { transition: transform var(--dur) var(--spring), opacity var(--dur) var(--ease); }
.ptr.spinning::before { animation: spin .7s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }

/* ─── tab bar ────────────────────────────────────────────────────── */

.tabs {
  position: relative; z-index: 40; flex: none;
  height: calc(var(--tab-h) + var(--safe-b));
  padding-bottom: var(--safe-b);
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  background: rgba(22,19,15,.86);
  backdrop-filter: saturate(170%) blur(20px);
  -webkit-backdrop-filter: saturate(170%) blur(20px);
  border-top: 1px solid var(--line);
}
/* The indicator slides between tabs rather than blinking on — it is what
   tells a thumb the two views are side by side, not stacked. */
.tabs::before {
  content: ""; position: absolute; top: -1px; height: 2px; border-radius: 99px;
  width: calc(100% / 4); left: 0; background: var(--life);
  transform: translate3d(calc(var(--tabi, 0) * 100%), 0, 0) scaleX(.34);
  transition: transform var(--dur) var(--spring);
}
.tabs button {
  position: relative;
  background: none; border: 0; color: var(--faint);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 3px; font-size: 10.5px; letter-spacing: .02em; padding: 0;
  transition: color var(--dur-fast) var(--ease);
}
/* One drawing at three sizes. They take colour from the text around them,
   which is why a selected tab, a section heading and a chip all look like
   they were drawn by the same hand — because they were. */
.ic { width: 22px; height: 22px; display: block; flex: none; }
.tabs button .ic { transition: transform var(--dur) var(--spring); }
.tabs button[aria-selected="true"] { color: var(--life); }
.tabs button[aria-selected="true"] .ic { transform: translateY(-1px) scale(1.14); }
.tabs button:active .ic { transform: scale(.88); }
.tabs button .dot {
  position: absolute; top: 6px; left: 50%; margin-left: 4px;
  min-width: 16px; height: 16px; border-radius: 99px;
  background: var(--now); color: #fff; font-size: 10px; font-weight: 700;
  display: grid; place-items: center; padding: 0 4px;
  box-shadow: 0 0 0 2px rgba(22,19,15,.9);
}

/* ─── today, the head of the board ───────────────────────────────
   The app used to open on a list of 31 things, which is a filing cabinet,
   not a farm. This is the one glance that answers "what is happening here
   today" before a thumb has moved. */

.today {
  position: relative; overflow: hidden;
  border: 1px solid var(--line); border-radius: var(--r-xl);
  background:
    radial-gradient(130% 100% at 100% 0%, rgba(227,169,92,.13), transparent 58%),
    linear-gradient(168deg, var(--panel-2), var(--panel) 62%);
  padding: 16px 17px 15px; margin: 4px 0 14px;
  box-shadow: var(--shadow);
}
.today .when { font-size: 11px; text-transform: uppercase; letter-spacing: .12em;
               color: var(--solar); font-weight: 660; }
.today .line { display: flex; align-items: center; gap: 14px; margin-top: 9px; }
.today .say { flex: 1; min-width: 0; }
.today .say b { display: block; font-size: 17.5px; font-weight: 640;
                letter-spacing: -.015em; line-height: 1.28; }
.today .say p { margin: 4px 0 0; color: var(--dim); font-size: 13px; }

/* the ring: done-today against the day's rota */
.ring { flex: none; width: 58px; height: 58px; position: relative; }
.ring svg { width: 100%; height: 100%; transform: rotate(-90deg); display: block; }
.ring circle { fill: none; stroke-width: 5; stroke-linecap: round; }
.ring .track { stroke: var(--line); }
.ring .live {
  stroke: var(--life);
  stroke-dasharray: var(--circ) var(--circ);
  stroke-dashoffset: var(--circ);
  transition: stroke-dashoffset 1.1s var(--ease);
}
.ring b { position: absolute; inset: 0; display: grid; place-items: center;
          font-size: 13px; font-weight: 660; font-variant-numeric: tabular-nums; }

.today .row { margin-top: 13px; }

/* ─── the filter, a segmented control ────────────────────────────── */

.seg {
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  position: relative; padding: 3px; margin: 0 0 13px;
  background: rgba(0,0,0,.28); border: 1px solid var(--line);
  border-radius: 11px;
}
.seg::before {
  content: ""; position: absolute; top: 3px; bottom: 3px; left: 3px;
  width: calc((100% - 6px) / var(--segn, 4));
  background: var(--panel-2); border: 1px solid var(--line-2);
  border-radius: 8px; box-shadow: var(--shadow);
  transform: translate3d(calc(var(--segi, 0) * 100%), 0, 0);
  transition: transform var(--dur) var(--spring);
}
.seg button {
  position: relative; z-index: 1;
  background: none; border: 0; color: var(--faint);
  padding: 7px 4px; font-size: 12.5px; font-weight: 560; min-height: 36px;
  border-radius: 8px;
  transition: color var(--dur-fast) var(--ease);
}
.seg button[aria-selected="true"] { color: var(--ink); }
.seg button .n {
  font-variant-numeric: tabular-nums; color: var(--faint);
  font-size: 11px; margin-left: 4px;
}
.seg button[aria-selected="true"] .n { color: var(--solar); }

/* ─── cards ──────────────────────────────────────────────────────── */

.card {
  position: relative;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r); padding: 13px 15px 13px 17px; margin: 0 0 9px;
  box-shadow: var(--shadow);
  transition: transform var(--dur-fast) var(--ease), opacity var(--dur) var(--ease);
}
/* The priority stripe is a gradient inside the radius rather than a flat
   border-left, so the corner does not go square where the stripe meets it. */
.card::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  border-radius: var(--r) 0 0 var(--r);
  background: var(--stripe, var(--line));
}
.card.now { --stripe: linear-gradient(180deg, var(--now), rgba(215,109,93,.35)); }
.card.soon { --stripe: linear-gradient(180deg, var(--solar), rgba(227,169,92,.32)); }
.card.normal { --stripe: linear-gradient(180deg, var(--life), rgba(134,185,111,.3)); }
.card.someday { --stripe: var(--line-2); }

/* A proposal must not look like a decision. Dashed, dimmer ground, and it
   never gets the solid stripe a published mission has. */
.card.draft {
  border-style: dashed; background: var(--panel-2);
  box-shadow: none;
}
.card.draft::before { opacity: .45; }
.card.done { opacity: .48; }
.card.blocked { opacity: .72; }
.card[data-act-able]:active { transform: scale(.988); }

/* struck through the moment a thumb lands, before the server has answered */
.card.doing { opacity: .62; }

.card h3 { margin: 0; font-size: 16px; font-weight: 620; letter-spacing: -.01em;
           display: flex; gap: 8px; align-items: baseline; flex-wrap: wrap; }
.card .brief { color: var(--dim); font-size: 13.5px; margin: 6px 0 0; }

.tags { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 9px; }
.tag { font-size: 11.5px; padding: 2px 8px; border-radius: 99px;
       border: 1px solid var(--line); color: var(--dim); background: rgba(0,0,0,.22); }
.tag.pri-now { color: var(--now); border-color: rgba(215,109,93,.42); }
.tag.recurs { color: var(--water); border-color: rgba(191,234,255,.3); }
.tag.points { color: var(--solar); border-color: rgba(227,169,92,.36);
              font-variant-numeric: tabular-nums; }

.origin { font-size: 10.5px; padding: 2px 8px; border-radius: 99px;
          font-weight: 660; letter-spacing: .03em; white-space: nowrap; }
.origin.heard { color: #d3e6c6; background: rgba(134,185,111,.17); }
.origin.inferred { color: #f2d9b0; background: rgba(227,169,92,.17); }
.origin.human { color: var(--faint); background: rgba(255,255,255,.05); }

.said { margin: 10px 0 0; padding: 8px 12px; border-left: 2px solid rgba(240,226,205,.28);
        color: var(--parchment); opacity: .78; font-size: 13px; font-style: italic; }
.said .who { display: block; font-style: normal; font-size: 11px;
             color: var(--faint); margin-top: 5px; }

.clips { margin-top: 9px; }
.clips summary { cursor: pointer; font-size: 12.5px; color: var(--faint);
                 list-style: none; padding: 3px 0; }
.clips summary::-webkit-details-marker { display: none; }
.clips summary::before { content: "▸"; display: inline-block; margin-right: 6px; }
.clips[open] summary::before { content: "▾"; }

.blockers { margin-top: 9px; font-size: 12.5px; color: var(--solar); }
.blockers b { font-weight: 600; }

.row { display: flex; gap: 8px; margin-top: 11px; flex-wrap: wrap; align-items: center; }

.btn { border-radius: 10px; padding: 8px 14px; font-size: 13.5px; font-weight: 500;
       border: 1px solid var(--line); background: var(--panel-2); min-height: 38px;
       transition: transform var(--dur-fast) var(--ease),
                   opacity var(--dur-fast) var(--ease),
                   border-color var(--dur-fast) var(--ease); }
.btn:active:not(:disabled) { transform: scale(.94); }
.btn:hover:not(:disabled) { border-color: var(--line-2); }
.btn:disabled { opacity: .4; cursor: default; }
.btn.go { background: var(--life); border-color: var(--life); color: #10210a; font-weight: 640; }
.btn.quiet { background: transparent; color: var(--dim); }
.btn.danger { background: transparent; color: var(--now); border-color: rgba(215,109,93,.32); }
.btn.sm { padding: 5px 11px; min-height: 32px; font-size: 12.5px; }

.done-note { color: var(--life); font-size: 13px; }
.msg { color: var(--now); font-size: 13px; margin-top: 8px; }
.msg[hidden] { display: none; }

/* ─── section headers ────────────────────────────────────────────── */

.sec { margin: 22px 0 0; }
.sec > h2 { font-size: 12px; text-transform: uppercase; letter-spacing: .1em;
            color: var(--faint); margin: 0 0 2px; font-weight: 640;
            display: flex; align-items: center; gap: 8px; }
.sec > h2 .ic { width: 16px; height: 16px; color: var(--life); opacity: .75; }
.sec > h2 .k { margin-left: auto; text-transform: none; letter-spacing: 0;
               font-weight: 500; color: var(--faint); font-variant-numeric: tabular-nums; }
.sec > .blurb { color: var(--faint); font-size: 12.5px; margin: 0 0 10px; }

.banner { border: 1px dashed var(--solar); border-radius: var(--r);
          padding: 13px 15px; margin: 14px 0 4px;
          background: linear-gradient(180deg, rgba(227,169,92,.10), rgba(227,169,92,.03)); }
.banner b { color: var(--solar); }
.banner p { margin: 5px 0 0; color: var(--dim); font-size: 13px; }

.empty { color: var(--faint); padding: 40px 16px; text-align: center; font-size: 14px; }
.empty .ic.big { width: 34px; height: 34px; margin: 0 auto 12px; opacity: .38; }

/* ─── skeletons ──────────────────────────────────────────────────
   The app used to show nothing at all until four RPCs had answered, then
   snap into a full board. On a phone in a field that reads as broken. The
   skeleton is the same shape as what replaces it, so nothing jumps. */

.sk { border-radius: var(--r); background: var(--panel);
      border: 1px solid var(--line); padding: 13px 15px; margin: 0 0 9px; }
.sk i { display: block; height: 11px; border-radius: 6px; margin: 0 0 8px;
        background: linear-gradient(90deg, var(--panel-2) 25%, var(--line) 50%, var(--panel-2) 75%);
        background-size: 280% 100%; animation: sweep 1.35s var(--ease) infinite; }
.sk i:last-child { margin-bottom: 0; }
.sk i.w70 { width: 70%; } .sk i.w45 { width: 45%; } .sk i.w88 { width: 88%; }
.sk i.tall { height: 16px; }
@keyframes sweep { from { background-position: 140% 0; } to { background-position: -40% 0; } }

/* ─── stats ──────────────────────────────────────────────────────── */

.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(88px, 1fr));
         gap: 8px; margin: 12px 0 4px; }
.stat { background: var(--panel); border: 1px solid var(--line);
        border-radius: var(--r); padding: 11px 12px; box-shadow: var(--shadow); }
.stat .n { font-size: 20px; font-weight: 660; font-variant-numeric: tabular-nums;
           letter-spacing: -.02em; }
.stat .l { font-size: 11px; color: var(--faint); margin-top: 1px; }
.stat.a .n { color: var(--solar); }
.stat.b .n { color: var(--life); }

/* progress through a phase */
.meter { height: 4px; border-radius: 99px; background: var(--line); overflow: hidden;
         margin-top: 8px; }
.meter i { display: block; height: 100%; background: var(--life); border-radius: 99px;
           transform-origin: left; animation: fill .9s var(--ease) both; }
@keyframes fill { from { transform: scaleX(0); } }

/* ─── the map ────────────────────────────────────────────────────── */

#mapcanvas { position: absolute; inset: 0; }
.maprap { position: relative; height: 100%; }

.maplibregl-ctrl-attrib { font-size: 10px !important; }
.maplibregl-ctrl-bottom-right,
.maplibregl-ctrl-bottom-left { margin-bottom: 8px; }

/* a pin */
.pin { display: grid; place-items: center; width: 34px; height: 34px;
       border-radius: 50% 50% 50% 6px; transform: rotate(-45deg);
       background: var(--panel); border: 2px solid var(--life);
       box-shadow: 0 3px 10px rgba(0,0,0,.55); cursor: pointer;
       animation: drop-in .45s var(--spring) both; animation-delay: var(--d, 0ms);
       transition: transform var(--dur) var(--spring); }
.pin:active { transform: rotate(-45deg) scale(.88); }
.pin > .ic { transform: rotate(45deg); width: 16px; height: 16px; color: var(--ink); }
.pin.hot { border-color: var(--now); }
.pin.warm { border-color: var(--solar); }
.pin .count { position: absolute; top: -5px; right: -5px; transform: rotate(45deg);
              min-width: 18px; height: 18px; border-radius: 99px; background: var(--now);
              color: #fff; font-size: 10.5px; font-weight: 700; display: grid;
              place-items: center; padding: 0 4px; border: 1.5px solid var(--bg); }
@keyframes drop-in { from { opacity: 0; transform: rotate(-45deg) translate(6px,-6px) scale(.4); } }

/* A hot pin breathes. One pin at a time, and only the ones that are urgent —
   an app where everything pulses is an app nobody looks at. */
.pin.hot::after {
  content: ""; position: absolute; inset: -2px; border-radius: inherit;
  border: 2px solid var(--now); animation: halo 2.4s var(--ease) infinite;
}
@keyframes halo {
  0% { opacity: .8; transform: scale(1); }
  70%, 100% { opacity: 0; transform: scale(1.75); }
}

/* What the map is doing. A blank dark rectangle is the same picture whether
   the tiles are still coming, the style failed, or the browser refused a 3D
   canvas — and only one of those is worth waiting for. */
.mapstate {
  position: absolute; left: 50%; top: 46%; z-index: 7;
  transform: translate(-50%, -50%);
  width: min(86%, 330px); text-align: center;
  background: rgba(26,23,18,.93);
  backdrop-filter: blur(18px) saturate(160%);
  -webkit-backdrop-filter: blur(18px) saturate(160%);
  border: 1px solid var(--line); border-radius: var(--r-lg);
  padding: 18px 20px; box-shadow: var(--shadow-up);
  font-size: 13px; color: var(--dim); line-height: 1.45;
  animation: rise var(--dur) var(--ease) both;
}
.mapstate b { display: block; color: var(--ink); font-size: 15px;
              font-weight: 620; letter-spacing: -.01em; margin-bottom: 5px; }
.mapstate .spin {
  width: 22px; height: 22px; border-radius: 50%; margin: 0 auto 12px;
  border: 2px solid var(--line-2); border-top-color: var(--solar);
  animation: spin .8s linear infinite;
}

/* the unpinned list, floated over the map */
.unpinned {
  position: absolute; left: 12px; right: 12px; bottom: 12px;
  background: rgba(26,23,18,.9); backdrop-filter: blur(18px) saturate(160%);
  -webkit-backdrop-filter: blur(18px) saturate(160%);
  border: 1px solid var(--line); border-radius: var(--r-lg);
  padding: 12px 14px; max-height: 46%; overflow-y: auto; z-index: 5;
  box-shadow: var(--shadow-up);
  animation: rise var(--dur-slow) var(--ease) both;
}
.unpinned h4 { margin: 0 0 3px; font-size: 13px; font-weight: 620; }
.unpinned p { margin: 0 0 9px; font-size: 12px; color: var(--faint); }
.chips { display: flex; gap: 6px; flex-wrap: wrap; }
.chip { border: 1px solid var(--line); background: var(--panel-2); color: var(--dim);
        border-radius: 99px; padding: 6px 12px 6px 9px; font-size: 12.5px;
        display: inline-flex; align-items: center; gap: 6px;
        transition: transform var(--dur-fast) var(--ease); }
.chip .ic { width: 15px; height: 15px; color: var(--faint); }
.chip[aria-pressed="true"] .ic { color: var(--life); }
.chip:active { transform: scale(.93); }
.chip[aria-pressed="true"] { border-color: var(--life); color: var(--life); }

.placing {
  position: absolute; left: 12px; right: 12px; top: 12px; z-index: 6;
  background: rgba(227,169,92,.94); color: #201703; border-radius: var(--r);
  padding: 11px 14px; font-size: 13.5px; font-weight: 560;
  display: flex; align-items: center; gap: 10px;
  box-shadow: var(--shadow); animation: rise var(--dur) var(--ease) both;
}
.placing .btn { background: rgba(0,0,0,.16); border-color: rgba(0,0,0,.2);
                color: #201703; margin-left: auto; }

/* the crosshair you drop a pin under */
.cross { position: absolute; left: 50%; top: 50%; z-index: 5;
         transform: translate(-50%,-50%); pointer-events: none; }
.cross::before, .cross::after { content: ""; position: absolute; background: var(--solar); }
.cross::before { left: -14px; width: 28px; height: 2px; top: -1px; }
.cross::after { top: -14px; height: 28px; width: 2px; left: -1px; }

/* ─── sheet ──────────────────────────────────────────────────────
   Drag-to-dismiss, because a sheet with a grab handle that cannot be grabbed
   is a screenshot of a native app rather than one. While the thumb is down
   the transition is off and the sheet tracks the finger exactly; on release
   it springs to open or closed. */

.sheet-bg { position: fixed; inset: 0; background: rgba(0,0,0,.55); z-index: 50;
            opacity: 0; transition: opacity var(--dur) var(--ease);
            backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px); }
.sheet-bg.on { opacity: 1; }
.sheet {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 51;
  background: var(--bg-2); border-top: 1px solid var(--line-2);
  border-radius: var(--r-xl) var(--r-xl) 0 0;
  padding: 8px 16px calc(var(--safe-b) + 20px);
  max-height: 88%; overflow-y: auto; overscroll-behavior: contain;
  box-shadow: var(--shadow-up);
  transform: translate3d(0, 100%, 0);
  transition: transform var(--dur-slow) var(--spring);
}
.sheet.on { transform: translate3d(0, var(--drag, 0px), 0); }
.sheet.dragging { transition: none; }
.sheet .grab { width: 38px; height: 5px; border-radius: 99px; background: var(--line-2);
               margin: 6px auto 12px; cursor: grab; }
.sheet .grab::before { content: ""; position: absolute; left: 0; right: 0; top: 0;
                       height: 44px; } /* the handle's real target */
.sheet h2 { margin: 0 0 4px; font-size: 20px; letter-spacing: -.018em; }

/* ─── toast ──────────────────────────────────────────────────────── */

.toast {
  position: fixed; left: 50%; z-index: 60;
  bottom: calc(var(--tab-h) + var(--safe-b) + 14px);
  max-width: min(92vw, 420px);
  padding: 10px 16px; border-radius: 99px;
  background: rgba(38,32,25,.94); border: 1px solid var(--line-2);
  backdrop-filter: blur(18px) saturate(160%);
  -webkit-backdrop-filter: blur(18px) saturate(160%);
  box-shadow: var(--shadow-up);
  font-size: 13.5px; text-align: center;
  transform: translate3d(-50%, 16px, 0) scale(.94); opacity: 0;
  transition: transform var(--dur) var(--spring), opacity var(--dur) var(--ease);
  pointer-events: none;
}
.toast.on { transform: translate3d(-50%, 0, 0) scale(1); opacity: 1; }
.toast.bad { color: #f7c9c2; border-color: rgba(215,109,93,.45); }
.toast.good { color: #d9ecca; border-color: rgba(134,185,111,.4); }

/* ─── the ember burst ────────────────────────────────────────────
   Doing a job pays embers. The count in the header changing by itself is a
   receipt nobody reads; embers flying up to it is the one moment this app
   is allowed to be pleased with somebody. transform/opacity only, removed
   from the DOM on animationend, and skipped entirely under reduced motion. */

.ember {
  position: fixed; z-index: 55; pointer-events: none;
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--solar); box-shadow: 0 0 9px 2px rgba(227,169,92,.6);
  animation: ember 1s var(--ease) both; animation-delay: var(--d, 0ms);
}
@keyframes ember {
  0% { opacity: 0; transform: translate3d(0,0,0) scale(.4); }
  12% { opacity: 1; transform: translate3d(0,0,0) scale(1); }
  100% { opacity: 0; transform: translate3d(var(--dx), var(--dy), 0) scale(.3); }
}

/* ─── forms ──────────────────────────────────────────────────────── */

label.f { display: block; font-size: 11px; color: var(--faint);
          text-transform: uppercase; letter-spacing: .08em; margin: 11px 0 4px; }
.f-in, textarea.f-in, select.f-in {
  width: 100%; font-size: 16px; /* 16px or iOS zooms the whole app on focus */
  background: var(--bg); color: var(--ink);
  border: 1px solid var(--line); border-radius: 10px; padding: 10px 11px;
  transition: border-color var(--dur-fast) var(--ease);
}
.f-in:focus { outline: none; border-color: var(--life); }
textarea.f-in { min-height: 76px; resize: vertical; }
.f2 { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }

/* ─── hands ──────────────────────────────────────────────────────── */

.hand { display: flex; align-items: center; gap: 11px; padding: 10px 0;
        border-bottom: 1px solid var(--line); }
.hand:last-child { border-bottom: 0; }
.hand .av { width: 34px; height: 34px; border-radius: 50%; display: grid;
            place-items: center; background: var(--panel-2); border: 1px solid var(--line);
            font-size: 14px; font-weight: 640; color: var(--dim); flex: none; }
.hand .who { flex: 1; min-width: 0; }
.hand .who b { display: block; font-size: 14.5px; font-weight: 580; }
.hand .who span { font-size: 12px; color: var(--faint); }
.hand select { font-size: 12.5px; padding: 5px 8px; border-radius: 8px;
               background: var(--panel-2); border: 1px solid var(--line); }

/* A claim link, shown once so it can be copied. Monospaced because it will
   be read aloud and retyped at least once. */
.mono-code {
  display: block; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px; letter-spacing: .12em; color: var(--solar);
}
.linkout {
  margin-top: 16px; padding: 14px 15px; border-radius: var(--r);
  border: 1px solid rgba(227,169,92,.3);
  background: linear-gradient(180deg, rgba(227,169,92,.10), rgba(227,169,92,.03));
}
.linkout .eyebrow-lite {
  font-size: 11px; text-transform: uppercase; letter-spacing: .1em;
  color: var(--solar); font-weight: 640; margin-bottom: 7px;
}
.linkout code {
  display: block; word-break: break-all;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12.5px; color: var(--ink); line-height: 1.5;
}

.feed { font-size: 13px; color: var(--dim); padding: 7px 0;
        border-bottom: 1px solid var(--line); }
.feed:last-child { border-bottom: 0; }
.feed b { color: var(--ink); font-weight: 560; }
.feed .t { color: var(--faint); font-size: 11.5px; }

@media (min-width: 720px) {
  .bar h1 { font-size: 34px; }
  .view.scroll { padding-left: 22px; padding-right: 22px; }
}

/* ─── reduced motion ─────────────────────────────────────────────
   Not optional. Everything above degrades to the same app with no travel:
   the states are all reachable, they just arrive instead of moving. */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
  .ember { display: none; }
  .pin.hot::after { display: none; }
}
