/* Range and heading sit clear of the bowl's rim, tight to the screen edges (John, 2026-09-12). */
.radar-side-labels {
  bottom:calc(6px + var(--radar-w,320px) * .045);
  inset-inline:2px
}

.radar-side-labels .range-readout {
  text-align:left
}


/* Header controls: a shorter Radar/Grid switch sitting next to a smaller ? (John, 2026-09-12). */
.header-discovery .segmented {
  border-radius:15px;
  padding:2px
}

.header-discovery .segmented button {
  min-width:52px;
  min-height:28px;
  height:28px;
  padding:0 12px;
  border-radius:13px;
  font-size:13px
}

.help-button {
  width:28px;
  height:28px;
  margin-left:6px;
  font-size:14px
}

.header-discovery {
  gap:8px
}

/* Filter row: all three left-aligned with checkboxes, nudged down off the header (John, 2026-09-12). */
.discovery-filters,
.discover>.discovery-filters {
  justify-content:flex-start;
  gap:16px;
  padding-top:6px;
  height:36px;
  min-height:36px
}





/* Range and heading read against the map: brighter type on a dark-blue fade (John, 2026-09-12). */
.radar-side-labels .range-readout,
.radar-side-labels #radar-direction {
  color:#fff;
  text-shadow:0 1px 4px #000a,0 0 10px #000c;
  border-radius:0;
  padding:16px 10px 6px;
  font-size:13px;
  line-height:1.1
}

/* The dark fade belongs to the row, not to each label: no bubbles. */
.radar-side-labels .range-readout {
  background:linear-gradient(to right,#000610 0,#000610cf 58%,#00061000 100%)
}


/* km / m under the number. */
.radar-side-labels .range-unit {
  display:block;
  margin:1px 0 0;
  font-size:9px;
  color:#cfe8f5;
  letter-spacing:.5px
}


/* The bowl draws over the gradient, never the other way round (John, 2026-09-12). Above .fx-layer (6) so bubbles don't cover the range/direction labels, below nav/header (8). */
.radar-side-labels {
  z-index:7
}


/* "No profiles in this direction" reads against the map (John, 2026-09-12). */
.direction-stage>.direction-empty {
  color:#f4fbff;
  text-shadow:0 1px 6px #000b
}

.direction-stage>.direction-empty.is-visible {
  opacity:.8
}

/* Location denied: keep the copy at full contrast (not the dim ambient empty state) and
   make the retry button both visible and tappable (the container above is pointer-events:none). */
.direction-stage>.direction-empty:has(.location-denied-retry) {
  opacity:1
}

.location-denied-retry {
  display:inline-flex;
  align-items:center;
  justify-content:center;
  min-height:44px;
  margin-top:12px;
  padding-inline:22px;
  pointer-events:auto;
  color:var(--ink);
  background:#0a1622cc;
  border:1.5px solid var(--ice);
  font-weight:650
}

.direction-stage>.direction-empty span {
  color:#dceaf3
}

/* Give the fade room so it reads as a wash, not a block. */
.radar-side-labels .range-readout,
.radar-side-labels #radar-direction {
  min-width:128px
}

.radar-side-labels .range-readout {
  text-align:left
}


/* The wash lives on a layer behind the text, faded on all sides, so no band edge shows and the type stays solid. */
.radar-side-labels .range-readout,
.radar-side-labels #radar-direction {
  position:relative;
  background:none
}

