:root{
  /* Blush canvas, white cards, one rose accent. Everything that used to be a
     dark surface is now a white card floating on pink; everything that used to
     glow now casts a soft rose shadow instead. */
  --bg: #FDF1F5;
  --surface: #FFFFFF;
  --surface-2: #FBF2F6;
  --border: #F5E3EA;
  --red: #E93B63;
  --red-press: #D22C52;
  /* True red, kept apart from the rose accent. Only the SOS button uses it,
     and only while it is being held down. */
  --alarm: #D81E24;
  --red-dim: #FDE7EE;
  --red-glow: rgba(233,59,99,0.22);
  --green: #22A45D;
  --amber: #F0930E;
  --text: #1D1D1F;
  --text-dim: #6E6E73;
  --text-faint: #A09AA3;
  --radius: 18px;
  --radius-sm: 14px;
  /* The ground the phone frame sits on, and the frosted white used over a map.
     Both were literals; the dark theme needs them to be tokens. */
  --page: #F4F2F3;
  --glass: rgba(255,255,255,0.94);
  --glass-dim: rgba(255,255,255,0.86);
  --hairline: rgba(196,124,148,0.16);
  --shell-shadow: 0 0 0 1px var(--hairline), 0 40px 90px -30px rgba(160,96,120,0.35);

  /* Pastel chips on the quick-action tiles. Each pair is a wash plus the icon
     colour that sits on it. */
  --chip-violet: #EDE7FC;  --ic-violet: #7C5CE0;
  --chip-green:  #DFF5E6;  --ic-green:  #22A45D;
  --chip-blue:   #DCEBFE;  --ic-blue:   #2F7FEE;
  --chip-amber:  #FDEBD3;  --ic-amber:  #F0930E;
  --chip-rose:   #FDE0EA;  --ic-rose:   #E93B63;

  /* Card lift. Tinted rose so it belongs to the background it falls on. */
  --lift: 0 1px 2px rgba(196,124,148,0.06), 0 6px 18px -8px rgba(196,124,148,0.28);
  --lift-lg: 0 2px 4px rgba(196,124,148,0.07), 0 14px 34px -12px rgba(196,124,148,0.34);

  font-family: 'Geist', -apple-system, BlinkMacSystemFont, sans-serif;
}

*{ box-sizing:border-box; margin:0; padding:0; }
/* Components below set display:flex, which outranks the UA rule for [hidden].
   Without this, el.hidden = true does nothing. */
[hidden]{ display:none !important; }

html,body{
  background: var(--page);
  color: var(--text);
  height:100%;
  -webkit-font-smoothing: antialiased;
}

button{ font-family:inherit; cursor:pointer; }

.app-shell{
  max-width: 430px;
  margin: 0 auto;
  min-height: 100dvh;
  background: var(--bg);
  position: relative;
  overflow:hidden;
  box-shadow: var(--shell-shadow);
}

@media (min-width: 431px){
  body{ padding: 32px 0; background: var(--page); }
  .app-shell{ border-radius: 40px; min-height: 860px; height:860px; }
}

/* ---------- screens ---------- */
.screen{
  display:none;
  flex-direction: column;
  height: 100%;
  min-height: 100dvh;
  position:absolute;
  inset:0;
  background: var(--bg);
}
@media (min-width:431px){ .screen{ min-height: 860px; border-radius:40px; } }
.screen.active{ display:flex; }

.scroll-body{
  flex:1;
  overflow-y:auto;
  /* Clears the floating tab bar, which itself rides up by the nav-bar inset. */
  padding: 18px 20px calc(110px + env(safe-area-inset-bottom));
}
.scroll-body::-webkit-scrollbar{ display:none; }
/* The two homes have no top bar; their greeting is the first thing under the
   status bar. */
#screen-home > .scroll-body,
#screen-watch > .scroll-body{ padding-top: calc(18px + env(safe-area-inset-top)); }

/* ---------- topbar ---------- */
/* Three columns with equal outer tracks, so the title sits on the true centre
   of the screen whether the right slot holds a 38px spacer or "+ Add". */
.topbar{
  display:grid;
  grid-template-columns: 1fr auto 1fr;
  align-items:center;
  gap: 10px;
  /* targetSdk 36 draws the app under the status bar on Android 15+. Zero
     wherever the inset is already handled, so this never doubles up. */
  padding: calc(20px + env(safe-area-inset-top)) 16px 14px;
  border-bottom: 1px solid var(--border);
}
.topbar > :last-child{ justify-self:end; }
.topbar h1{
  font-size: 17px;
  font-weight:700;
  letter-spacing: -0.02em;
  text-align:center;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.topbar .link-btn{ min-height:38px; padding: 0 4px; font-size:14px; }
.topbar-alert h1.alert-title{ color: var(--text); transition: color .25s ease; }
.sos-live h1.alert-title, .sos-sending h1.alert-title{ color: var(--red); }
.icon-btn{
  width:38px; height:38px;
  display:flex; align-items:center; justify-content:center;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  color: var(--text);
  transition: background .16s ease, transform .1s ease;
}
@media (hover: hover){ .icon-btn:hover{ background: var(--surface-2); } }
.icon-btn:active{ transform: scale(0.94); }
.icon-btn svg{ width:18px; height:18px; }
.icon-btn:empty{ background:transparent; border:none; }

/* ---------- welcome ---------- */
#screen-welcome{ justify-content:flex-start; }
.welcome-wrap{
  /* The welcome and auth screens are taller than a short phone, so this pane
     scrolls on its own -- .app-shell clips, and without this the button at the
     bottom is unreachable. min-height:0 lets a flex child actually shrink. */
  flex:1;
  min-height:0;
  overflow-y:auto;
  padding: calc(28px + env(safe-area-inset-top)) 24px calc(24px + env(safe-area-inset-bottom));
  display:flex;
  flex-direction:column;
  align-items:stretch;
  text-align:left;
  background: radial-gradient(110% 46% at 0% 0%, var(--red-dim), transparent 72%), var(--bg);
}
.brand-mark{
  width:72px; height:72px;
  border-radius: 22px;
  background: var(--red);
  display:flex; align-items:center; justify-content:center;
  box-shadow: 0 10px 26px -8px var(--red-glow);
  margin-bottom:18px;
}
.brand-mark svg{ width:36px; height:36px; color:#fff; }
/* The logo carries its own plate, so it drops the red one underneath. */
.brand-mark.logo{ background:none; }
.brand-mark.logo img{ width:100%; height:100%; display:block; }
.welcome-head{ margin-top:44px; }
.brand-name{ font-size:34px; line-height:1.05; font-weight:700; letter-spacing:-0.035em; text-wrap:balance; }
.brand-sub{ color: var(--text-dim); font-size:15px; line-height:1.5; margin-top:12px; max-width:34ch; }

/* One card with hairlines between the rows: seven separately shadowed cards
   read as seven buttons, and none of them does anything. */
.feature-list{
  list-style:none;
  margin: 28px 0 0;
  background: var(--surface);
  border-radius: var(--radius);
  box-shadow: var(--lift);
  padding: 4px 16px;
}
.feature-list li{
  display:flex; align-items:center; gap:12px;
  font-size:14px; font-weight:500; color: var(--text);
  padding: 11px 0;
  border-bottom: 1px solid var(--border);
}
.feature-list li:last-child{ border-bottom:0; }
.feature-list svg{ width:17px; height:17px; color: var(--red); flex-shrink:0; }
.welcome-foot{ margin-top:auto; padding-top:28px; display:flex; flex-direction:column; gap:14px; }

.btn-primary{
  background: var(--red);
  color:#fff;
  border:none;
  border-radius: 16px;
  padding: 16px 24px;
  font-weight:700;
  font-size:15px;
  line-height:1.4;
  box-shadow: 0 10px 22px -8px var(--red-glow);
  transition: background .16s ease, transform .1s ease;
}
@media (hover: hover){ .btn-primary:hover{ background: var(--red-press); } }
.btn-primary:active{ transform: scale(0.985); background: var(--red-press); }
.btn-primary span{ font-weight:600; font-size:13px; opacity:0.9; }
.btn-block{ width:100%; }

/* ---------- home ---------- */
/* The greeting replaces the old bare wordmark topbar. The name is written in
   by app.js once the profile loads. */
.greet-head{
  display:flex; align-items:flex-start; justify-content:space-between; gap:14px;
  padding: 4px 0 20px;
}
.greet-head > div{ min-width:0; }
.greet-hi{ font-size:15px; color: var(--text-dim); font-weight:500; }
.greet-head h1{
  font-size:27px; font-weight:700; letter-spacing:-0.035em;
  margin-top:2px; text-wrap:balance;
}
.greet-sub{ font-size:14px; line-height:1.45; color: var(--text-dim); margin-top:6px; max-width:30ch; }
.greet-actions{ display:flex; gap:8px; flex-shrink:0; }

/* Two readings side by side: where you are, and whether anything is wrong.
   One white card, a hairline between the halves. */
.status-card{
  display:grid; grid-template-columns:1fr 1fr;
  background: var(--surface);
  border-radius: var(--radius);
  box-shadow: var(--lift);
  overflow:hidden;
  margin-bottom:26px;
}
.status-cell{
  display:flex; align-items:center; gap:11px;
  padding: 16px 14px;
  text-align:left; color: var(--text);
  background:none; border:none; min-width:0;
  border-left:1px solid var(--border);
}
.status-cell:first-child{ border-left:none; }
.status-cell > svg:first-child{ width:22px; height:22px; color: var(--red); flex-shrink:0; }
.status-cell span{ display:flex; flex-direction:column; gap:2px; min-width:0; }
.status-cell em{
  font-style:normal; font-size:12px; color: var(--text-dim); line-height:1.3;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.status-cell strong{
  font-size:14.5px; font-weight:700; letter-spacing:-0.02em; line-height:1.25;
  overflow:hidden; text-overflow:ellipsis;
  /* A real address is longer than a coordinate pair was. Two lines, then cut
     -- enough for the house number and the neighbourhood, which is the part
     worth reading; the rest is on the element's title. */
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical;
}
.status-cell.status-pill{ transition: background .2s ease; }
.status-cell.status-pill:active{ background: var(--surface-2); }
.chev{ width:16px; height:16px; color: var(--text-faint); flex-shrink:0; }

/* An alarm in progress. The safety half turns over completely -- icon, label
   and value -- so "fine" and "not fine" are never on screen together. */
.status-pill.alarmed{ background: var(--red-dim); }
.status-pill.alarmed svg:first-child{ color: var(--red); }
.status-pill.alarmed em{ color: var(--red); font-weight:600; }
.status-pill.alarmed strong::after{
  content:""; display:inline-block; width:6px; height:6px; margin-left:7px;
  border-radius:50%; background: var(--red); vertical-align:middle;
  animation: livePulse 1.6s ease-in-out infinite;
}
@keyframes livePulse{ 0%,100%{ opacity:1; box-shadow:0 0 0 0 var(--red-glow); }
  50%{ opacity:.55; box-shadow:0 0 0 5px rgba(233,59,99,0); } }

/* ---------- the owner's Home ----------
   One account (isOwner in app.js) gets the app's numbers and nothing else:
   no greeting, status card, dial or tiles. Those screens stay reachable from
   the menu and the tab bar. Rose stays the only accent. */
#screen-home:not(.owner) .greet-owner,
#screen-home:not(.owner) .owner-home,
#screen-home.owner .greet-plain,
#screen-home.owner .status-card,
#screen-home.owner .checkin-bar,
#screen-home.owner .sos-wrap,
#screen-home.owner .actions-grid{ display:none; }

.greet-owner h1{ display:flex; flex-wrap:wrap; align-items:center; gap:4px 10px; }
.admin-badge{
  font-size:11px; font-weight:700; letter-spacing:.08em; text-transform:uppercase;
  color: var(--red); background: var(--red-dim);
  padding: 4px 9px; border-radius: 999px; line-height:1.2;
}

.owner-home{ margin-bottom:26px; }
.ov-head{ display:flex; align-items:center; gap:10px; margin-bottom:12px; }
.ov-head em{ font-style:normal; font-family:'Geist Mono',monospace; font-size:11.5px; color: var(--text-faint); }
.ov-live{
  display:flex; align-items:center; gap:7px;
  font-size:12px; font-weight:650; letter-spacing:.06em; text-transform:uppercase; color: var(--text-dim);
}
.ov-live b{ width:7px; height:7px; border-radius:50%; background: var(--green); animation: ovBreathe 2.4s ease-in-out infinite; }
.ov-refresh{ margin-left:auto; width:34px; height:34px; }
.ov-refresh svg{ width:16px; height:16px; }
.ov-refresh.busy svg{ animation: ovSpin .9s linear infinite; }

.ov-grid{ display:grid; grid-template-columns:1fr 1fr; gap:10px; }
.ov-tile{
  display:flex; flex-direction:column; gap:4px; min-width:0;
  padding:15px 15px 14px;
  background: var(--surface); border:1px solid var(--border);
  border-radius: var(--radius);
}
.ov-tile > em, .ov-tile > div > em{ font-style:normal; font-size:12px; font-weight:550; color: var(--text-dim); }
.ov-tile strong{
  font-size:26px; font-weight:700; letter-spacing:-0.035em; line-height:1.1;
  font-variant-numeric: tabular-nums;
}
.ov-tile strong small{ font-size:15px; font-weight:600; color: var(--text-faint); letter-spacing:-0.01em; }
.ov-note{ font-size:12px; color: var(--text-dim); line-height:1.4; }
.ov-note.warn{ color: var(--red); font-weight:600; }
.ov-wide{ grid-column: 1 / -1; }

/* The headline tile: the count on the left, its fortnight on the right. */
.ov-hero{
  grid-column: 1 / -1;
  display:grid; grid-template-columns: 1fr minmax(96px, 42%); align-items:end; gap:4px 14px;
  padding:18px 18px 0;
}
.ov-hero > div{ display:flex; flex-direction:column; gap:4px; min-width:0; }
.ov-big{ font-size:40px !important; letter-spacing:-0.045em !important; }
.ov-spark{ width:100%; height:56px; overflow:visible; margin-bottom:6px; animation: ovDraw 1.1s cubic-bezier(.16,1,.3,1) both .15s; }
.ov-spark polyline{
  fill:none; stroke: var(--red); stroke-width:2; stroke-linejoin:round; stroke-linecap:round;
  vector-effect: non-scaling-stroke;
}
.ov-spark polygon{ fill: var(--red); opacity:.1; stroke:none; }
.ov-foot{
  grid-column: 1 / -1;
  display:flex; gap:16px; margin: 12px -18px 0; padding: 11px 18px 13px;
  border-top:1px solid var(--border);
  font-size:12px; color: var(--text-dim);
}
.ov-foot b{ color: var(--text); font-weight:650; font-variant-numeric: tabular-nums; margin-right:2px; }

.ov-tile.hot{ background: var(--red-dim); border-color: transparent; }
.ov-tile.hot strong, .ov-tile.hot > em{ color: var(--red); }
.ov-tile.ask .ov-note{ color: var(--text); font-weight:600; }

.ov-line{ display:flex; align-items:baseline; justify-content:space-between; gap:10px; }
.ov-bar{ display:block; height:6px; margin:6px 0 4px; border-radius:99px; background: var(--surface-2); overflow:hidden; }
.ov-bar i{
  display:block; height:100%; border-radius:inherit; background: var(--red);
  transform-origin:left; animation: ovGrow .9s cubic-bezier(.16,1,.3,1) both .2s;
}

/* Six small counts on hairlines rather than six more boxes. */
.ov-mini{ display:grid; grid-template-columns: repeat(3, 1fr); padding:0; gap:0; overflow:hidden; }
.ov-mini div{ padding:12px 14px; border-left:1px solid var(--border); border-top:1px solid var(--border); min-width:0; }
.ov-mini div:nth-child(3n+1){ border-left:none; }
.ov-mini div:nth-child(-n+3){ border-top:none; }
.ov-mini dt{ font-size:11.5px; color: var(--text-dim); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.ov-mini dd{ margin:2px 0 0; font-size:17px; font-weight:700; letter-spacing:-0.02em; font-variant-numeric: tabular-nums; }

.ov-cta{
  display:flex; align-items:center; gap:12px; margin-top:12px;
  padding:16px 18px; border-radius: var(--radius);
  background: var(--red); color:#fff; text-decoration:none;
  box-shadow: 0 12px 26px -14px var(--red-glow);
  transition: transform .12s ease, background .16s ease;
}
.ov-cta span{ display:flex; flex-direction:column; flex:1; min-width:0; }
.ov-cta strong{ font-size:15.5px; font-weight:700; letter-spacing:-0.015em; }
.ov-cta em{ font-style:normal; font-size:12.5px; opacity:.85; margin-top:1px; }
.ov-cta svg{ width:20px; height:20px; flex-shrink:0; }
.ov-cta:active{ transform: scale(0.98); background: var(--red-press); }
@media (hover: hover){ .ov-cta:hover{ background: var(--red-press); } }

.ov-error{
  display:flex; align-items:center; gap:12px; padding:14px 16px;
  background: var(--surface); border:1px solid var(--border); border-radius: var(--radius);
}
.ov-error > svg{ width:20px; height:20px; color: var(--red); flex-shrink:0; }
.ov-error span{ display:flex; flex-direction:column; flex:1; min-width:0; }
.ov-error strong{ font-size:14px; font-weight:650; }
.ov-error em{ font-style:normal; font-size:12px; color: var(--text-dim); overflow:hidden; text-overflow:ellipsis; }

/* Skeletons sized like what replaces them. */
.sk{
  display:inline-block; width:2.4em; height:.8em; border-radius:6px; vertical-align:middle;
  background: linear-gradient(90deg, var(--surface-2) 25%, var(--border) 50%, var(--surface-2) 75%);
  background-size:200% 100%; animation: ovShimmer 1.3s linear infinite;
}
.sk-wide{ width:11em; height:.75em; }
.sk-spark{ width:100%; height:44px; margin-bottom:12px; border-radius:10px; }

/* Tiles arrive in sequence when the numbers do -- not for the skeleton. */
.ov-grid.in > *{ animation: ovIn .55s cubic-bezier(.16,1,.3,1) both; animation-delay: calc(var(--i) * 55ms); }

@keyframes ovIn{ from{ opacity:0; transform: translateY(8px); } }
@keyframes ovGrow{ from{ transform: scaleX(0); } }
@keyframes ovDraw{ from{ clip-path: inset(0 100% 0 0); } to{ clip-path: inset(0 -4px 0 0); } }
@keyframes ovShimmer{ to{ background-position:-200% 0; } }
@keyframes ovSpin{ to{ transform: rotate(360deg); } }
@keyframes ovBreathe{ 0%,100%{ opacity:1; transform: scale(1); } 50%{ opacity:.45; transform: scale(.8); } }
@media (prefers-reduced-motion: reduce){
  .ov-grid.in > *, .ov-bar i, .ov-spark, .ov-live b, .sk, .ov-refresh.busy svg{ animation:none; }
}

/* ---------- the button ---------- */
.sos-wrap{
  display:flex; flex-direction:column; align-items:center;
  margin: 10px 0 30px;
}
/* The dial is its own box so the rings centre on the button, not on the
   button plus the caption underneath it. */
.sos-dial{
  position:relative;
  display:grid; place-items:center;
  width:296px; height:296px;
}
/* Two concentric washes of the accent, sitting behind the button like ripples.
   Pure CSS rings -- no gradient, no blur, so they stay crisp at any density. */
.sos-dial::before,
.sos-dial::after{
  content:""; position:absolute; top:50%; left:50%; translate:-50% -50%;
  border-radius:50%; pointer-events:none;
}
.sos-dial::before{ width:296px; height:296px; background: rgba(233,59,99,0.07); }
.sos-dial::after{ width:246px; height:246px; background: rgba(233,59,99,0.11); }
/* The halo follows the face, otherwise a red button sits in a pink glow for
   the whole hold. Browsers without :has() just keep the rose rings, which is
   a cosmetic loss and nothing more. */
.sos-dial::before,
.sos-dial::after{ transition: background .14s ease; }
.sos-dial:has(.holding)::before{ background: rgba(216,30,36,0.09); transition: background 1500ms linear; }
.sos-dial:has(.holding)::after{ background: rgba(216,30,36,0.14); transition: background 1500ms linear; }

.sos-btn{
  position:relative; z-index:1;
  width:196px; height:196px; border-radius:50%;
  background: var(--red);
  border:none; color:#fff;
  display:flex; flex-direction:column; align-items:center; justify-content:center; gap:4px;
  box-shadow: 0 14px 30px -10px rgba(214,44,82,0.55);
  touch-action:none; -webkit-tap-highlight-color:transparent;
  transition: transform .12s ease, box-shadow .22s ease, background .14s ease;
}
/* A press that is not yet a hold: the squeeze only, no colour change. */
.sos-btn:active{
  transform: scale(0.965);
  background: var(--red-press);
  box-shadow: 0 8px 20px -10px rgba(214,44,82,0.6);
}

/* Held. The face ramps from the resting rose to true red across the whole
   hold, so the colour is the countdown -- it arrives at red at the same
   moment the alarm arms. Listed after :active so it wins the background.
   The 1500ms is HOLD_MS in app.js; the two have to stay in step. */
.sos-btn.holding{
  transform: scale(0.965);
  background: var(--alarm);
  box-shadow: 0 10px 30px -8px rgba(216,30,36,0.7);
  transition:
    background 1500ms linear,
    box-shadow 1500ms linear,
    transform .12s ease;
}
/* Letting go drops .holding and falls back to the base rule above, whose
   shorter background transition snaps the rose back -- an aborted hold should
   read as cancelled immediately, not fade out over another second and a half. */
.sos-btn.holding .sos-ring-track{ stroke: rgba(255,255,255,0.3); }
.sos-btn:focus-visible{ outline:3px solid var(--text); outline-offset:8px; }

/* The hold indicator, drawn just inside the button's edge so it reads on the
   fill rather than fighting the rings behind it. It closes as the alarm arms. */
.sos-ring{ position:absolute; inset:6px; width:calc(100% - 12px); height:calc(100% - 12px); transform: rotate(-90deg); }
.sos-ring circle{ fill:none; stroke-width:4; }
.sos-ring-track{ stroke: rgba(255,255,255,0.22); }
.sos-ring-fill{
  stroke:#fff; stroke-linecap:round;
  stroke-dasharray:352; stroke-dashoffset:352;
}
.sos-label{
  font-family:'Geist',sans-serif; font-size:52px; font-weight:700;
  letter-spacing:-0.01em; line-height:1;
}
.sos-sub{
  font-size:12.5px; font-weight:600; letter-spacing:0.09em; opacity:0.92;
}
/* The icon under the label. Hidden mid-hold so the countdown owns the face. */
.sos-btn .sos-ic{ width:26px; height:26px; margin-top:2px; opacity:0.95; }
.sos-btn.holding .sos-ic{ opacity:0.35; }

.sos-hint{ text-align:center; margin-top:22px; }
.sos-hint b{ display:block; color: var(--red); font-size:14px; font-weight:600; }
.sos-hint span{
  display:block; color: var(--text-dim); font-size:13.5px;
  line-height:1.5; margin-top:4px; max-width:32ch;
}

/* ---------- sections ---------- */
.section-row{ display:flex; align-items:center; justify-content:space-between; margin-bottom:14px; }
.section-row h3{ font-size:15px; font-weight:600; letter-spacing:-0.015em; }
.link-btn{ background:none; border:none; color: var(--red); font-size:13px; font-weight:600; }
.link-btn:active{ opacity:.7; }
/* Red is the alarm's colour. A "see the rest" link sitting next to the SOS
   button does not get to borrow it. The auth screen's link still does -- there
   it is the only way forward. */
.section-row .link-btn{ color: var(--text-dim); font-weight:500; }
.section-row .link-btn:active{ color: var(--text); opacity:1; }

/* Two columns of white cards, each led by a pastel chip. Three squeezed every
   caption onto two lines with the chevron wedged beside it, and tiles in one
   row came out different heights. The chip colour is the only thing that
   varies between them -- a wayfinding cue, not a second accent. */
.actions-grid{
  display:grid; grid-template-columns: repeat(2,1fr); gap:12px; margin-bottom:10px;
}
.action-tile{
  position:relative;
  background: var(--surface);
  border:none;
  border-radius: var(--radius);
  box-shadow: var(--lift);
  padding: 16px 16px 15px;
  display:flex; flex-direction:column; align-items:flex-start; gap:3px;
  color: var(--text); text-align:left;
  transition: box-shadow .18s ease, transform .1s ease;
}
@media (hover: hover){ .action-tile:hover{ box-shadow: var(--lift-lg); } }
.action-tile:active{ transform: scale(0.975); }

/* The chip is a <b> rather than a <span> on purpose: tools.js reads a tile's
   first <strong> or <span> to work out which tool to run, and a wrapper span
   here would hand it an empty string. */
.action-tile .tile-chip{
  width:40px; height:40px; border-radius:12px;
  display:grid; place-items:center;
  background: var(--chip-violet);
  margin-bottom:12px;
}
.action-tile .tile-chip svg{ width:20px; height:20px; color: var(--ic-violet); }
.action-tile[data-chip="green"] .tile-chip{ background: var(--chip-green); }
.action-tile[data-chip="green"] .tile-chip svg{ color: var(--ic-green); }
.action-tile[data-chip="blue"] .tile-chip{ background: var(--chip-blue); }
.action-tile[data-chip="blue"] .tile-chip svg{ color: var(--ic-blue); }
.action-tile[data-chip="amber"] .tile-chip{ background: var(--chip-amber); }
.action-tile[data-chip="amber"] .tile-chip svg{ color: var(--ic-amber); }
.action-tile[data-chip="rose"] .tile-chip{ background: var(--chip-rose); }
.action-tile[data-chip="rose"] .tile-chip svg{ color: var(--ic-rose); }

.action-tile strong{ font-size:14.5px; font-weight:700; letter-spacing:-0.02em; }
.action-tile .tile-foot{ display:block; width:100%; }
.action-tile em{
  font-style:normal; font-size:12.5px; color: var(--text-dim);
  line-height:1.35;
}
/* The chevron leaves the caption line for the top corner, level with the
   chip, so the caption gets the full width of the tile. */
.action-tile .tile-foot svg{
  position:absolute; top:18px; right:14px;
  width:16px; height:16px; color: var(--text-faint);
  transition: transform .3s cubic-bezier(.16,1,.3,1);
}
@media (hover: hover){ .action-tile:hover .tile-foot svg{ transform: translateX(2px); } }

/* ---------- tabbar ---------- */
/* A floating white bar rather than a docked one, so the blush canvas runs
   behind it to the bottom edge of the phone. */
.tabbar{
  position:absolute; left:14px; right:14px;
  bottom: calc(12px + env(safe-area-inset-bottom));
  height:66px;
  background: var(--surface);
  border-radius: 22px;
  box-shadow: var(--lift-lg);
  display:flex;
  align-items:center;
  justify-content:space-around;
  padding: 0 6px;
}
.tab-item{
  position:relative;
  display:flex; flex-direction:column; align-items:center; gap:3px;
  background:none; border:none; color: var(--text-faint);
  font-size:11px; font-weight:500;
  padding: 8px 16px;
  border-radius: 15px;
  transition: color .16s ease, background .16s ease;
}
.tab-item svg{ width:21px; height:21px; }
/* The open tab gets a filled chip, not just a colour, so the current place
   survives a screenshot in greyscale. */
.tab-item.active{ color: var(--red); background: var(--red-dim); font-weight:600; }

/* ---------- SOS activated ---------- */
.center-body{ align-items:center; text-align:center; }
.help-title{ font-size:20px; font-weight:800; margin-top:6px; }
.help-sub{ color: var(--text-dim); font-size:13px; margin-top:8px; line-height:1.5; }
.help-sub.small{ font-size:12px; margin-top:22px; margin-bottom:14px; }
.help-sub.pad-top{ margin-top: 20px; }

/* ---------- SOS screen ---------- */
/* Three states, and the screen has to say which one it is in from across a
   room: sending, live, not sent. The badge, the ring and the escalation row
   all read the class on the screen rather than each being set by hand. */
.sos-head{
  width:100%;
  display:flex; flex-direction:column; align-items:flex-start;
  text-align:left; gap:7px;
}
.sos-head .help-title, .sos-head .help-sub{ margin-top:0; }
.sos-badge{
  display:inline-flex; align-items:center; gap:7px;
  font-size:10px; font-weight:700; letter-spacing:0.09em; text-transform:uppercase;
  padding:5px 10px; border-radius:999px;
  background: var(--red-dim); color: var(--red);
}
.sos-badge::before{
  content:""; width:6px; height:6px; border-radius:50%; background: currentColor;
}
/* A live alarm is the only state that moves. */
.sos-live .sos-badge::before{ animation: sos-pulse 1.4s ease-in-out infinite; }
@keyframes sos-pulse{ 50%{ opacity:.2; transform:scale(.65); } }
.sos-failed .sos-badge, .sos-idle .sos-badge{ background: var(--surface-2); color: var(--text-dim); }

.timer-ring{
  width:150px; height:150px;
  border-radius:50%;
  border: 3px solid var(--red);
  background: var(--surface);
  box-shadow: 0 12px 30px -14px var(--red-glow);
  color: var(--red);
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  margin: 26px auto 0;
  font-size:30px; font-weight:700;
  font-variant-numeric: tabular-nums;
  transition: border-color .25s ease, color .25s ease, box-shadow .25s ease;
}
.timer-ring em{
  font-style:normal; font-size:9.5px; font-weight:700;
  letter-spacing:0.1em; text-transform:uppercase;
  color: var(--text-faint); margin-top:3px;
}
/* Nothing is counting when nothing was sent. A red ring ticking upward there
   reads as an alarm in progress, which is the opposite of what happened. */
.sos-failed .timer-ring, .sos-idle .timer-ring{
  border-color: var(--border); color: var(--text-faint); box-shadow:none;
}
.sos-sending .timer-ring{ border-style:dashed; }

/* A section label, not a sentence -- it stops competing with the title. */
.sos-caption{
  width:100%; text-align:left;
  font-size:10.5px; font-weight:700; letter-spacing:0.08em; text-transform:uppercase;
  color: var(--text-faint);
  margin: 26px 0 11px;
}

/* ---------- recording status ---------- */
/* Replaces a toast that slid in over the buttons and was gone before it was
   read. The ring drains over the three seconds and hands its place to a
   pulsing dot: the only moving thing on the card, in the same spot. */
.rec-card{
  width:100%; display:flex; align-items:center; gap:14px;
  margin-top:18px; padding:12px 16px 12px 12px;
  background: var(--surface); border:1px solid var(--border);
  border-radius: var(--radius-sm); box-shadow: var(--lift);
  text-align:left;
}
.rec-card[hidden]{ display:none; }
.rec-mark{
  position:relative; flex-shrink:0;
  width:44px; height:44px; display:grid; place-items:center;
  color: var(--red);
}
.rec-mark svg{ position:absolute; inset:0; transform: rotate(-90deg); }
.rec-mark circle{ fill:none; stroke-width:3; }
.rec-track{ stroke: var(--red-dim); }
/* 119.4 is the circumference of r=19. */
.rec-drain{ stroke: var(--red); stroke-linecap:round; stroke-dasharray:119.4; stroke-dashoffset:0; }
.rec-countdown .rec-drain{ animation: rec-drain 3s linear forwards; }
@keyframes rec-drain{ to{ stroke-dashoffset:119.4; } }
.rec-mark b{ font-size:18px; font-weight:700; font-variant-numeric: tabular-nums; }
.rec-text{ display:flex; flex-direction:column; gap:2px; min-width:0; }
.rec-text strong{ font-size:14.5px; font-weight:650; color: var(--text); }
.rec-text em{ font-style:normal; font-size:12.5px; color: var(--text-dim); line-height:1.4; }

.rec-live .rec-mark svg, .rec-live .rec-mark b,
.rec-off .rec-mark svg, .rec-off .rec-mark b{ display:none; }
/* The dot stays solid; only its halo breathes. A dot that fades out reads as
   "stopped" at the bottom of every pulse. */
.rec-live .rec-mark::before{
  content:""; width:14px; height:14px; border-radius:50%;
  background: var(--red); box-shadow: 0 0 0 4px var(--red-dim);
  animation: rec-breathe 1.6s ease-in-out infinite;
}
@keyframes rec-breathe{ 50%{ box-shadow: 0 0 0 10px var(--red-dim); } }
/* Not recording is information, not an alarm: quiet, and still said. */
.rec-off{ background: var(--surface-2); box-shadow:none; }
.rec-off .rec-mark::before{
  content:""; width:12px; height:12px; border-radius:50%;
  border:2px solid var(--text-faint);
}
.rec-off .rec-text strong{ color: var(--text-dim); }

.row-call{ margin-bottom:26px; }
/* On the failure path this is the only thing on screen that still works, so
   it stops being a quiet row and becomes the action. */
.sos-failed .row-call{ border-color: var(--red); background: var(--red-dim); }
.sos-failed .row-call em{ color: var(--red); }

.sos-actions{ width:100%; display:flex; flex-direction:column; gap:10px; }

.stack-list{ width:100%; display:flex; flex-direction:column; gap:12px; margin: 4px 0 26px; }
.row-card{
  width:100%;
  display:flex; align-items:center; gap:12px;
  background: var(--surface);
  border:1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 14px 14px;
  color: var(--text);
  text-align:left;
}
.row-card:is(a){ text-decoration:none; }
.row-card svg:first-child{ width:18px; height:18px; color: var(--red); flex-shrink:0; }
.row-card span{ display:flex; flex-direction:column; flex:1; }
.row-card strong{ font-size:14.5px; font-weight:650; }
.row-card em{ font-style:normal; font-size:12.5px; color: var(--text-dim); margin-top:2px; line-height:1.4; }

.btn-danger{
  background: var(--red);
  color:#fff;
  border:none;
  border-radius: 14px;
  padding:15px;
  font-weight:700;
  font-size:15px;
  letter-spacing:-0.01em;
  box-shadow: 0 10px 22px -10px var(--red-glow);
  transition: background .16s ease, transform .1s ease;
}
@media (hover: hover){ .btn-danger:hover{ background: var(--red-press); } }
.btn-danger:active{ transform: scale(0.985); background: var(--red-press); }

.avatar-row{ display:flex; margin: 4px 0 28px; }
/* Rose wash rather than --surface-2: on a white card that was a blank circle
   with a grey letter floating in it. */
.avatar{
  width:38px; height:38px; border-radius:50%; flex-shrink:0;
  background: var(--red-dim);
  border: 2px solid var(--bg);
  display:flex; align-items:center; justify-content:center;
  font-size:14px; font-weight:700; color: var(--red);
  margin-left:-10px;
}
.avatar:first-child{ margin-left:0; }
.avatar.more{ background: var(--surface-2); color: var(--text-dim); }
.plain-list .avatar, .responder-row .avatar, .profile-card .avatar{ border-color: transparent; }
.avatar.big{ width:56px; height:56px; font-size:20px; margin-left:0; }
.avatar.police{ background: var(--red-dim); color: var(--red); }
.avatar.police svg{ width:16px; height:16px; }
.muted{ color: var(--text-faint); font-size:12px; }

/* ---------- responders ---------- */
.list-caption{ font-size:13px; color: var(--text-dim); margin-bottom:14px; }
.responder-list{ display:flex; flex-direction:column; gap:12px; margin-bottom:22px; }
.responder-row{
  display:flex; align-items:center; gap:12px;
  background: var(--surface);
  border:1px solid var(--border);
  border-radius: var(--radius-sm);
  padding:12px 14px;
}
.responder-row .avatar{ margin-left:0; }
.responder-row .info{ flex:1; display:flex; flex-direction:column; }
.responder-row .info strong{ font-size:14.5px; font-weight:650; }
.responder-row .info em{ font-style:normal; font-size:12px; color: var(--text-faint); display:flex; align-items:center; gap:4px; margin-top:2px; }
.responder-row .info em svg{ width:11px; height:11px; color: var(--green); }
.responder-row .meta{ text-align:right; }
.responder-row .meta strong{ display:block; font-size:14px; font-variant-numeric: tabular-nums; }
.responder-row .meta span{ font-size:12px; color: var(--text-faint); }

/* ---------- pill tabs ---------- */
.pill-tabs{ display:flex; gap:8px; margin-bottom:20px; overflow-x:auto; }
.pill{
  background: var(--surface);
  border:1px solid var(--border);
  color: var(--text-dim);
  padding: 9px 16px;
  border-radius: 20px;
  font-size:12.5px; font-weight:600;
  white-space:nowrap;
}
.pill.active{ background: var(--red); color:#fff; border-color: var(--red); }

/* ---------- toggle list ---------- */
.toggle-list{ display:flex; flex-direction:column; gap:12px; }
.toggle-row{
  display:flex; align-items:center; gap:12px;
  background: var(--surface);
  border:1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 14px;
}
.toggle-row svg{ width:18px; height:18px; color: var(--red); flex-shrink:0; }
.toggle-row > span{ display:flex; flex-direction:column; flex:1; min-width:0; }
.toggle-row strong{ font-size:14px; }
.toggle-row em{ font-style:normal; font-size:12px; line-height:1.4; color: var(--text-dim); margin-top:2px; }

.switch{ position:relative; width:42px; height:24px; flex-shrink:0; cursor:pointer; }
.switch input{ position:absolute; opacity:0; width:0; height:0; }
/* The off track was --surface-2 on a white card: an outline nobody could see,
   so "off" looked like "missing". A grey wash of the faint text colour reads
   as a control in both themes; the fallback covers WebViews before
   color-mix (Chrome 111). */
.switch i{
  position:absolute; inset:0;
  background: var(--border);
  background: color-mix(in srgb, var(--text-faint) 34%, var(--surface));
  border-radius:20px;
  transition: background .25s cubic-bezier(.16,1,.3,1);
}
.switch i::before{
  content:""; position:absolute; width:20px; height:20px; left:2px; top:2px;
  background:#fff; border-radius:50%;
  box-shadow: 0 1px 3px rgba(60,30,45,0.28);
  transition: transform .3s cubic-bezier(.16,1,.3,1);
}
.switch input:checked + i{ background: var(--red); }
.switch input:checked + i::before{ transform: translateX(18px); }
.switch input:focus-visible + i{ outline:2px solid var(--red); outline-offset:2px; }
.switch:active i::before{ width:23px; }
.switch:active input:checked + i::before{ transform: translateX(15px); }

/* An explanatory panel: icon in a rose chip, text beside it. It had no rules
   at all, so the Shake screen showed body text at the browser default. */
.notice{
  display:flex; align-items:flex-start; gap:12px;
  margin-top:14px; padding:14px 16px;
  background: var(--surface); border:1px solid var(--border);
  border-radius: var(--radius-sm);
}
.notice svg{
  width:32px; height:32px; padding:7px; flex-shrink:0;
  border-radius:10px; background: var(--red-dim); color: var(--red);
}
.notice p{ font-size:13px; line-height:1.55; color: var(--text-dim); }

/* ---------- records ---------- */
.group-label{ font-size:12px; font-weight:700; color: var(--text-faint); text-transform:uppercase; letter-spacing:0.05em; margin: 18px 0 12px; }
.group-label:first-of-type{ margin-top:0; }
.record-item{
  display:flex; align-items:center; gap:12px;
  background: var(--surface);
  border:1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 12px 12px 12px 14px;
  margin-bottom:10px;
}
.thumb{
  width:42px; height:42px; border-radius:10px;
  display:flex; align-items:center; justify-content:center;
  flex-shrink:0;
}
.thumb svg{ width:18px; height:18px; }
.video-thumb, .photo-thumb{ background: var(--chip-blue); }
.video-thumb svg, .photo-thumb svg{ color: var(--ic-blue); }
.audio-thumb{ background: var(--chip-rose); }
.audio-thumb svg{ color: var(--ic-rose); }
.log-thumb{ background: var(--chip-violet); }
.log-thumb svg{ color: var(--ic-violet); }
.record-item span{ flex:1; min-width:0; display:flex; flex-direction:column; }
.record-item strong{
  font-size:14px; font-weight:650;
  /* Evidence is named after its storage key: one long unbroken token. */
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.record-item em{ font-style:normal; font-size:12px; color: var(--text-dim); margin-top:2px; }
.record-item em + em{ color: var(--text-faint); margin-top:0; }
.record-item > svg{ width:15px; height:15px; color: var(--text-faint); }
/* Open sits on the card as a plain glyph, not a second bordered card. */
.record-item .open-btn{ background:none; border:0; width:34px; height:34px; color: var(--text-dim); }

/* ---------- tools grid ---------- */
.tools-grid{ display:grid; grid-template-columns:1fr 1fr; gap:12px; margin-bottom:24px; }
.tool-card{
  background: var(--surface);
  border:1px solid var(--border);
  border-radius: var(--radius);
  padding:16px;
  display:flex; flex-direction:column; align-items:flex-start; gap:8px;
  color: var(--text);
  text-align:left;
}
.tool-card svg{ width:20px; height:20px; color: var(--red); }
.tool-card strong{ font-size:14px; font-weight:650; }
.tool-card em{ font-style:normal; font-size:12px; color: var(--text-dim); line-height:1.4; }

/* ---------- profile ---------- */
.profile-card{
  display:flex; align-items:center; gap:14px;
  background: var(--surface);
  border:1px solid var(--border);
  border-radius: var(--radius);
  padding:16px;
  margin-bottom:22px;
}
.profile-card div{ flex:1; display:flex; flex-direction:column; gap:2px; }
.profile-card strong{ font-size:16px; font-weight:700; letter-spacing:-0.015em; }
.profile-card em{ font-style:normal; font-size:12.5px; color: var(--text-dim); }

/* Grouped rows read as one card: a hairline around the group, hairlines
   between the rows, nothing under the last one. The drawer keeps the older
   bare-row look -- it is a nav list, not a settings group -- so the padding
   and the last-row rule are scoped to this container. */
.plain-list{
  display:flex; flex-direction:column;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  /* the rows paint their own hover, which would square off the corners */
  overflow: hidden;
  margin-bottom: 18px;
}
.plain-list .plain-row{ padding: 14px 16px; }
.plain-list .plain-row:last-child{ border-bottom: 0; }
.plain-row{
  display:flex; align-items:center; gap:12px;
  padding: 14px 2px;
  border-bottom: 1px solid var(--border);
  /* rows are <button> so they are tappable and keyboard-reachable */
  width:100%; text-align:left; background:none; border-left:0; border-right:0;
  border-top:0; color:inherit; font:inherit; cursor:pointer;
}
@media (hover: hover){ .plain-row:hover{ background: var(--surface-2); } }
.plain-row:is(a){ text-decoration:none; }
.plain-row svg:first-child{ width:18px; height:18px; color: var(--text-dim); }
.plain-row span{ flex:1; min-width:0; font-size:14px; font-weight:500; }
/* Capped so a long value (an email, a list of names) wraps on its own side
   instead of crushing the label to one word a line. */
.plain-row em{
  font-style:normal; font-size:12.5px; color: var(--text-faint); text-align:right;
  max-width:56%; overflow-wrap:anywhere; line-height:1.35;
}
/* An em inside the label is its subtitle, not the row's value. */
.plain-row span em{ display:block; max-width:none; text-align:left; margin-top:2px; }
/* A switch is a <span> too, and the rule above is the more specific of the
   two -- without this it stops being a 42px control and stretches across the
   whole row as a bare pink slab. */
.plain-row .switch{ flex:0 0 42px; }
.plain-row em.perm-ok{ color: var(--green); }
.plain-row em.perm-warn{ color: var(--amber); }
.plain-row em.perm-bad{ color: var(--red); }
.contact-row em.reach-ok{ color: var(--green); font-size:12px; }
.contact-row em.reach-warn{ color: var(--amber); font-size:12px; }

/* ---------- contacts ---------- */
/* Avatar | who they are | call, then the per-contact actions on a second line
   under the name. Grouped in a .plain-list card like every other list. */
.contact-row{
  display:grid; grid-template-columns: auto 1fr auto;
  column-gap:12px; row-gap:10px; align-items:start;
  padding: 14px 16px;
  border-bottom: 1px solid var(--border);
}
.contact-row:last-child{ border-bottom:0; }
.contact-info{ display:flex; flex-direction:column; min-width:0; padding-top:1px; }
.contact-row strong{ font-size:14.5px; font-weight:650; }
.contact-row em{ font-style:normal; font-size:12.5px; color: var(--text-dim); margin-top:2px; line-height:1.4; }
.contact-row .contact-call{
  width:38px; height:38px; border-radius:50%;
  display:grid; place-items:center;
  background: var(--chip-green);
  transition: transform .1s ease;
}
.contact-row .contact-call:active{ transform: scale(0.92); }
.call-ic{ width:18px; height:18px; color: var(--green); }
.contact-actions{
  grid-column: 2 / -1;
  display:flex; align-items:center; gap:8px; flex-wrap:wrap;
}
.contact-actions .del{ margin-left:auto; }

/* ---------- safe zones ---------- */
.zone-row{
  display:flex; align-items:center; gap:12px;
  padding: 14px 16px;
  border-bottom: 1px solid var(--border);
}
.zone-row:last-child{ border-bottom:0; }
.zone-row span{ display:flex; flex-direction:column; gap:2px; flex:1; min-width:0; }
.zone-row strong{ font-size:14.5px; font-weight:650; }
.zone-row em{ font-style:normal; font-size:12.5px; color: var(--text-dim); }

/* ---------- scrollbar hide ---------- */
::-webkit-scrollbar{ width:0; height:0; }

/* ---------- auth ---------- */
/* Left-set rather than centred: the eye lands on the headline, runs down the
   fields and ends on the one button, instead of zig-zagging across a column of
   centred lines. The rose wash in the corner is the only decoration. */
.auth-wrap{
  align-items:stretch;
  text-align:left;
  padding: calc(28px + env(safe-area-inset-top)) 24px calc(24px + env(safe-area-inset-bottom));
  background: radial-gradient(110% 46% at 0% 0%, var(--red-dim), transparent 72%), var(--bg);
}
.auth-brand{ display:flex; align-items:center; gap:10px; }
.auth-mark{ position:relative; width:40px; height:40px; flex-shrink:0; }
.auth-mark img{ position:relative; width:100%; height:100%; display:block; border-radius:12px; }
/* A slow beacon behind the mark: the one thing on this screen that moves on
   its own. Compositor-only, and gone with reduced motion. */
.auth-mark::before{
  content:''; position:absolute; inset:0; border-radius:12px;
  background: var(--red); opacity:0;
  animation: auth-beacon 3.6s cubic-bezier(.16,1,.3,1) infinite;
}
.auth-word{ font-size:17px; font-weight:700; letter-spacing:-0.02em; }

.auth-head{ margin-top:48px; }
.auth-head h1{ font-size:34px; line-height:1.05; font-weight:700; letter-spacing:-0.035em; }
.auth-head p{ margin-top:10px; font-size:15px; line-height:1.5; color: var(--text-dim); max-width:32ch; text-wrap: balance; }

.auth-form{ display:flex; flex-direction:column; gap:18px; width:100%; margin: 32px 0 0; }
.auth-field{ display:flex; flex-direction:column; gap:8px; }
.auth-label{ font-size:13px; font-weight:600; color: var(--text-dim); }
.auth-label-row{ display:flex; align-items:baseline; justify-content:space-between; }
.auth-forgot{ background:none; border:none; padding:4px 0; color: var(--red); font-size:13px; font-weight:600; }
.auth-forgot:active{ opacity:.7; }
.auth-help{ font-size:12px; color: var(--text-faint); }
.auth-form:not(.signup) .auth-help{ display:none; }

#screen-auth .field{
  height:52px; padding:0 16px; border-radius:14px; font-size:16px;
  transition: border-color .2s ease, box-shadow .3s cubic-bezier(.16,1,.3,1);
}
#screen-auth .field:focus{ border-color: var(--red); box-shadow: 0 0 0 4px var(--red-dim); }
.auth-pw{ position:relative; }
.auth-pw .field{ padding-right:52px; }
.auth-eye{
  position:absolute; top:4px; right:4px; width:44px; height:44px;
  display:flex; align-items:center; justify-content:center;
  background:none; border:none; border-radius:10px; color: var(--text-faint);
}
.auth-eye:active{ transform: scale(0.92); }
.auth-eye svg{ width:18px; height:18px; }
.auth-eye svg + svg,
.auth-eye[aria-pressed="true"] svg:first-child{ display:none; }
.auth-eye[aria-pressed="true"] svg + svg{ display:block; color: var(--text); }

/* Name, phone and the role picker arrive when the form turns into sign-up. */
.auth-form.signup [data-signup]{ animation: auth-in .45s cubic-bezier(.16,1,.3,1) both; }

/* Doubled class: the shared .form-error rules further down centre the text. */
.form-error.auth-error{
  text-align:left; font-size:13.5px; line-height:1.45;
  background: var(--red-dim); border-radius:12px; padding:12px 14px;
  animation: auth-nudge .4s cubic-bezier(.16,1,.3,1);
}
.form-error.auth-error.ok{ background: var(--chip-green); animation: auth-in .45s cubic-bezier(.16,1,.3,1); }

.auth-submit{
  position:relative; overflow:hidden; height:54px; margin-top:4px; padding:0 24px;
  display:flex; align-items:center; justify-content:center; gap:10px;
}
/* The arrow is drawn in CSS because app.js rewrites the button's text. */
.auth-submit::after{
  content:''; width:18px; height:18px; background: currentColor;
  -webkit-mask: var(--arrow) center / contain no-repeat; mask: var(--arrow) center / contain no-repeat;
  --arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h14M13 6l6 6-6 6'/%3E%3C/svg%3E");
  transition: transform .3s cubic-bezier(.16,1,.3,1);
}
@media (hover: hover){ .auth-submit:hover::after{ transform: translateX(3px); } }
/* Waiting on Supabase: a light sweep across the button instead of a spinner,
   and no arrow, so it does not read as tappable again. */
.btn-primary.auth-submit[disabled]{ opacity:.85; }
.auth-submit[disabled]::after{ display:none; }
.auth-submit[disabled]::before{
  content:''; position:absolute; inset:0;
  background: linear-gradient(100deg, transparent 30%, rgba(255,255,255,.28) 50%, transparent 70%);
  animation: auth-sweep 1.1s ease-in-out infinite;
}

.auth-foot{ margin-top:auto; padding-top:32px; display:flex; flex-direction:column; gap:14px; }
.auth-switch{
  height:52px; width:100%; background: var(--surface); color: var(--text);
  border:1px solid var(--border); border-radius:16px; font-size:14.5px; font-weight:600;
  transition: transform .1s ease, background .16s ease;
}
@media (hover: hover){ .auth-switch:hover{ background: var(--surface-2); } }
.auth-switch:active{ transform: scale(0.985); }
.auth-sos{
  display:flex; align-items:center; gap:10px; padding:4px 2px;
  color: var(--text-dim); font-size:13px; line-height:1.4; text-decoration:none;
}
.auth-sos svg{ width:16px; height:16px; color: var(--red); flex-shrink:0; }
.auth-sos strong{ color: var(--text); font-weight:600; }

/* Entrance: brand, headline, form, footer, each a beat after the last. It
   replays whenever the screen is shown, since display:none resets it. */
.auth-rise{ animation: auth-rise .7s cubic-bezier(.16,1,.3,1) both; animation-delay: calc(var(--i, 0) * 70ms); }
@keyframes auth-rise{ from{ opacity:0; transform: translateY(12px); } }
@keyframes auth-in{ from{ opacity:0; transform: translateY(-4px); } }
@keyframes auth-nudge{ 0%{ opacity:0; transform: translateX(0); } 30%{ opacity:1; transform: translateX(-4px); } 60%{ transform: translateX(3px); } }
@keyframes auth-sweep{ from{ transform: translateX(-100%); } to{ transform: translateX(100%); } }
@keyframes auth-beacon{ 0%{ transform: scale(1); opacity:.32; } 70%, 100%{ transform: scale(1.75); opacity:0; } }
@media (prefers-reduced-motion: reduce){
  .auth-rise, .form-error.auth-error, .auth-form [data-signup], .auth-mark::before, .auth-submit[disabled]::before{ animation:none; }
}
.field{
  width:100%;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text);
  font-family: inherit;
  font-size: 15px;
  padding: 15px 16px;
}
.field::placeholder{ color: var(--text-faint); }
.field:focus{ outline:none; border-color: var(--red); }
.form-error{ color: var(--red); font-size:13px; text-align:center; }
.form-error.ok{ color: var(--green); }
.btn-primary[disabled]{ opacity:0.6; }
.contact-row .del-ic{ color: var(--text-faint); width:18px; height:18px; margin-left:10px; }
.empty-note{ color: var(--text-faint); font-size:13px; text-align:center; padding: 24px 0; }
.avatar-row .empty-note{ text-align:left; padding:0; line-height:1.5; }
.made-by{ display:flex; align-items:center; justify-content:center; gap:5px; }
.made-by svg{ width:13px; height:13px; color: var(--red); fill: var(--red); }

/* An empty list says what belongs in it and how to add the first one. */
.empty-state{
  display:flex; flex-direction:column; align-items:center; text-align:center;
  padding: 40px 24px 32px;
  background: var(--surface); border:1px dashed var(--border);
  border-radius: var(--radius);
}
.empty-ic{
  width:52px; height:52px; border-radius:16px; margin-bottom:14px;
  display:grid; place-items:center; background: var(--red-dim);
}
.empty-ic svg{ width:24px; height:24px; color: var(--red); }
.empty-state strong{ font-size:15.5px; font-weight:700; letter-spacing:-0.015em; }
.empty-state p{ font-size:13px; line-height:1.5; color: var(--text-dim); margin-top:6px; max-width:30ch; }

/* "Back to home" sits where "Stop sharing" and "I'm safe" live, and used to
   wear their red while doing nothing alarming. Quiet unless something is on. */
.btn-danger.is-quiet,
.sos-idle .sos-actions .btn-danger,
.sos-failed .sos-actions .btn-danger{
  background: var(--surface); color: var(--text);
  border:1px solid var(--border); box-shadow:none;
}

/* Each screen settles in as it opens. The bars stay put; only the content
   moves, and only on transform and opacity. Replays on every visit because
   display:none resets it -- a data reload does not, the pane is not reinserted.
   backwards, not both: a transform left in effect afterwards would make the
   pane a containing block for anything fixed inside it. Welcome and sign-in
   stagger their own children instead (.auth-rise). */
.screen.active > .scroll-body{
  animation: screen-in .42s cubic-bezier(.16,1,.3,1) backwards;
}
@keyframes screen-in{ from{ opacity:0; transform: translateY(8px); } }

/* ---------- live data states ---------- */
.quick-chip div{ display:flex; flex-direction:column; align-items:center; gap:2px; }
.quick-chip .warn{ color: var(--text-faint); font-size:10px; }
.status-pill.alarmed{ border-color: var(--red); background: var(--red-dim); }
.status-pill.alarmed svg:first-child{ color: var(--red); }
.avatar.primary{ background: var(--red); color:#fff; }
.live-dot.idle{ background: var(--surface-2); color: var(--text-dim); }
.contact-row .icon-btn.del, .zone-row .icon-btn.del{ background:none; border:none; color: var(--text-faint); padding:6px; }
.contact-row .icon-btn.del svg, .zone-row .icon-btn.del svg{ width:18px; height:18px; }
.contact-row a{ color: inherit; display:inline-flex; }
.record-item a{ color: var(--text-dim); display:inline-flex; }
.btn-danger[disabled]{ opacity:0.5; }

/* ---------- toast ---------- */
.toast-host{
  position:fixed; left:50%; bottom:92px; transform:translateX(-50%);
  z-index:200; display:flex; flex-direction:column; gap:8px;
  width:min(390px, calc(100vw - 32px)); pointer-events:none;
}
.toast{
  background: var(--surface-2);
  border:1px solid var(--border);
  border-radius: var(--radius-sm);
  padding:13px 16px; font-size:13.5px; font-weight:500; line-height:1.4;
  box-shadow: var(--lift-lg);
  opacity:0; transform: translateY(12px);
  transition: opacity .22s ease, transform .22s ease;
}
.toast.in{ opacity:1; transform: translateY(0); }
.toast-ok{ border-color: var(--green); color: var(--green); }
.toast-error{ border-color: var(--red); color: var(--red); }

/* ---------- bottom sheet ---------- */
.sheet-backdrop{
  position:fixed; inset:0; z-index:210;
  background: rgba(0,0,0,0.6);
  display:flex; align-items:flex-end; justify-content:center;
  opacity:0; transition: opacity .22s ease;
}
.sheet-backdrop.in{ opacity:1; }
.sheet{
  width:min(430px,100%);
  background: var(--surface);
  border:1px solid var(--border); border-bottom:none;
  border-radius: 24px 24px 0 0;
  padding: 12px 20px calc(24px + env(safe-area-inset-bottom));
  display:flex; flex-direction:column; gap:14px;
  transform: translateY(100%); transition: transform .26s cubic-bezier(.22,1,.36,1);
  max-height: 88vh; overflow-y:auto;
}
.sheet-backdrop.in .sheet{ transform: translateY(0); }
.sheet-grab{ width:38px; height:4px; border-radius:2px; background: var(--border); align-self:center; margin-bottom:4px; }
.sheet h3{ font-size:18px; font-weight:800; font-family:'Geist',sans-serif; }
.sheet-desc{ font-size:13px; color: var(--text-dim); line-height:1.5; margin-top:-6px; }
.sheet-field{ display:flex; flex-direction:column; gap:7px; }
.sheet-field > span{ font-size:12.5px; font-weight:600; color: var(--text-dim); }
.sheet-field em{ color: var(--text-faint); font-style:normal; font-weight:500; }
.sheet-toggle{
  display:flex; align-items:center; justify-content:space-between;
  background: var(--surface-2); border:1px solid var(--border);
  border-radius: var(--radius-sm); padding: 13px 16px; font-size:14px; font-weight:500;
}
.sheet-actions{ display:flex; gap:10px; margin-top:4px; }
.sheet-actions button{ flex:1; }
.btn-ghost{
  background: var(--surface-2); color: var(--text);
  border:1px solid var(--border); border-radius:16px;
  padding:16px 24px; font-weight:600; font-size:15px;
}

/* ---------- tool overlays ---------- */
.overlay{
  position:fixed; inset:0; z-index:220; background: var(--bg);
  display:flex; align-items:center; justify-content:center;
  opacity:0; transition: opacity .2s ease;
}
.overlay.in{ opacity:1; }
.overlay-torch{ background:#fff; }
.torch-hint{ color:#333; font-size:13px; }

/* Two overlays deliberately stay dark while the rest of the app is light.
   The fake call has to pass for the handset's own call screen, and discreet
   mode is a disguise -- a blush-pink clock would give the game away. */
.overlay-call{ background:#121013; }
.overlay-call .call-status,
.overlay-call .call-sub{ color: rgba(255,255,255,0.62); }
.overlay-call .call-name{ color:#fff; }
.overlay-call .call-avatar{ background: rgba(255,255,255,0.14); color:#fff; border-color:transparent; }
.overlay-discreet{ background:#000; }

.call-screen{ display:flex; flex-direction:column; align-items:center; gap:10px; padding:56px 24px calc(48px + env(safe-area-inset-bottom)); width:100%; height:100%; }
.call-status{ color: var(--text-dim); font-size:13px; letter-spacing:0.08em; text-transform:uppercase; }
.call-avatar{ width:96px; height:96px; font-size:34px; margin:22px 0 6px; }
.call-name{ font-size:28px; font-weight:800; font-family:'Geist',sans-serif; }
.call-sub{ color: var(--text-dim); font-size:14px; }
.call-actions{ display:flex; gap:56px; margin-top:auto; padding-top:60px; }
.call-btn{ width:66px; height:66px; border-radius:50%; border:none; display:grid; place-items:center; }
.call-btn svg{ width:26px; height:26px; color:#fff; }
.call-btn.accept{ background: var(--green); }
.call-btn.decline{ background: var(--red); }

/* ---------- guardian SOS takeover ---------- */
/* Fired by alerts.js on a live SOS from someone who lists this account as a
   guardian. Deliberately styled to be jarring — full red, animated ring,
   nothing else reachable — since the whole point is that it can't be missed
   the way a quiet notification badge can be. */
.overlay-sos{ background: var(--red); }
.sos-takeover{
  display:flex; flex-direction:column; align-items:center; text-align:center;
  gap:10px; padding:64px 28px calc(48px + env(safe-area-inset-bottom)); width:100%; height:100%;
  color:#fff;
}
.sos-takeover-badge{
  width:88px; height:88px; border-radius:50%; display:grid; place-items:center;
  background: rgba(255,255,255,0.16); margin-bottom:8px;
  animation: sosPulse 1.2s ease-in-out infinite;
}
.sos-takeover-badge svg{ width:40px; height:40px; color:#fff; }
@keyframes sosPulse{
  0%, 100%{ box-shadow: 0 0 0 0 rgba(255,255,255,0.35); }
  50%{ box-shadow: 0 0 0 18px rgba(255,255,255,0); }
}
.sos-takeover-eyebrow{ font-size:13px; font-weight:700; letter-spacing:0.14em; opacity:0.85; }
.sos-takeover-name{ font-size:30px; font-weight:800; font-family:'Geist',sans-serif; }
.sos-takeover-sub{ font-size:15px; opacity:0.9; max-width:300px; }
.sos-takeover-actions{ display:flex; flex-direction:column; gap:10px; width:100%; margin-top:auto; }
.sos-takeover-actions .btn-primary{ background:#fff; color: var(--red); }
.sos-takeover-actions .btn-ghost{ background: rgba(255,255,255,0.12); color:#fff; border-color: rgba(255,255,255,0.3); }

.rec-screen{ display:flex; flex-direction:column; align-items:center; gap:14px; padding:32px 24px; width:min(390px,100%); }
.rec-screen h2{ font-size:44px; font-weight:800; font-family:'Geist',sans-serif; font-variant-numeric: tabular-nums; }
.rec-screen .btn-danger{ margin-top:28px; }
.rec-dot{ width:14px; height:14px; border-radius:50%; background: var(--red); animation: pulse-dot 1.2s infinite; }
@keyframes pulse-dot{ 0%,100%{ opacity:1; } 50%{ opacity:0.25; } }
.panic-ic{ width:44px; height:44px; color: var(--red); }
.checkin-bar{ margin: 0 0 14px; }
.sos-nearby{ margin-top: 10px; }

.cam-screen{ position:absolute; inset:0; display:flex; flex-direction:column; }
.cam-screen video{ flex:1; width:100%; object-fit:cover; background:#000; }
.cam-actions{ display:flex; align-items:center; justify-content:space-between; padding:20px 24px calc(28px + env(safe-area-inset-bottom)); }
.shutter{ width:68px; height:68px; border-radius:50%; background:#fff; border:5px solid var(--border); }

.discreet-screen{ text-align:center; }
.discreet-screen h1{ font-size:64px; font-weight:300; font-variant-numeric: tabular-nums; color:#fff; }
.discreet-screen p{ color: rgba(255,255,255,0.45); font-size:15px; margin-top:8px; }
.record-item .sealed{ width:16px; height:16px; color: var(--text-faint); flex-shrink:0; }

/* ---------- real maps ---------- */
.map-live{
  position:relative;
  height:280px;
  border-radius: var(--radius);
  overflow:hidden;
  border:1px solid var(--border);
  background: var(--surface);
  margin-bottom:22px;
}
.map-live.small{ height:180px; }
/* On Live tracking the map is the screen; at 280px the location card covered
   half of it. */
#trackMap{ height:360px; }
.map-canvas{ position:absolute; inset:0; display:none; background: var(--surface); }
/* The app is light now, so OSM's own tiles already belong here. All they get
   is a slight desaturation so the map does not out-colour the rose accent. */
.map-canvas .leaflet-tile-pane{
  filter: saturate(0.72) brightness(1.03);
}
/* Attribution the tiles require, in our own type. Leaflet stacks its own
   panes and controls up to z-index 800, so anything meant to sit on top of
   the map has to say so. */
.map-live.has-map::after{
  content: "© OpenStreetMap";
  position:absolute; right:8px; top:8px; z-index:900;
  font-size:9.5px; letter-spacing:0.02em;
  color: var(--text-dim);
  background: var(--glass-dim);
  padding:3px 7px; border-radius:6px;
  pointer-events:none;
}
.map-live.has-map .map-canvas{ display:block; }
.map-live.has-map .map-empty{ display:none; }
.map-empty{
  position:absolute; inset:0; display:grid; place-items:center;
  color: var(--text-faint); font-size:13px;
}
.map-card{
  position:absolute; left:12px; right:12px; bottom:12px; z-index:900;
  background: var(--glass);
  backdrop-filter: blur(12px);
  border:1px solid var(--border); border-radius: var(--radius-sm);
  box-shadow: var(--lift);
  padding:13px 15px; display:flex; flex-direction:column; gap:3px;
}
.map-card strong{ font-size:14px; font-weight:700; }
.map-card em{ font-style:normal; font-size:12.5px; color: var(--text-dim); }
.map-coords{
  font-family:'Geist Mono', ui-monospace, monospace;
  font-size:11px; color: var(--text-faint); font-variant-numeric: tabular-nums;
  margin-top:2px;
}
.live-dot{
  align-self:flex-start;
  background: var(--red); color:#fff;
  font-size:10px; font-weight:700; letter-spacing:0.08em; text-transform:uppercase;
  padding:3px 8px; border-radius:6px; margin-bottom:4px;
}

/* Readouts under stress: tabular numerals everywhere a number matters. */
#timerText, .rec-screen h2, .record-item em{
  font-variant-numeric: tabular-nums;
}
#timerText, .rec-screen h2{ font-family:'Geist Mono', ui-monospace, monospace; }

/* ---------- detection countdown (shake) ---------- */
.overlay-scream{ background: var(--bg); }
.scream-alert{
  display:flex; flex-direction:column; align-items:center; text-align:center;
  gap:14px; padding:32px 28px; width:100%; max-width:340px;
}
.scream-alert svg{ width:42px; height:42px; color: var(--red); }
.scream-alert h2{ font-size:22px; font-family:'Geist',sans-serif; }
.scream-alert p{ color: var(--text-dim); font-size:14px; }
.scream-alert p strong{ color: var(--text); font-variant-numeric: tabular-nums; }
.scream-alert .btn-primary,
.scream-alert .btn-ghost{ width:100%; }

/* ---------- side drawer (menu + alert activity) ---------- */
.overlay-drawer{
  background: rgba(0,0,0,0.6);
  align-items: stretch; justify-content: flex-start;
}
.drawer{
  width: min(86%, 340px); height:100%;
  background: var(--surface);
  border-right: 1px solid var(--border);
  display:flex; flex-direction:column;
  transform: translateX(-100%); transition: transform .22s ease;
  padding-top: env(safe-area-inset-top);
}
.overlay-drawer.in .drawer{ transform: none; }

/* The activity panel comes in from the other side, so the two never read as
   the same surface even though they share the shell. */
.overlay-drawer:has(.drawer-right){ justify-content: flex-end; }
.drawer-right{ border-right:0; border-left:1px solid var(--border); transform: translateX(100%); }

.drawer-head{
  display:flex; align-items:center; gap:12px;
  padding:20px 18px; border-bottom:1px solid var(--border);
}
.drawer-head > div{ flex:1; min-width:0; }
.drawer-head strong{ display:block; font-size:15px; font-weight:700; }
.drawer-head em{
  display:block; font-style:normal; font-size:12px; color: var(--text-dim);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}

.drawer-nav{ flex:1; overflow-y:auto; padding: 6px 18px; }
.drawer-item{
  display:flex; align-items:center; gap:14px; width:100%;
  padding:13px 2px; background:none; border:0; border-bottom:1px solid var(--border);
  color:inherit; font:inherit; text-align:left; cursor:pointer;
}
@media (hover: hover){ .drawer-item:hover{ background: var(--surface-2); } }
/* Grey, like the settings rows: eleven rose icons in a column out-shout the
   one red thing in the app that matters. */
.drawer-item svg:first-child{ width:19px; height:19px; color: var(--text-dim); flex-shrink:0; }
.drawer-item span{ flex:1; min-width:0; }
.drawer-item strong{ display:block; font-size:14px; font-weight:600; }
.drawer-item em{ display:block; font-style:normal; font-size:12px; color: var(--text-faint); margin-top:2px; }

/* Activity rows reuse .plain-row, whose em is right-aligned — the nested one
   inside the label is a subtitle, not a value. */
.drawer-nav .plain-row span em{ text-align:left; display:block; margin-top:2px; }
.drawer-nav .plain-row svg:first-child{ color: var(--text-dim); }

.drawer-foot{ padding:18px; border-top:1px solid var(--border); padding-bottom: calc(18px + env(safe-area-inset-bottom)); }

/* ---------- leaflet ---------- */
/* Markers are CSS, not the default icon set — no image requests to 404. */
.map-dot{
  display:block; width:16px; height:16px; border-radius:50%;
  border:2px solid var(--surface); box-shadow:0 1px 5px rgba(90,50,65,0.35);
}
.map-dot-you{ background: var(--red); box-shadow:0 0 0 6px rgba(233,59,99,0.22), 0 1px 5px rgba(90,50,65,0.35); }
.map-dot-start{ background: var(--text-faint); width:12px; height:12px; }
.map-dot-responder{ background: var(--green); }

.map-canvas .leaflet-control-zoom a{
  background: var(--glass); color: var(--text);
  border-color: var(--border); width:30px; height:30px; line-height:29px;
}
.map-canvas .leaflet-control-zoom a:hover{ background: var(--surface-2); color: var(--text); }
.map-canvas .leaflet-bar{ border:1px solid var(--border); box-shadow:none; }
.map-canvas .leaflet-tooltip{
  background: var(--glass); color: var(--text);
  border:1px solid var(--border); border-radius:8px;
  font-family:'Geist',sans-serif; font-size:11.5px; font-weight:600;
  box-shadow: var(--lift);
}
.map-canvas .leaflet-tooltip::before{ display:none; }   /* the arrow inherits a white border */

/* ---------- offline ---------- */
/* Above every overlay: the app opening from cache while the network is gone
   is exactly when someone would otherwise press SOS and believe it worked. */
.offline-bar{
  position:fixed; left:0; right:0; bottom:0; z-index:400;
  background: var(--amber); color:#1A1206;
  font-size:12.5px; font-weight:600; text-align:center;
  padding: 10px 16px calc(10px + env(safe-area-inset-bottom));
}
.offline-bar a{ color:#1A1206; }

/* ---------- unread marker on the bell, unread message rows ---------- */
/* A dot rather than a count: the number is not the point, "there is
   something you have not seen" is. */
#bellBtn.has-unread { position: relative; }
#bellBtn.has-unread::after {
  content: '';
  position: absolute; top: 6px; right: 6px;
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--red);
  box-shadow: 0 0 0 2px var(--bg);
}
.plain-row.unread { background: var(--red-dim); }
.plain-row.unread strong::after {
  content: '';
  display: inline-block; margin-left: 6px;
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--red);
  vertical-align: middle;
}

/* ---------- the Hub ---------- */
/* On the SOS screen, under the list of who was alerted. The board answers the
   one question a person in trouble cannot ask out loud: has anybody actually
   picked this up. A name with no state next to it is not a failure, it is
   "not yet", and it has to read that way at a glance. */
.hub-card{
  background: var(--surface); border:1px solid var(--border);
  border-radius: var(--radius); padding:14px 16px; margin: 4px 0 18px;
  text-align:left;
}
.hub-card h3{ margin:0 0 10px; font-size:12px; letter-spacing:.06em;
  text-transform:uppercase; color: var(--text-dim); }
.hub-board{ list-style:none; padding:0; margin:0; }
.hub-board li{
  display:flex; justify-content:space-between; gap:12px; align-items:center;
  padding:9px 0; border-bottom:1px solid var(--border); font-size:13.5px;
}
.hub-board li:last-child{ border-bottom:0; }
.hub-board .st{ font-size:12px; color: var(--text-faint); }
.hub-board .st.responding{ color: var(--green); font-weight:600; }
.hub-board .st.viewed{ color: var(--text-dim); }
.hub-board .st.unavailable{ color: var(--amber); }
.hub-thread{ list-style:none; padding:0; margin:12px 0 0; max-height:180px; overflow-y:auto; }
.hub-thread li{ padding:7px 0; font-size:13px; border-top:1px solid var(--border); }
.hub-thread b{ display:block; font-size:11px; color: var(--text-faint); font-weight:600; }
.hub-thread li.mine b{ color: var(--red); }
.hub-say{ display:flex; gap:8px; margin-top:10px; }
.hub-say input{
  flex:1; background: var(--surface-2); border:1px solid var(--border);
  border-radius: var(--radius-sm); color: var(--text); padding:10px 12px; font-size:14px;
}
.hub-say input{ height:44px; }
.hub-say button{ flex:0 0 auto; height:44px; padding:0 18px; border-radius: var(--radius-sm); font-size:14px; }
/* The toggle on the contacts screen. Deliberately not styled as a warning:
   leaving someone off the Hub still alerts them. */
/* Unscoped: the responder panel uses the same pill for "I'm going" and for
   the duty switch, and had no styling for it at all. */
.hub-btn{
  flex-shrink:0;
  min-height:32px; padding:0 14px;
  background: var(--surface); border:1px solid var(--border); color: var(--text-dim);
  border-radius:999px; font-size:12.5px; font-weight:600;
  transition: transform .1s ease, background .16s ease;
}
.hub-btn:active{ transform: scale(0.96); }
.hub-btn.on{ border-color: transparent; background: var(--chip-green); color: var(--green); }
.hub-btn[disabled]{ opacity:.55; }

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

/* ---------- light-theme card lift ---------- */
/* Every panel that used to rely on a dark fill for separation now sits as a
   white card on the blush canvas, so it needs a shadow to read as raised.
   Grouped here rather than repeated on each rule above. */
.row-card,
.tool-card,
.responder-row,
.record-item,
.toggle-row,
.notice,
.profile-card,
.hub-card,
.map-live,
.sheet-toggle{
  box-shadow: var(--lift);
}

/* Pressable cards get a press state; they had none on light. */
.row-card, .tool-card, .record-item{
  transition: box-shadow .18s ease, transform .1s ease;
}
@media (hover: hover){
  .row-card:hover, .tool-card:hover, .record-item:hover{ box-shadow: var(--lift-lg); }
}
.row-card:active, .tool-card:active{ transform: scale(0.985); }

/* ---------- focus and motion ---------- */
/* A visible ring on the accent, on every interactive thing that did not have
   one. Keyboard users had only the sos button before. */
:where(button, a, input, select, textarea, [tabindex]):focus-visible{
  outline: 2px solid var(--red);
  outline-offset: 2px;
  border-radius: 6px;
}
.field:focus{ box-shadow: 0 0 0 3px var(--red-dim); }
html{ scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce){ html{ scroll-behavior: auto; } }

/* ===================== DARK THEME ===================== */
/* Same blush identity after dark: a plum-charcoal ground instead of white,
   the rose accent lifted just enough to hold contrast on it. Only the tokens
   are redefined -- every rule above already reads them.

   Three states, because the toggle has to beat the OS in both directions:
   no attribute follows the system, [data-theme] is an explicit choice. */
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]){
    --bg: #000000;
    --surface: #121012;
    --surface-2: #1A171A;
    --border: #262126;
    --red: #F2537A;
    --red-press: #D93E64;
    --alarm: #E5252B;
    --red-dim: #3B1F2A;
    --red-glow: rgba(242,83,122,0.26);
    --green: #34C46F;
    --amber: #F5A62E;
    --text: #F4EEF1;
    --text-dim: #B2A7AD;
    --text-faint: #7E7279;

    --chip-violet: #2C2540;  --ic-violet: #A78BFA;
    --chip-green:  #1C3327;  --ic-green:  #34C46F;
    --chip-blue:   #1D2B42;  --ic-blue:   #6BA6FB;
    --chip-amber:  #392B18;  --ic-amber:  #F5A62E;
    --chip-rose:   #3B1F2A;  --ic-rose:   #F2537A;

    --lift: 0 1px 2px rgba(0,0,0,0.42), 0 6px 18px -8px rgba(0,0,0,0.6);
    --lift-lg: 0 2px 4px rgba(0,0,0,0.46), 0 14px 34px -12px rgba(0,0,0,0.68);

    --page: #000000;
    --glass: rgba(18,16,18,0.94);
    --glass-dim: rgba(18,16,18,0.86);
    --hairline: rgba(255,255,255,0.07);
    --shell-shadow: 0 0 0 1px var(--hairline), 0 40px 90px -30px rgba(0,0,0,0.8);

    color-scheme: dark;
  }
}
:root[data-theme="dark"]{
  --bg: #000000;
  --surface: #121012;
  --surface-2: #1A171A;
  --border: #262126;
  --red: #F2537A;
  --red-press: #D93E64;
  --alarm: #E5252B;
  --red-dim: #3B1F2A;
  --red-glow: rgba(242,83,122,0.26);
  --green: #34C46F;
  --amber: #F5A62E;
  --text: #F4EEF1;
  --text-dim: #B2A7AD;
  --text-faint: #7E7279;

  --chip-violet: #2C2540;  --ic-violet: #A78BFA;
  --chip-green:  #1C3327;  --ic-green:  #34C46F;
  --chip-blue:   #1D2B42;  --ic-blue:   #6BA6FB;
  --chip-amber:  #392B18;  --ic-amber:  #F5A62E;
  --chip-rose:   #3B1F2A;  --ic-rose:   #F2537A;

  --lift: 0 1px 2px rgba(0,0,0,0.42), 0 6px 18px -8px rgba(0,0,0,0.6);
  --lift-lg: 0 2px 4px rgba(0,0,0,0.46), 0 14px 34px -12px rgba(0,0,0,0.68);

  --page: #000000;
  --glass: rgba(18,16,18,0.94);
  --glass-dim: rgba(18,16,18,0.86);
  --hairline: rgba(255,255,255,0.07);
  --shell-shadow: 0 0 0 1px var(--hairline), 0 40px 90px -30px rgba(0,0,0,0.8);

  color-scheme: dark;
}

/* OpenStreetMap ships one set of tiles and they are daylight-bright, which is
   a lit rectangle in the middle of a dark screen. Inverting the raster is the
   usual way to darken them without a second tile provider or an API key.
   ponytail: filter on panning tiles costs a repaint; swap to a dark tile
   source (Carto dark_matter) if it ever feels slow on a low-end phone. */
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]) .leaflet-tile{
    filter: invert(1) hue-rotate(180deg) brightness(0.92) contrast(0.88) saturate(0.7);
  }
}
:root[data-theme="dark"] .leaflet-tile{
  filter: invert(1) hue-rotate(180deg) brightness(0.92) contrast(0.88) saturate(0.7);
}

/* ===================== LOADING SKELETONS ===================== */
/* Shaped like the content that is about to land, so a screen does not jump
   when it arrives. Two kinds, because the loaders come in two kinds:
     .skel      fills a container the loader is going to overwrite wholesale
     .skel-text masks a field the loader only patches -- clearing that markup
                would destroy the very node the loader writes into. */
.skel, .skel-text{
  background: linear-gradient(90deg,
    var(--surface) 0%, var(--surface-2) 42%, var(--surface) 84%);
  background-size: 280% 100%;
  animation: skel-shimmer 1.4s cubic-bezier(0.16, 1, 0.3, 1) infinite;
}
.skel{
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
}
.skel-list{ display:flex; flex-direction:column; gap:12px; padding-top:4px; }
.skel-row{ display:flex; align-items:center; gap:10px; }
.skel.dot{ width:38px; height:38px; border-radius:50%; flex-shrink:0; }
.skel-text{
  /* Inline by default: these sit on <em> and <strong> inside existing rows. */
  display:inline-block; min-width:92px; min-height:1em;
  border-radius:6px;
  color: transparent !important;
}
@keyframes skel-shimmer{
  from{ background-position: 140% 0; }
  to  { background-position: -140% 0; }
}
@media (prefers-reduced-motion: reduce){
  .skel, .skel-text{ animation: none; }
}

/* ---------- family sharing ---------- */
/* The child's list of parents, and the parent's confirmation screen. Both are
   plain reading surfaces: the only thing either of them does is hand over, or
   take back, sight of someone's emergencies. */
.family-row span{ display:flex; flex-direction:column; gap:2px; }
.family-row span em{
  font-style:normal; font-size:11.5px; color: var(--text-faint);
  font-weight:500; text-align:left;
}
.link-plain, .link-danger{
  background:none; border:0; padding:6px 2px; font:inherit;
  font-size:12.5px; font-weight:600; cursor:pointer; flex:0 0 auto;
}
.link-plain{ color: var(--text-dim); }
.link-danger{ color: var(--red); }

/* Shown once, at creation. The database keeps only the hash of the code, so
   this overlay is the single moment the link exists to be copied. */
.overlay-sheet{ padding:24px; }
.invite-sheet{
  width:min(430px,100%);
  background: var(--surface); border:1px solid var(--border);
  border-radius: 24px; padding: 22px 20px;
  display:flex; flex-direction:column; gap:14px;
  max-height: 88vh; overflow-y:auto;
}
.invite-sheet h3{ font-size:18px; font-weight:800; font-family:'Geist',sans-serif; }
.invite-link{
  background: var(--surface-2); border:1px solid var(--border);
  border-radius: var(--radius-sm); padding:12px 14px;
  font-family:'Geist Mono',ui-monospace,monospace; font-size:12px;
  /* A 64-character code has no spaces to break at. */
  word-break: break-all; line-height:1.5;
}

.join-card{ display:flex; flex-direction:column; gap:12px; padding-top:8px; }
.join-card h2{ font-size:19px; font-weight:800; font-family:'Geist',sans-serif; }
.join-name{ font-size:24px; font-weight:800; font-family:'Geist',sans-serif; }
.join-card .btn-block + .btn-block{ margin-top:2px; }

/* ---------- watching over someone ---------- */
/* The home screen for an account whose reason for existing is another person.
   Deliberately not a mirror of the personal home: no dial, nothing to press
   in a hurry, because a watcher's job is to notice, not to trigger. */
.role-pick{ border:0; margin:0; padding:0; display:flex; flex-direction:column; gap:8px; }
.role-pick legend{ font-size:12.5px; font-weight:600; color: var(--text-dim); padding:0 0 6px; }
.role-pick label{
  display:flex; align-items:flex-start; gap:12px; text-align:left;
  background: var(--surface); border:1px solid var(--border);
  border-radius: var(--radius-sm); padding:14px 16px; cursor:pointer;
  transition: border-color .2s ease, background .2s ease;
}
.role-pick label:has(:checked){ border-color: var(--red); background: var(--red-dim); }
.role-pick input{
  appearance:none; -webkit-appearance:none; flex-shrink:0;
  width:18px; height:18px; margin-top:1px; border-radius:50%;
  border:1.5px solid var(--text-faint); background: var(--surface);
  transition: border .2s cubic-bezier(.16,1,.3,1);
}
.role-pick input:checked{ border:5.5px solid var(--red); }
.role-pick input:focus-visible{ outline:2px solid var(--red); outline-offset:2px; }
.role-pick strong{ display:block; font-size:14px; font-weight:600; }
.role-pick em{ display:block; font-style:normal; font-size:12px; color: var(--text-dim); margin-top:2px; line-height:1.4; }

.watch-card{
  background: var(--surface); border:1px solid var(--border);
  border-radius: var(--radius); padding:16px; margin-bottom:12px;
  display:flex; flex-direction:column; gap:12px;
}
/* An active alarm is the only urgent thing on this screen. It should not be
   possible to scroll past it thinking everything is fine. */
.watch-card.alarm{ border-color: var(--alarm); box-shadow: 0 0 0 2px var(--red-glow); }
.watch-card header{ display:flex; align-items:center; gap:12px; }
.watch-card h3{ font-size:16px; font-weight:700; font-family:'Geist',sans-serif; }
.watch-avatar{
  width:44px; height:44px; flex:0 0 44px; border-radius:50%;
  background: var(--surface-2); color: var(--text-dim);
  display:flex; align-items:center; justify-content:center;
  font-weight:700; font-size:17px;
}
.watch-card.alarm .watch-avatar{ background: var(--alarm); color:#fff; }
.watch-state{ font-size:12.5px; margin-top:2px; }
.watch-ok{ color: var(--green); }
.watch-warn{ color: var(--amber); }
.watch-bad{ color: var(--alarm); font-weight:700; }
.watch-reach{ font-size:12px; color: var(--text-faint); line-height:1.5; }
p.watch-warn{ font-size:12.5px; line-height:1.5; }
.watch-empty{ text-align:center; padding:48px 20px; }
.watch-empty svg{ width:32px; height:32px; color: var(--text-faint); }
.watch-empty h3{ font-size:16px; font-weight:700; margin-top:12px; font-family:'Geist',sans-serif; }