.radar-side-labels .range-readout::before,
.radar-side-labels #radar-direction::before {
  content:"";
  position:absolute;
  inset:-26px -4px -26px -4px;
  z-index:-1;
  pointer-events:none;
  -webkit-mask-image:linear-gradient(to bottom,transparent 0,#000 32%,#000 68%,transparent 100%);
  mask-image:linear-gradient(to bottom,transparent 0,#000 32%,#000 68%,transparent 100%)
}

.radar-side-labels .range-readout::before {
  background:linear-gradient(to right,#000610 0,#000610d9 46%,#00061000 100%)
}


/* The heading lives in the rose inside the bowl now (John, 2026-09-12): sitting above the origin so the nav
   bar never clips it. */
.compass-field .corner-compass {
  top:calc(100% - var(--radar-w,318px) * .23);
  pointer-events:auto;
  border:0;
  padding:0;
  cursor:pointer;
  width:62px;
  height:62px;
  background:#04101a;
  box-shadow:0 0 18px #05121ccc
}

.compass-field .corner-compass.on {
  box-shadow:0 0 0 1px #4cd8e866,0 0 20px #4cd8e833
}

.compass-field .corner-compass .compass-rose span {
  background:none;
  font:600 10px ui-monospace,SFMono-Regular,monospace;
  color:#cfeaf5;
  text-shadow:0 1px 3px #000
}

.compass-field .corner-compass .rose-n {
  color:#eafcff!important
}

.compass-field .compass-caption {
  display:block;
  top:auto;
  bottom:-15px;
  font:600 10px ui-monospace,SFMono-Regular,monospace;
  color:#fff;
  text-shadow:0 1px 4px #000
}

.radar-side-labels {
  justify-content:flex-start
}

/* Make the rose read on the map: solid disc, brighter strokes, heading word above it. */
.compass-field .corner-compass {
  width:66px;
  height:66px;
  background:radial-gradient(circle,#061826f2 0,#04101ae6 70%,#04101a99 100%);
  box-shadow:0 0 0 1px #4cd8e83d,0 0 22px #04101a
}

.compass-field .corner-compass .compass-rose svg {
  stroke:#9adcee;
  stroke-opacity:.85
}

.compass-field .corner-compass .compass-rose .rose-north {
  fill:#bdf3ff;
  opacity:1
}

.compass-field .corner-compass .compass-rose span {
  font-size:11px;
  color:#e6f7ff
}

.compass-field .compass-caption {
  top:-15px;
  bottom:auto
}

/* With the Pixi scene on, the compass letters are drawn on the radar itself; the DOM rose stays only as the
   tap target for compass-follow. */
body.scene-on .compass-field .corner-compass {
  top:calc(100% - var(--radar-w,318px) * .16);
  background:none;
  box-shadow:none
}

body.scene-on .compass-field .corner-compass .compass-rose,
body.scene-on .compass-field .corner-compass .compass-needle,
body.scene-on .compass-field .corner-compass .compass-center,
body.scene-on .compass-field .compass-caption {
  display:none
}

/* Tap, woof and wink on his profile too (John, 2026-09-12). */
.react-row {
  display:flex;
  gap:8px;
  margin:10px 0 2px
}

.react-row button {
  flex:1;
  display:inline-flex;
  align-items:center;
  justify-content:center;
  gap:6px;
  min-height:40px;
  padding:0 8px;
  border:1px solid var(--line);
  border-radius:12px;
  background:#14202b;
  color:#d7e7f0;
  font:inherit;
  font-size:13px;
  cursor:pointer
}

.react-row button:active {
  background:#1b2b38
}

.react-row button.action-sent {
  border-color:#79d8bc;
  background:#173b36;
  color:#dffff5;
  animation:action-button-confirm .42s cubic-bezier(.2,.9,.2,1.35)
}

@keyframes action-button-confirm {
  0% { transform:scale(.9) }
  65% { transform:scale(1.08) }
  100% { transform:scale(1) }
}

/* Split screen: the open chat sits beside the radar in a phone-shaped column (John, 2026-09-12). */
.shell-body {
  display:flex;
  align-items:stretch;
  min-height:0
}

.shell-body>main {
  flex:1;
  min-width:0
}

.side-chat {
  flex:none;
  display:none;
  border-left:1px solid #1c2a35;
  background:var(--bg);
  overflow:hidden
}

body.split-chat .side-chat {
  display:block;
  height:max(0px,calc(var(--vvh,100dvh) - 44px - env(safe-area-inset-top) - var(--nav-h,calc(69px + env(safe-area-inset-bottom))) - var(--setup-h,0px)));
  min-height:0;
  aspect-ratio:9 / 19.5;
  max-width:42vw;
  min-width:320px
}

/* The keyboard hides both header and nav; app-2 updates --vvh from visualViewport on resize. */
body.split-chat.keyboard-open .side-chat {
  height:max(0px,calc(var(--vvh,100dvh) - env(safe-area-inset-top) - var(--setup-h,0px)));
}

body.split-chat .side-chat .conversation {
  height:100%;
  min-height:0
}

.side-chat[hidden] {
  display:none
}

/* Social comfort controls and Activity quick actions. These stay calm at rest,
   then use the same short transitions as the existing tap confirmation. */
.intention-options {
  display:grid;
  grid-template-columns:repeat(2,minmax(0,1fr));
  gap:8px
}

.intention-choice,
.comfort-choice span {
  min-height:42px;
  padding:9px 11px;
  border:1px solid var(--line);
  border-radius:12px;
  background:#132633;
  color:var(--ink);
  font:inherit;
  font-size:13px;
  cursor:pointer;
  transition:background .18s,border-color .18s,transform .18s
}

.intention-choice:hover,
.comfort-choice:hover span {
  transform:translateY(-1px);
  border-color:#6edcf0
}

.intention-choice.active {
  border-color:var(--ice);
  background:#1c4050;
  box-shadow:0 0 0 1px #b7deef33
}

.intention-controls {
  display:flex;
  align-items:center;
  gap:9px;
  margin-top:11px;
  color:var(--muted);
  font-size:12px
}

.intention-controls select {
  min-height:36px;
  width:auto;
  margin:0
}

.intention-controls .text-button {
  margin-left:auto
}

.intention-current {
  margin:10px 0 0;
  color:var(--muted);
  font-size:12px
}

.comfort-options {
  display:grid;
  gap:8px
}

.comfort-choice {
  display:block;
  cursor:pointer
}

.comfort-choice input {
  position:absolute;
  opacity:0;
  width:1px;
  height:1px
}

.comfort-choice span {
  display:block
}

.comfort-choice input:checked+span {
  border-color:var(--ice);
  background:#1c4050
}

.comfort-choice input:focus-visible+span {
  outline:2px solid var(--ice);
  outline-offset:2px
}

.comfort-public {
  display:flex;
  gap:10px;
  align-items:flex-start;
  margin-top:13px;
  color:var(--ink);
  font-size:13px
}

.comfort-public input {
  width:18px;
  height:18px;
  flex:none;
  accent-color:var(--ice)
}

.comfort-public small,
.curiosity-control small,
.curiosity-setting small {
  display:block;
  margin-top:4px;
  color:var(--muted);
  font-size:11px
}

.intention-display {
  display:flex;
  flex-wrap:wrap;
  align-items:baseline;
  gap:7px;
  margin:0 0 12px;
  padding:10px 12px;
  border:1px solid #4fd8e855;
  border-radius:13px;
  background:#123142;
  color:var(--ink)
}

.intention-display strong {
  font-size:14px
}

.intention-display small {
  color:#b9d9e5;
  font-size:11px
}

.comfort-display {
  margin-top:3px
}





.activity-list {
  display:grid
}

.activity-row {
  border-bottom:1px solid var(--line)
}

.activity-target {
  border-bottom:0
}

.activity-actions {
  display:flex;
  flex-wrap:wrap;
  gap:7px;
  padding:0 0 12px 58px
}

.activity-quick {
  min-height:34px;
  padding:6px 12px;
  border-radius:11px;
  font-size:12px;
  transition:background .18s,border-color .18s,transform .18s
}

.activity-quick:hover {
  transform:translateY(-1px)
}

.hobby-row,
.hobby-row .hobby,
.hobby-chip,
.hobby-chip span {
  user-select:none;
  -webkit-user-select:none
}

.hobby {
  cursor:pointer
}

@media (max-width:460px) {
  .activity-actions {
    padding-left:0
  }
}

@media (prefers-reduced-motion:reduce) {
  .intention-choice,
  .comfort-choice span,
  .activity-quick,
  .hobby {
    transition:none
  }

  .intention-choice:hover,
  .comfort-choice:hover span,
  .activity-quick:hover {
    transform:none
  }
}

/* The right-hand column also holds the chat list on desktop; its close button only shows there. */
.side-close {
  display:none
}

body.split-chat .side-chat .side-close {
  display:inline-flex;
  margin-left:auto
}

body.split-chat .side-chat .chats-section {
  height:100%;
  overflow:auto;
  padding:12px 12px 20px
}

body.split-chat .side-chat .chat-list-head {
  align-items:center
}

body.split-chat .side-chat .conversation-header .side-close {
  margin-left:8px
}

/* Name & picture block: one tidy row instead of three stray pieces (John, 2026-09-13). */
.me-section .avatar-field {
  margin-top:22px;
  padding:0;
  border:0
}

.me-section .avatar-field legend {
  padding:0 0 10px;
  margin:0;
  font-size:13px;
  font-weight:600;
  color:#cfe3ee;
  letter-spacing:.2px
}

.avatar-editor {
  align-items:center;
  gap:14px
}

.editor-avatar,.editor-avatar.avatar {
  width:88px;
  height:88px;
  border-radius:50%;
  flex:none
}

.editor-avatar img {
  object-position:50% 22%
}

.avatar-editor>div:last-child {
  display:flex;
  flex-direction:column;
  align-items:flex-start;
  gap:6px
}

.upload-button {
  min-height:38px;
  padding:8px 14px;
  border-radius:10px
}

#photo-status {
  margin:0
}

.preview-circle {
  padding:0;
  min-height:0;
  font-size:12px
}

.avatar-picker {
  margin-top:14px
}

.avatar-picker summary {
  min-height:0;
  padding:6px 0
}

/* Spacing pass (John, 2026-09-13: "smooshed, not properly spaced, or just a bad use of the space"). */

/* Activity was the one full-page view with no content column: it ran edge to edge on a monitor. */
@media (min-width:900px) {
  .activity-section {
    max-width:760px;
    margin-inline:auto;
    padding-inline:4px
  }

  /* Label and control were drifting 350px apart; keep the pair together and left-aligned. */
  .stats-field .stat-grid {
    grid-template-columns:180px minmax(0,320px);
    gap:10px 18px;
    justify-content:start
  }
}

/* Fieldset legends inside the profile sat indented from the copy they belong to. */
.me-section fieldset > legend,
.stats-field > legend {
  padding-inline:0;
  margin-left:0
}

.stats-field > legend {
  display:flex;
  flex-wrap:wrap;
  align-items:baseline;
  gap:4px 12px;
  width:100%
}

.stats-field legend span {
  flex:1 1 auto
}

/* Phone: a label above its control beats a squeezed two-column row. */
@media (max-width:560px) {
  .stats-field .stat-grid {
    grid-template-columns:minmax(0,1fr);
    gap:4px 0
  }

  .stats-field .stat-grid label {
    margin-top:10px
  }

  .stats-field .stat-toggle label {
    margin-top:0
  }
}

/* The type chips were tight against each other and their fieldset. */
.type-options {
  gap:8px
}

.me-section fieldset {
  margin-top:22px
}

/* Anonymous profiles stay visibly temporary everywhere they appear. The red is intentionally shared by the
   bubble, radar dot and grid tile so a guest is recognizable without exposing any account details. */








body.guest-mode .dot-origin {
  background:#ff5f6d;
  box-shadow:0 0 0 4px #ff5f6d33,0 0 12px #ff5f6d,0 0 24px #ff5f6d88
}

.guest-radar-bar,
.guest-profile-note {
  margin:10px 14px;
  padding:10px 12px;
  border:1px solid #ff5f6d66;
  border-radius:14px;
  background:#ff5f6d12;
  color:#ffdadd
}


.guest-radar-bar i,.dot.guest {width:9px;height:9px;border-radius:50%;background:#ff5f6d;box-shadow:0 0 10px #ff5f6d}
.guest-auth-top {
  min-height:48px;
  margin:2px 0 14px;
  padding:8px 12px;
  display:flex;
  align-items:center;
  justify-content:flex-end;
  gap:10px;
  border:1px solid #ff5f6d55;
  border-radius:14px;
  background:#ff5f6d0d;
  font-size:13px
}
.guest-auth-top span {margin-right:auto;color:var(--muted)}
.guest-auth-top a,.guest-auth-top button {min-height:32px;padding:6px 9px;border-radius:10px;color:#ffdadd;font-weight:750;text-decoration:none}
.guest-auth-top button {border:1px solid #ff5f6d66;background:#ff5f6d18}
.guest-profile-note {display:grid;gap:3px}
.guest-profile-note span,.guest-connect-note p,.guest-entry-note {color:var(--muted);font-size:13px;line-height:1.45}
.guest-keep-button {width:calc(100% - 28px);margin:16px 14px}
.guest-account-link,.guest-signin-link {display:block;text-align:center;margin-top:12px}
.guest-entry {display:block;margin-top:14px;text-align:center}
.guest-entry-note {margin:8px 0 0;text-align:center}


.guest-onboarding {max-width:620px;margin:0 auto;padding-bottom:32px}
.guest-onboarding h1 {margin:8px 14px 4px}
.guest-onboarding form {display:grid;gap:14px;margin:18px 14px 0}
.guest-onboarding fieldset {margin:0;padding:14px;border:1px solid #31495a;border-radius:16px;background:#11212d}
.guest-onboarding fieldset>legend {padding:0 7px;font-size:16px;font-weight:800;color:#f5f9fb}
.guest-onboarding .field-note {margin:0 0 10px;color:var(--muted);font-size:12px}
.guest-avatar-options {grid-template-columns:repeat(5,minmax(0,1fr));gap:16px 8px}
.guest-avatar-options .avatar {background:#1a303f}
.guest-photo-choice>input:checked+#guest-photo-preview {outline:2px solid var(--ice);outline-offset:3px}
#guest-photo-preview {display:grid;place-items:center;width:48px;height:48px;border-radius:50%;background:#1a303f;color:var(--ice);font-size:22px;overflow:hidden}
#guest-photo-preview>.avatar {width:100%;height:100%}
.guest-photo-upload {display:flex;width:max-content;margin:8px auto 0}
.guest-onboarding select {width:100%;min-height:48px}
.guest-consent {display:grid;gap:10px;padding:14px;border:1px solid #4b6879;border-radius:16px;background:#172936}
.guest-consent label {display:grid;grid-template-columns:24px 1fr;align-items:start;gap:10px;line-height:1.4}
.guest-consent input {width:20px;height:20px;margin:1px 0 0;accent-color:#8fe7ff}
.guest-consent a {color:#8fe7ff;text-underline-offset:2px}
.guest-consent p {margin:2px 0 0;color:var(--muted);font-size:12px;line-height:1.45}
.guest-onboarding form>.primary {width:100%;min-height:52px;font-size:16px}
@media(max-width:360px){.guest-avatar-options {grid-template-columns:repeat(4,minmax(0,1fr))}}


/* Anonymous mode: a small top-right chip instead of the red bar. Tapping it opens the sign-in / create-account box. */
.anon-chip {
  position:absolute;
  top:6px;
  right:12px;
  z-index:8;
  display:flex;
  align-items:center;
  gap:7px;
  padding:6px 10px;
  border:1px solid #ff5f6d55;
  border-radius:12px;
  background:#0a1622cc;
  color:#ffdadd;
  font-size:11px;
  font-weight:700;
  line-height:1.15;
  text-align:right;
  cursor:pointer
}
.anon-chip i {width:8px;height:8px;border-radius:50%;background:#ff5f6d;box-shadow:0 0 8px #ff5f6d;flex:none}
.anon-chip span {display:block}
.discovery-bar {position:relative;min-height:36px}
/* Inside the full filters bar (anonymous parity, John 2026-09-24): a plain inline chip, not the corner badge. */
.anon-chip.compact {position:static;flex:none;padding:6px 8px;gap:5px;font-size:13px;min-height:36px}

/* Sheets never take the whole screen: the radar and map stay visible behind them, unchanged. */
dialog {max-height:72dvh;overflow-y:auto}
dialog::backdrop {background:#060e1766;backdrop-filter:none}
dialog.compact-modal {inset:50% 0 auto;transform:translateY(-50%);width:min(92%,380px);border-radius:22px;border-bottom:1px solid #3c505f;padding-bottom:22px}

/* ---- iPhone chaos/polish pass (Codex review, 2026-09-20) -----------------------------------------
   Root cause of three separate findings: iOS zooms the page whenever a focused field is under 16 px,
   and that zoom sticks. The header then no longer fits, the help control slides off the right edge,
   and every later swipe pans the whole document instead of moving a row. Every text field holds at
   16 px, and nothing is allowed to make the document wider than the screen. */
input:not([type=radio]):not([type=checkbox]):not([type=hidden]),
select,
textarea,
.stats-field .stat-grid select,
.stats-field .stat-grid input[type=number],
.stats-field .stat-grid input[type=date],
.stats-field select,
#intention-duration,
#guest-position {
  font-size:16px
}

/* The stats pickers were 23 pt tall: unusable with a thumb. */
.stats-field .stat-grid select,
.stats-field .stat-grid input[type=number],
.stats-field .stat-grid input[type=date],
.stats-field select,
#intention-duration,
#guest-position {
  min-height:44px
}

html,body {
  overflow-x:hidden;
  max-width:100%
}

.swipe-row {
  touch-action:pan-y
}

/* A 44 pt target around the 28 pt help circle, without growing the circle. */
.help-button {
  position:relative
}

.help-button::after {
  content:"";
  position:absolute;
  inset:-8px
}

/* The live count read as disabled metadata next to Radar/Grid. */
.online-now {
  font-size:13px;
  color:#c1ced7
}

/* Emoji alone told nobody what they were choosing: every interest carries its word underneath the
   tile. The tile styling lives on .hobby-chip span, so the label has to opt out of it. */
.hobby-grid {
  display:flex;
  flex-wrap:wrap;
  gap:10px 8px
}

.hobby-chip {
  display:flex;
  flex-direction:column;
  align-items:center;
  gap:4px;
  width:76px
}

.hobby-grid .hobby-chip .hobby-label {
  display:block;
  width:100%;
  height:auto;
  min-height:0;
  padding:0;
  border:0;
  border-radius:0;
  background:none;
  box-shadow:none;
  transform:none;
  filter:none;
  opacity:1;
  font-size:12px;
  line-height:1.2;
  color:var(--muted);
  text-align:center;
  overflow-wrap:anywhere
}

.hobby-grid .hobby-chip input:checked~.hobby-label {
  color:var(--ink)
}

/* The hover/focus tooltip repeated a label that is now always visible. */
.hobby-chip[data-tip]::after,
.hobby-chip:has(input:focus-visible)::after {
  content:none
}

/* The same on a profile: each interest has its word under the emoji (John, 2026-09-27, T4). */
.hobby-item {
  display:flex;
  flex-direction:column;
  align-items:center;
  gap:3px;
  width:60px;
  user-select:none;
  -webkit-user-select:none
}

.hobby-item .hobby {
  cursor:default
}

.hobby-item .hobby:hover {
  transform:none
}

.hobby-word {
  width:100%;
  font-size:11px;
  line-height:1.2;
  color:var(--muted);
  text-align:center;
  overflow-wrap:anywhere
}

/* The one-time "He’ll see that you looked." on a first look (T1), under his name. */
.look-note {
  text-align:center
}

/* Helper copy ("Expires automatically", "Choose what helps conversations feel good") was floated right
   and landed on top of the first row of controls. A wrapping flex row keeps it beside the legend when
   it fits and drops it to its own line when it does not. */
.me-section legend,
.guest-onboarding legend {
  display:flex;
  flex-wrap:wrap;
  align-items:baseline;
  gap:4px 10px
}

.me-section legend span,
.guest-onboarding legend span {
  float:none;
  margin-left:auto;
  padding-top:0
}

/* App chrome is not a web page to select: long-pressing a label or legal copy popped iOS's
   Copy / Look Up menu over the product. Fields and message text stay selectable. */
.app-header,.bottom-nav,.me-section label,.me-section legend,.me-section h1,.me-section h2,
.settings-section label,.settings-section h1,.settings-section h2,.policy-page,
.acc-head,.settings-row,.hobby-chip,.type-chip,.avatar-choice,.status-pill,.anon-chip {
  -webkit-user-select:none;
  user-select:none;
  -webkit-touch-callout:none
}

.policy-page a,.policy-page input,.policy-page textarea,
input,textarea,[contenteditable],.bubble[data-msg] .bubble-text {
  -webkit-user-select:auto;
  user-select:auto
}

/* The header row fits at 402 pt today, but it had no give: once the wordmark font loaded (or iOS's
   sticky zoom kicked in) the help control was pushed off the edge. Every part can now shrink. */
.app-header {
  gap:10px;
  min-width:0
}

.app-header .wordmark {
  flex:0 1 auto;
  min-width:0
}

.app-header .wordmark img {
  max-width:100%;
  height:auto
}

/* Shrink order on a narrow phone: the wordmark gives way first, then the live count. The Radar/Grid
   switch and the help control keep their size, because they are what you tap. */
.header-discovery {
  min-width:0;
  flex:0 1 auto
}

.header-discovery .segmented {
  flex:0 0 auto
}

.online-now {
  flex:0 1 auto;
  white-space:nowrap;
  overflow:hidden;
  text-overflow:ellipsis
}

.help-button {
  flex:0 0 auto
}

@media (max-width:560px) {
  .app-header .wordmark img {
    max-height:28px;
    width:auto
  }
}

/* Session ended: a blocking recovery card instead of silently swapping the page for the entry form. */
.session-ended {
  position:fixed;
  left:16px;
  right:16px;
  bottom:calc(84px + env(safe-area-inset-bottom));
  z-index:30000;
  display:flex;
  flex-direction:column;
  gap:6px;
  padding:16px;
  border-radius:18px;
  background:#0d1b27;
  border:1px solid #2c4252;
  box-shadow:0 18px 44px #000a
}

.session-ended strong {
  font-size:16px
}

.session-ended span {
  color:var(--muted);
  font-size:13px;
  line-height:1.35
}

.session-ended-actions {
  display:flex;
  align-items:center;
  gap:12px;
  margin-top:6px
}

.session-ended-actions .primary {
  flex:1 1 auto;
  min-height:44px
}

.session-ended-actions .text-button {
  min-height:44px
}

/* ---- Intention on Nearby (STICKY-PLAN item 3, 2026-09-21) ---------------------------------------
   Sits above the Radar/Grid switch: what you are open to, how long it lasts, and who else is. */
/* One scrolling row: vertical space above the radar is the most expensive space in the app. */
/* This row scrolled sideways with the scrollbar hidden, so on a 402 pt phone "Free tonight 3" sat at
   x=341 at 111 pt wide: 61 pt of it on screen, its label cut to "Free…", and nothing to say the row
   moved at all. Measured, matching Codex big-thumbs 6 almost exactly (2026-09-22). A hidden control with
   a truncated label is not a control, least of all for a man who cannot see well. It wraps now: every
   filter is on screen, readable and tappable, and the radar below recalculates its own height from the
   real measured chrome (outerH(), GAYDA-009), so a second line costs the bowl nothing it cannot absorb. */
.intention-bar {
  display:flex;
  flex-wrap:wrap;
  align-items:center;
  gap:8px;
  padding:0 16px 8px;
  position:relative;
  z-index:6
}

.intention-bar::-webkit-scrollbar {
  display:none
}

.intention-bar > * {
  flex:0 0 auto
}

.intent-chip {
  display:flex;
  align-items:center;
  gap:8px;
  min-height:44px;
  padding:0 14px;
  border-radius:22px;
  border:1px solid #2c4252;
  background:#0f2130cc;
  color:var(--muted);
  font-size:14px;
  font-weight:600;
  backdrop-filter:blur(6px)
}

.intent-chip i {
  width:8px;
  height:8px;
  border-radius:50%;
  background:#44607a
}

.intent-chip.set {
  color:var(--ink);
  border-color:#39dfff66;
  background:#12303dcc
}

.intent-chip.set i {
  background:#39dfff;
  box-shadow:0 0 10px #39dfffaa;
  animation:intent-breathe 2.4s ease-in-out infinite
}

.intent-chip small {
  color:var(--muted);
  font-weight:500;
  font-size:12px
}

@keyframes intent-breathe {
  0%,100% { opacity:1 }
  50% { opacity:.45 }
}

.intent-filters {
  display:flex;
  gap:6px
}

.intent-filter {
  min-height:44px;
  padding:0 12px;
  border-radius:22px;
  border:1px solid #24394799;
  background:#0b1a26aa;
  color:var(--muted);
  font-size:13px
}

.intent-filter b {
  color:var(--ink);
  font-weight:700
}

.intent-filter.on {
  border-color:#39dfff66;
  color:var(--ink);
  background:#12303dcc
}

.intent-pulse {
  margin:0;
  padding-right:8px;
  white-space:nowrap;
  font-size:13px;
  color:#8fd3ff
}

.grid-intent {
  display:inline-block;
  padding:2px 8px;
  border-radius:12px;
  background:#12303dee;
  border:1px solid #39dfff55;
  color:#d7f4ff;
  font-size:11px;
  font-weight:600
}

.intention-sheet .intention-options {
  display:flex;
  flex-wrap:wrap;
  gap:8px;
  margin-bottom:14px
}

.intention-sheet .intention-choice {
  min-height:44px;
  padding:0 16px;
  border-radius:22px;
  border:1px solid #2c4252;
  background:#0f2130;
  color:var(--muted);
  font-size:15px
}

.intention-sheet .intention-choice.active {
  border-color:#39dfff;
  background:#12303d;
  color:var(--ink)
}

.intention-sheet .intention-controls {
  display:flex;
  align-items:center;
  gap:10px;
  margin-bottom:10px
}

.intention-sheet .intention-controls select {
  flex:1 1 auto
}





/* ---- "Your move" on Activity (STICKY-PLAN item 2, 2026-09-21) -----------------------------------
   The top of Activity is now what is waiting on you, not who looked at you. */
.your-move {
  margin-bottom:18px
}

.your-move .list-heading span {
  background:#39dfff22;
  border:1px solid #39dfff55;
  color:#d7f4ff
}

.move-row {
  border-left:2px solid #39dfff55;
  border-radius:0 14px 14px 0;
  background:linear-gradient(90deg,#12303d55,transparent 60%)
}

.move-row .activity-target small {
  color:#bcd6e4
}

.move-reveal {
  border-left-color:#ffe45c99;
  background:linear-gradient(90deg,#3a341455,transparent 60%)
}

.move-request {
  border-left-color:#6fffa499;
  background:linear-gradient(90deg,#15351f55,transparent 60%)
}

/* Action buttons sit on one line at the end of the row instead of stretching to full width, which is
   what .primary does by default and what made Activity read like a stack of banners. */
.activity-actions {
  justify-content:flex-end;
  align-items:center
}

.activity-actions .activity-quick {
  width:auto;
  flex:0 0 auto;
  min-height:44px;
  padding:0 18px;
  font-size:14px
}

.activity-actions .activity-quick.text-button {
  padding:0 10px
}

/* Views collapse to one line per person. */
.looks-group {
  margin-top:8px;
  border-top:1px solid #23394799;
  padding-top:8px
}

.looks-group summary {
  display:flex;
  align-items:center;
  gap:8px;
  min-height:44px;
  cursor:pointer;
  color:var(--muted);
  font-size:14px;
  font-weight:600;
  list-style:none
}

.looks-group summary::-webkit-details-marker {
  display:none
}

.looks-group summary::after {
  content:"\203a";
  margin-left:auto;
  transform:rotate(90deg);
  transition:transform .2s
}

.looks-group[open] summary::after {
  transform:rotate(-90deg)
}

.looks-group summary span {
  padding:2px 8px;
  border-radius:12px;
  background:#16293799;
  color:var(--ink);
  font-size:12px
}

.look-row {
  width:100%;
  min-height:56px
}

.look-new {
  width:8px;
  height:8px;
  border-radius:50%;
  background:#39dfff;
  margin-left:auto;
  flex:0 0 auto
}

/* ---- Invites (STICKY-PLAN item 4 + John's own ask, 2026-09-21) ----------------------------------
   One link per friend, copy-paste only, three states you can watch and never a name. */
.invite-reward {
  padding:14px;
  border-radius:16px;
  border:1px solid #2c4252;
  background:linear-gradient(160deg,#12303d88,#0d1b27);
  margin-bottom:14px
}

.invite-reward-head {
  display:flex;
  align-items:baseline;
  justify-content:space-between;
  gap:10px;
  margin-bottom:10px
}

.invite-reward-head strong {
  font-size:15px
}

.invite-reward-head span {
  color:#39dfff;
  font-size:13px;
  font-weight:600;
  white-space:nowrap
}

.invite-reward small {
  display:block;
  margin-top:8px;
  color:var(--muted);
  font-size:12px;
  line-height:1.35
}

.invite-meter {
  height:8px;
  border-radius:4px;
  background:#16293799;
  overflow:hidden
}

.invite-meter i {
  display:block;
  height:100%;
  border-radius:4px;
  background:linear-gradient(90deg,#39dfff,#6fffa4);
  box-shadow:0 0 12px #39dfff66;
  transition:width .5s cubic-bezier(.2,.9,.3,1)
}

.invite-make {
  width:100%;
  min-height:48px
}

.invite-heading {
  display:flex;
  align-items:center;
  gap:8px;
  margin:18px 0 8px;
  font-size:14px;
  color:var(--muted)
}

.invite-heading span {
  padding:2px 8px;
  border-radius:12px;
  background:#16293799;
  color:var(--ink);
  font-size:12px
}

.invite-card {
  padding:12px;
  border-radius:14px;
  border:1px solid #23394799;
  background:#0d1b2799;
  margin-bottom:10px
}

.invite-card-head {
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:10px;
  margin-bottom:8px
}

.invite-state {
  padding:3px 10px;
  border-radius:12px;
  font-size:12px;
  font-weight:700;
  border:1px solid #2c4252;
  color:var(--muted)
}

.invite-waiting .invite-state {
  border-color:#8fd3ff55;
  color:#8fd3ff
}

.invite-opened .invite-state {
  border-color:#ffe45c66;
  color:#ffe45c
}

.invite-joined .invite-state {
  border-color:#6fffa466;
  color:#6fffa4
}

.invite-joined {
  border-color:#6fffa433;
  background:linear-gradient(120deg,#15351f55,#0d1b2799)
}

.invite-card-head time {
  color:var(--muted);
  font-size:12px
}

.invite-card .invite-link {
  display:block;
  margin-bottom:8px;
  padding:8px 10px;
  border-radius:10px;
  background:#07121c;
  color:#d7f4ff;
  font-size:12px;
  overflow-wrap:anywhere
}

.invite-card small {
  display:block;
  color:var(--muted);
  font-size:12px;
  line-height:1.35
}

.invite-card-actions {
  display:flex;
  align-items:center;
  gap:10px;
  margin-top:10px
}

.invite-card-actions .primary {
  flex:0 0 auto;
  width:auto;
  min-height:44px;
  padding:0 18px
}

.invite-past summary {
  min-height:44px;
  display:flex;
  align-items:center;
  gap:8px;
  color:var(--muted);
  font-size:14px;
  cursor:pointer
}

/* The invite page says what this is before it asks for anything. */
.invite-explainer {
  list-style:none;
  margin:14px 0 0;
  padding:0;
  display:flex;
  flex-direction:column;
  gap:10px
}

.invite-explainer li {
  padding:10px 12px;
  border-radius:12px;
  border:1px solid #23394799;
  background:#0d1b2799;
  color:var(--muted);
  font-size:13px;
  line-height:1.4
}

.invite-explainer b {
  display:block;
  color:var(--ink);
  font-weight:650;
  margin-bottom:2px
}




/* A new member met six screens of form with nothing saying what was actually required (Codex persona 2). */
.setup-minimum {
  margin:0 0 18px;
  padding:14px 16px;
  border-radius:16px;
  border:1px solid #39dfff33;
  background:linear-gradient(160deg,#12303d66,#0d1b27)
}

.setup-minimum strong {
  display:block;
  font-size:15px;
  margin-bottom:8px
}

.setup-minimum ol {
  margin:0;
  padding-left:20px;
  color:var(--ink);
  font-size:14px;
  line-height:1.5
}

.setup-minimum span {
  display:block;
  margin-top:8px;
  color:var(--muted);
  font-size:12px;
  line-height:1.4
}

.guest-auth-top small {
  flex:1 1 100%;
  margin-top:6px;
  color:var(--muted);
  font-size:12px;
  line-height:1.35
}




/* A 61-year-old aiming 8 pt low missed "Log in" twice: the row was 32 pt (Codex persona 3). */
.guest-auth-top {
  align-items:center;
  flex-wrap:wrap;
  gap:8px
}

.guest-auth-top a,
.guest-auth-top button {
  display:inline-flex;
  align-items:center;
  min-height:48px;
  padding:0 14px;
  border-radius:24px;
  border:1px solid #2c4252;
  background:#0f2130;
  font-weight:600
}

/* Live type counts in the picker (John, 2026-09-21). */
.type-chip .type-count {
  display:inline-flex;
  align-items:center;
  justify-content:center;
  min-width:20px;
  height:18px;
  margin-left:6px;
  padding:0 5px;
  border-radius:9px;
  background:#16293799;
  color:#8fd3ff;
  font-size:11px;
  font-style:normal;
  font-weight:700
}

.type-chip input:checked~span .type-count {
  background:#39dfff22;
  color:#d7f4ff
}

.type-live {
  margin:10px 0 0;
  color:var(--muted);
  font-size:12px;
  line-height:1.4;
  min-height:17px
}

.type-hint {
  margin:-4px 0 10px;
  color:var(--muted);
  font-size:12px;
  line-height:1.35
}

/* ---- Persona 3, the 61-year-old without his glasses (Codex, 2026-09-21) -------------------------
   Everything below was measured at 10-12 px or under 44 pt on a real device. He needs this copy to
   choose safely, and these targets to reach the safety actions at all. */
.chat-more,
.icon-button.chat-more {
  min-width:44px;
  min-height:44px
}

/* Consent rows were 24 pt with a 20 pt box: the gate he cannot skip was the hardest thing to hit. */
.guest-consent label {
  display:flex;
  align-items:flex-start;
  gap:12px;
  min-height:44px;
  padding:10px 0;
  font-size:14px;
  line-height:1.4
}

.guest-consent input[type=checkbox] {
  width:26px;
  height:26px;
  margin-top:1px;
  flex:0 0 auto
}

/* Task-essential copy at 10-12 px. Nothing here is decorative: it is what he reads to decide. */
.login-form label,
.auth-form label,
.policy-links,
.login-copy .connection-note,
.avatar-choice>span:last-child:not(.avatar),
.guest-avatar-options>label>span:last-child:not(.avatar),
.three-greetings button small,
.three-greetings .greeting-note,
.activity,
.bubble-time,
.date-divider,
.invite-state,
.invite-card small,
.invite-card-head time,
.invite-reward small,
.invite-left,
.type-hint,
.type-live {
  font-size:13px
}

.invite-card .invite-link {
  font-size:13px
}

/* .bubble-time is declared three times earlier: 10 px, 12 px, and `.msg-row > .bubble-time` at 10 px.
   The last one out-specifies a plain selector however late it loads, so this has to match its shape. */
.bubble-time,
.msg-row > .bubble-time {
  font-size:13px
}

/* Cold anonymous entry, after the tourist pass (Codex persona 4, 2026-09-21).
   The loudest thing on a stranger's first screen was Log in, and the way in sat under four optional
   fieldsets. Now: headline, two boxes, button. Everything else folds away, and Log in is a footnote. */
.guest-onboarding.first-entry .guest-auth-top{
  order:99; margin:24px 0 0; padding:0; border:0; background:none;
  display:block; text-align:center; font-size:13px; color:var(--muted,#8b93a7);
}
.guest-onboarding.first-entry .guest-auth-top a{
  font-size:13px; font-weight:600; padding:0; border:0; background:none; color:var(--link,#6db3ff);
  margin-left:6px;
}
.guest-onboarding.first-entry .guest-auth-top small{
  display:block; margin-top:6px; font-size:12px; opacity:.75;
}
.guest-onboarding.first-entry h1{ margin-top:8px }
.guest-onboarding.first-entry .guest-consent{ margin-bottom:14px }
.guest-onboarding.first-entry button.primary[type="submit"]{ width:100%; margin-top:4px }

.guest-optional{ margin-top:22px; border-top:1px solid var(--hairline,#232a3a); padding-top:14px }
.guest-optional > summary{
  list-style:none; cursor:pointer; min-height:44px; display:flex; align-items:center; gap:8px;
  font-weight:600; font-size:15px;
}
.guest-optional > summary::-webkit-details-marker{ display:none }
.guest-optional > summary::after{ content:'›'; margin-left:auto; font-size:20px; opacity:.6; transition:transform .15s }
.guest-optional[open] > summary::after{ transform:rotate(90deg) }
.guest-optional > summary .hint{ font-weight:400; font-size:13px; opacity:.7 }

/* User text is user text, and must not reorder itself against the app's own chrome.
   Sending "مرحبا بالعالم שלום" pulled a toast and the pull-to-refresh label into the message bubble
   (Codex chaos monkey, 2026-09-21). `plaintext` gives each run its own direction and isolates it, so a
   right-to-left message lays out right-to-left inside its bubble and nowhere else. */
.bubble,
.reply-preview,
.location-title,
.profile-name,
.chat-wait,
#toast { unicode-bidi: plaintext }
/* Names and previews sit inline next to app copy: isolate them without changing the line's own direction. */
.msg-reaction,
.activity-line span,
.your-move-card strong,
.chat-list-head span { unicode-bidi: isolate }

/* Nothing under the session-ended card keeps taking taps. The invite sheet went on accepting them and
   minted a second link once the server came back (Codex chaos monkey, 2026-09-21). */
body.session-over main,
body.session-over nav,
body.session-over dialog { pointer-events: none }
body.session-over .session-ended { pointer-events: auto }

/* ============================================================================
   THE SIGNAL SYSTEM — one place, four channels, nothing outranks anything.
   John, 2026-09-21: "types and statuses are confusing and inconsistent across
   the map and grid. We need a better system."

   They were one winner-takes-all token spread over four CSS files, so each
   surface picked a different winner. A grid tile's border said "linked" while
   the dot beside it said "online". A man who was anonymous lost every other
   thing about him to red.

     COLOUR  is he here now          .is-here / .is-away      --presence
     RING    do we know each other   .is-linked               yellow, always
     EDGE    is this a real account  .is-temporary            faded, never a colour
     CHIP    what is he open to      .grid-intent/.arc-intent/.row-intent

   A man can be all four at once and you can see all four at once. Add a
   surface by consuming --presence; never by inventing a fifth colour.
   ========================================================================== */
.is-here  { --presence:#6fffa4; --presence-soft:#37ff8b55; --presence-dim:#37ff8b40 }
.is-away  { --presence:#9fb0bb; --presence-soft:#9fb0bb55; --presence-dim:#9fb0bb26 }
:root     { --linked-ring:#ffe45c }

/* Radar bubble */
.arc-profile .avatar{
  border:3px solid var(--presence,#9fb0bb);
  box-shadow:0 0 0 1px var(--presence-soft),0 0 16px var(--presence-dim);
}
.arc-profile.is-linked .avatar{ box-shadow:0 0 0 3px var(--linked-ring),0 0 18px var(--presence-dim) }
.arc-profile.is-temporary .avatar{ border-style:dashed; opacity:.92 }

/* Radar distance dot */
.distance-dot>span{
  background:var(--presence,#9fb0bb);
  box-shadow:0 0 calc(3px + var(--echo,0)*9px) var(--presence-soft);
}
.distance-dot.is-linked>span{ box-shadow:0 0 0 1.5px var(--linked-ring),0 0 calc(3px + var(--echo,0)*9px) var(--presence-soft) }
.distance-dot.is-temporary>span{ opacity:.75 }

/* Grid tile */
.grid-profile{ box-shadow:inset 0 0 0 2px var(--presence,#9fb0bb),0 0 12px var(--presence-dim) }
.grid-profile.is-linked{ box-shadow:inset 0 0 0 2px var(--presence),inset 0 0 0 5px var(--linked-ring),0 0 14px var(--presence-dim) }
.grid-profile.is-temporary{ box-shadow:inset 0 0 0 2px var(--presence);opacity:.97 }

/* The presence dot. One mark, one meaning, on every surface that has room for it.
   The grid tile used to carry a second, contradicting three-way dot in this corner. */
.presence-dot{
  position:absolute; top:8px; right:8px; width:10px; height:10px; border-radius:50%;
  background:var(--presence,#9fb0bb); border:2px solid #0a1622;
  box-shadow:0 0 6px var(--presence-soft);
}
.is-away .presence-dot{ background:transparent; border-color:var(--presence); box-shadow:none }
.person-row{ position:relative }
.person-row .presence-dot{ position:static; margin-left:8px; flex:0 0 auto }

/* Intention: the only thing allowed to be a word, and it appears everywhere now.
   It is the signal that expires, so it never hides behind a colour. */
.arc-intent,.row-intent{
  font-size:11px; font-weight:600; letter-spacing:.01em; white-space:nowrap;
  padding:2px 7px; border-radius:999px;
  background:#12304a; color:#bfe4ff; border:1px solid #23557f;
}

.row-end{ display:flex; align-items:center; gap:6px }

/* Bubble tabs: the two things worth wearing on a radar bubble.
   John, 2026-09-21: "can the annotating things on the bubble look cool though?
   like rounded tabs with curved text or something?" */

/* The intention, set on a real arc under the circle. Not a flat pill pretending. */
.arc-curve{
  position:absolute; inset:-9%; width:118%; height:118%;
  pointer-events:none; overflow:visible; z-index:3;
}
.arc-curve text{
  fill:#cfe9ff;
  font-size:9.5px; font-weight:700; letter-spacing:.14em;
  text-transform:uppercase;
  paint-order:stroke; stroke:#06121c; stroke-width:2.6px; stroke-linejoin:round;
}
.is-here .arc-curve text{ fill:#dffbe9 }

/* The chat tab. White says the thread is read; yellow says it is not. */
.chat-tab{
  position:absolute; top:-4%; right:-8%; z-index:4;
  display:flex; align-items:center; gap:2px;
  min-height:18px; padding:2px 6px 2px 5px;
  border-radius:10px 10px 10px 3px;      /* the square-ish corner is the tail into the bubble */
  background:#f2f7fb; color:#0a1622;
  border:1.5px solid #06121c;
  box-shadow:0 2px 6px #00000066;
  pointer-events:none;
}
.chat-tab svg{ width:11px; height:11px; fill:none; stroke:currentColor; stroke-width:2; stroke-linejoin:round }
.chat-tab b{ font-size:10px; font-weight:800; line-height:1; letter-spacing:-.02em }
.chat-tab.has-unread{
  background:var(--linked-ring,#ffe45c);
  box-shadow:0 2px 8px #ffd93a55,0 2px 6px #00000066;
}
/* Small bubbles at the back of the stack cannot carry either legibly. */
.arc-profile[style*="--bubble-size: 3"] .arc-curve,
.arc-profile[style*="--bubble-size: 2"] .arc-curve,
.arc-profile[style*="--bubble-size: 1"] .arc-curve{ display:none }
.stack-behind .arc-curve,.stack-behind .chat-tab{ display:none }

/* The intention sheet says what each phrase costs you in time, before you pick it. */
.intention-choice small{ display:block; margin-top:2px; font-size:11px; font-weight:400; opacity:.72 }
.intention-window{ display:block; margin-top:6px; font-size:12px; opacity:.75; line-height:1.35 }
.intention-controls[data-for="Free tonight"] label,
.intention-controls[data-for="Free tonight"] select{ display:none }

/* Grid tiles speak the same four words as the bubbles (John, 2026-09-21: "need to simplify the grid to be
   similar too"). The tile used to carry seven marks, two of which repeated what the ring and edge already
   said in colour, and one of which flatly disagreed with the border beside it. */
.grid-profile .chat-tab{ top:6px; left:6px; right:auto }
.grid-profile .presence-dot{ top:8px; right:8px }
.grid-new{ top:6px; right:24px }


/* The picture is the thing you are looking at. Nothing sits on it.
   John, 2026-09-21: "i think i'd rather not overlap the pictures in the grid with too much...
   perhaps the decorative stuff could be attached below". So the signals hang in a strip under it. */
.grid-profile{
  aspect-ratio:auto;
  flex-direction:column;
  align-items:stretch;
}
.grid-profile>.grid-image{ flex:0 0 auto }
.grid-overlay{
  position:static; inset:auto;
  background:#0c1b28;
  padding:6px 7px 7px;
  border-top:1px solid #1d3242;
  display:block;
}
.grid-line{
  display:flex; align-items:center; gap:5px; min-height:18px;
}
.grid-line .grid-name{
  flex:1 1 auto; min-width:0; font-size:12px; font-weight:600; text-shadow:none;
}
/* In the strip these are in the flow, not pinned to a corner of the photo. */
.grid-profile .presence-dot,
.grid-profile .chat-tab,
.grid-profile .grid-mutual,
.grid-profile .grid-star{ position:static; top:auto; right:auto; left:auto; flex:0 0 auto }
.grid-profile .presence-dot{ width:9px; height:9px; border-width:0; box-shadow:none }
.is-away .grid-profile .presence-dot,.grid-profile.is-away .presence-dot{
  background:transparent; box-shadow:inset 0 0 0 2px var(--presence);
}
.grid-profile .chat-tab{ border-radius:9px 9px 9px 3px; padding:1px 5px; min-height:16px }
.grid-profile .grid-star{ font-size:12px; line-height:1 }
.grid-profile .grid-intent{
  display:inline-block; margin-top:4px; font-size:11px; padding:1px 7px;
}
.grid-profile .grid-new{ margin:4px 4px 0 0 }
.grid-profile .grid-meta{ margin-top:3px; font-size:11px; margin-right:32px }
/* The ring is on the tile, so it must not be clipped by the strip's own background. */
.grid-profile.is-linked{ box-shadow:inset 0 0 0 2px var(--presence),inset 0 0 0 4px var(--linked-ring) }

/* Fixes from looking at the strip on a real phone-sized viewport. */
.grid-profile .grid-meta{ margin-right:0 }
/* New + intention share a line and must wrap rather than truncate the chip. */
.grid-overlay{ display:flex; flex-direction:column; align-items:flex-start }
.grid-profile .grid-new,.grid-profile .grid-intent{ max-width:100%; margin-right:0 }
.grid-profile .grid-intent{ overflow:hidden; text-overflow:ellipsis; white-space:nowrap }
/* An inset ring is drawn behind the photo, which is flush to the tile edge. Presence takes the border and
   linked takes a halo outside the tile, so both are visible with a picture in the way. */
.grid-profile{ border:2px solid var(--presence,#9fb0bb); box-shadow:0 0 12px var(--presence-dim) }
.grid-profile.is-linked{ box-shadow:0 0 0 2px var(--linked-ring),0 0 12px var(--presence-dim) }
.grid-profile.is-temporary{ border-style:dashed; box-shadow:none }
.grid-profile.is-temporary.is-linked{ box-shadow:0 0 0 2px var(--linked-ring) }
/* Mutual is a real signal and was a near-invisible grey dot in the strip. */
.grid-profile .grid-mutual{ width:7px; height:7px; border-radius:50%; background:#ff6ad5; border:0 }

/* Seven neon frames at once is a lot of green. The dot carries presence at full strength; the tile edge
   says the same thing quietly, so a screen full of men reads as men rather than as a warning light. */
.is-here { --presence-edge:#2f7f5a }
.is-away { --presence-edge:#3c4c58 }
.grid-profile{ border-width:1.5px; border-color:var(--presence-edge,#3c4c58); box-shadow:none }
.grid-profile.is-linked{ box-shadow:0 0 0 1.5px var(--linked-ring) }
.grid-profile.is-temporary.is-linked{ box-shadow:0 0 0 1.5px var(--linked-ring) }
/* The strip I added on 2026-09-21 shipped at 9 and 10px, which is smaller than anything else in the app,
   and it cut "Anonymous nearby" to "Anonymous nearb" — a name clipped one letter short reads as a bug,
   not as a long name (Codex persona 1 F5/F14, 2026-09-22). The sizes are fixed where they are set, a
   few rules up, rather than overridden down here where the next reader would find two answers. */
/* Two lines rather than a truncation. The name is the one thing on the tile he is reading. */
.grid-profile .grid-name{
  white-space:normal; overflow:hidden; text-overflow:ellipsis;
  display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:2; line-clamp:2;
  line-height:1.2;
}
.grid-line{ align-items:flex-start }
.grid-profile .presence-dot{ margin-top:4px }
/* His age sits right after his name, when he shows it (John, 2026-09-27, U5). */
.grid-line .grid-name:has(+.grid-age){ flex-grow:0 }
.grid-age{ flex:1 0 auto; font-size:12px; line-height:1.2; color:#dbe6ee }

/* Measured on a 402 pt phone (tests/browser/touch_targets.py, matching Codex big-thumbs 11 almost to the
   point): Radar 59x28, Grid 52x28, Help 28x28, the Filters chip 15 pt tall, Starred 28. All under the
   44 pt a thumb needs, and this app is used one-handed in the dark.

   The hit area grows, the drawn control does not. That is deliberate: the radar sizes its own bowl from
   the measured height of this chrome (outerH(), GAYDA-009), and making these boxes physically taller
   would quietly take that space back off the men. A pseudo-element stretches the target vertically only
   — never sideways, because Radar at x=229 and Grid at x=289 are neighbours and overlapping targets
   would be a worse bug than small ones. */
.segmented button,
.app-header .help-button,
.discovery-filters .star-filter,
.discovery-filters .filters-chip,
.discovery-filters .reciprocal-filter{ position:relative }
.segmented button::after,
.app-header .help-button::after,
.discovery-filters .star-filter::after,
.discovery-filters .filters-chip::after,
.discovery-filters .reciprocal-filter::after{
  content:''; position:absolute; left:0; right:0;
  top:50%; height:44px; transform:translateY(-50%);
  /* It must sit ABOVE the row's own background, not behind it: at z-index:-1 the bar painted over it and
     Filters and Starred still measured 15 and 28 pt. A pseudo-element's hits belong to its own element,
     so raising it costs nothing and is what makes the target real. Vertical only, so it never reaches
     into a neighbour. */
  z-index:1;
}
/* The help button is a 28 pt circle with a single character in it; its label had nowhere to go. */
.app-header .help-button{ overflow:visible }

/* Filters and Starred could not be fixed that way, and the measurement is what said so: their row is
   36 px tall with overflow:auto, so it simply clips the 8 px the hit area needs — no pseudo-element can
   escape a clipping parent. They get real height instead. The row grows by 8 px and the radar takes 8 px
   less, which is the honest trade: a control a thumb cannot hit is not saving anyone that space. */
.discovery-filters{ min-height:44px; align-items:center }
.discovery-filters .star-filter,
.discovery-filters .reciprocal-filter,
.discovery-filters .filter-control,
.discovery-filters .filters-chip{ min-height:44px; display:inline-flex; align-items:center }

/* The birthday field says what it currently claims about him, because on a phone a stray tap can leave the
   picker holding a date he never chose and that field is the 19+ gate (F10, 2026-09-22). */
.birthdate-echo{
  grid-column:1 / -1;
  margin:-2px 0 2px; font-size:12px; color:#9fc4d8; opacity:.9;
}

/* A walking band under the distance, so "1.8 km WNW" becomes a decision (GAYDA-007). Quiet on purpose:
   it is a hint, never a figure, and nothing here implies a route. */
.walk-band{
  display:block; margin-top:3px;
  font-size:12px; color:#9fc4d8; opacity:.85;
}

/* Round-4 big-thumbs (2026-09-23): he reads without glasses, and measured the text he needs to act on at 8-12 px.
   Everything he chooses, reads a distance from or navigates by is now 13 px or more; badges and units are
   brought up too. Last in the cascade on purpose. */
.discovery-filters .reciprocal-filter,.discovery-filters .star-filter,.filter-control .filters-chip,.compass-field .here-pill{font-size:13px}
.grid-line .grid-name{font-size:14px}
.grid-overlay .grid-meta,.grid-overlay .grid-intent{font-size:13px}
.grid-overlay .grid-new{font-size:11px}
.range-readout .range-unit{font-size:11px}
.bottom-nav button{font-size:12px}
.bottom-nav button i{font-size:11px}
.acc-head small,.settings-row small{font-size:13px}
.login-form label,.login-foot,.login-policies,.login-policies a{font-size:13px}
/* First setup: "Save & go online" rides along at the bottom instead of waiting several screens down (round-4
   out-and-proud: the three required things were quick, finding the button was not). */
.profile-save-bar.first-run{position:sticky;bottom:calc(var(--nav-h,calc(69px + env(safe-area-inset-bottom))) + 8px);z-index:6;padding:10px 0 4px;background:linear-gradient(to bottom,transparent,var(--bg,#0a1622) 30%)}
.profile-save-bar.first-run .form-error:empty{display:none}
/* The locked-photos line is now the way in to Swap or show (round-4 out-and-proud). */
button.locked-photo-summary{width:100%;text-align:left;font:inherit;color:inherit;cursor:pointer}
/* Name, distance with direction, and online, over the foot of the profile's hero photo (round-4 tourist). */
.hero-photo{position:relative}
.hero-caption{position:absolute;left:0;right:0;bottom:0;display:grid;gap:2px;padding:28px 14px 11px;background:linear-gradient(to bottom,transparent,rgba(5,12,18,.86));color:#fff;pointer-events:none}
.hero-caption strong{font-size:19px;line-height:1.15}
.hero-caption span{font-size:14px;opacity:.92}
.filter-modal-status{gap:6px}
/* On a 375 px phone the count was cut to "7 o…" beside Radar/Grid. Narrow screens show a green dot and the number;
   the words come back when there is room. The number itself never shrinks. */
.online-now{flex:0 0 auto}
/* The count now says what it counts at every width ("4 here now"; a green dot and a bare number explained nothing to
   two testers, 2026-09-27). To make room the wordmark gives way, never the switch: shrinking the group slid Radar/Grid
   under the ?, and on a narrow phone the switch's buttons are a little tighter. */
.header-discovery{flex-shrink:0}
@media (max-width:420px){.header-discovery .segmented button{padding:0 9px;min-width:0}}
.login-form .birthdate-echo{display:block;font-size:13px;margin:-4px 0 10px;color:#b8c7d1}
/* An empty radar's buttons ("Show everyone", location retry) sat under the profile arc, which took the tap (round-5
   tourist: "Show everyone" could not be pressed). The empty state rises above the arc whenever it holds a button. */
.direction-stage>.direction-empty:has(button){opacity:1;z-index:40}
.direction-stage>.direction-empty button{position:relative;pointer-events:auto}
#setup-reminder .setup-dismiss{min-width:44px;min-height:44px}
.photo-note.said{color:#ffd27a;font-weight:600}
.sheet-bottom-close{display:block;width:100%;min-height:48px;margin:14px 0 4px;font-size:16px}
fieldset.needs-attention,.type-options.needs-attention,.guest-consent label.needs-attention{outline:2px solid #ffd27a;outline-offset:6px;border-radius:12px;transition:outline-color .3s}
/* The consent message sits under the box he missed, in the error colour (.guest-consent p would grey it out). */
.guest-consent p.consent-error{margin:-4px 0 0 34px;color:#ffc0b9;font-size:13px}

/* App health (owner only, 2026-09-24) */
.app-health .health-stats {display:grid;grid-template-columns:repeat(4,1fr);gap:8px;margin:4px 0 12px}
.app-health .health-stats p {margin:0;padding:10px 6px;border-radius:12px;background:#ffffff0d;text-align:center}
.app-health .health-stats b {display:block;font-size:22px}
.app-health .health-stats small {font-size:13px;opacity:.75}
.app-health .health-days {width:100%;border-collapse:collapse;font-size:14px;margin:8px 0 16px}
.app-health .health-days th,.app-health .health-days td {padding:5px 4px;text-align:right;border-bottom:1px solid #ffffff14}
.app-health .health-days th:first-child,.app-health .health-days td:first-child {text-align:left}
.app-health h3 span {opacity:.6;font-weight:400}
.app-health .health-error {padding:10px 0;border-bottom:1px solid #ffffff14;overflow-wrap:anywhere}
.app-health .health-error small {display:block;font-size:13px;opacity:.75;margin-top:3px}
.app-health .health-error pre {white-space:pre-wrap;font-size:12px;max-height:200px;overflow:auto}

/* Breathing room (2026-09-25): the birthday read-back sat on the date box's border on Create account. */
#birthdate-echo, .birthdate-echo { display: block; margin-top: 8px; line-height: 1.4 }
