/* sidewalk.fm web — layout and components on top of tokens.css (brand/DESIGN.md).
   The map is the ground: gradient under desaturated tiles at 65% = the three
   stops tinting the tiles at 35%. Everything else floats in glass. */

html, body { height: 100%; }
body { overflow: hidden; -webkit-font-smoothing: antialiased; }
#app { position: relative; height: 100%; }

/* ---- ground ---- */
#map { position: absolute; inset: 0; background: linear-gradient(135deg, var(--ground-a) 0%, var(--ground-b) 50%, var(--ground-c) 100%); }
[hidden] { display: none !important; }

/* ---- floating layer, 1200px max ---- */
#ui { position: absolute; inset: 0; z-index: 1000; max-width: 1200px; margin: 0 auto; pointer-events: none; }
#ui > * { pointer-events: auto; }
.icon { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; flex: none; }

/* shown only when the globe cannot start; the rest of the page carries on */
#map-notice { position: absolute; z-index: 2; top: 50%; left: 50%; transform: translate(-50%, -50%); max-width: min(360px, calc(100% - 48px)); margin: 0; padding: 14px 18px; box-sizing: border-box; text-align: center; line-height: 1.4; border-radius: var(--r-card); }
body.not-map #map-notice { display: none !important; }

/* top bar: search pill + the round location button, the app's Map toolbar */
#topbar { position: absolute; top: 16px; left: 50%; transform: translateX(-50%); z-index: 3; display: flex; align-items: center; gap: 8px; width: min(612px, calc(100% - 32px)); }
body.not-map #topbar { display: none; }
#search { flex: 1; min-width: 0; height: 48px; display: flex; align-items: center; gap: 8px; padding: 0 6px 0 18px; box-sizing: border-box; border-radius: var(--r-pill); color: var(--text-secondary); }
#locate { flex: none; }
#locate.denied { opacity: 0.45; }
#search .input { flex: 1; min-width: 0; height: 100%; padding: 0; border: 0; background: transparent; outline: none; box-shadow: none; }
#search .input::placeholder { color: var(--text-secondary); }
/* Sits under the field so the caret and the typed text stay on top of it. */
#search { position: relative; }
.field-hint {
  position: absolute; left: 46px; top: 50%; transform: translateY(-50%);
  color: var(--text-secondary); font: 400 16px var(--font);
  pointer-events: none; white-space: nowrap;
}
.field-hint strong { font-weight: 600; color: var(--text); }
#search.has-text .field-hint { display: none; }
#search .input::-webkit-search-cancel-button { display: none; }
#search:focus-within { color: var(--text); }
.btn-icon.small { width: 44px; height: 44px; background: transparent; border-color: transparent; }
.btn-icon.small .icon { width: 16px; height: 16px; }

/* bottom bar: the app's floating glass pill of four round buttons, all widths */
#bar { position: absolute; z-index: 3; bottom: max(16px, env(safe-area-inset-bottom)); left: 50%; transform: translateX(-50%); display: flex; gap: 10px; padding: 8px; border-radius: var(--r-pill); }
#sheet { z-index: 2; }
.view { z-index: 1; }
#bar .btn-icon .icon { width: 18px; height: 18px; }
/* The map tab carries the wordmark glyph, not a stroked symbol, and a mark
   needs more room than a line drawing to read at the same weight. iOS draws it
   at 26 in a 44pt button where its symbols sit at 18; this is that ratio. The
   id in `#bar .btn-icon .icon` outranks `.icon.glyph`, which is why the glyph
   was being squeezed to symbol size here and nowhere else. */
#bar .btn-icon .icon.glyph { width: 26px; height: 26px; }
#bar .btn-icon[aria-pressed="true"] { background: var(--accent-fill); color: var(--on-accent); }

/* views */
.view { position: absolute; padding: 16px; box-sizing: border-box; overflow: auto; overscroll-behavior: contain; }
.view-head { position: relative; min-height: 56px; display: flex; flex-direction: column; justify-content: center; text-align: center; margin: 0 0 12px; }
.view-head #refresh { position: absolute; right: 0; top: 6px; }
.view-head .title { margin: 0; }
.view-head .caption { margin: 4px 0 0; }
.view p { margin: 0 4px 12px; line-height: 1.5; }
.em { font-weight: 600; }

/* room cards in the list */
.card { display: flex; align-items: center; gap: 12px; padding: 16px; margin-bottom: 12px; background: var(--glass); border: 1px solid var(--hairline); border-radius: var(--r-card); box-shadow: var(--shadow); }
.card .avatar { position: relative; width: 40px; height: 40px; border-radius: var(--r-pill); display: grid; place-items: center; background: var(--glass-strong); border: 1px solid var(--hairline); flex: none; }
.card .avatar .dot { position: absolute; left: -1px; bottom: -1px; }
.card .body { flex: 1; min-width: 0; }
.card .name { font: 500 16px/1.25 var(--font); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.card .caption { margin: 2px 0 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.card .btn-icon { flex: none; }

/* download cards */
.dl-card { background: var(--glass); border: 1px solid var(--hairline); border-radius: var(--r-card); box-shadow: var(--shadow); padding: 16px; margin-bottom: 12px; }
.dl-card .dl-name { font: 500 16px/1.25 var(--font); margin: 0; }
.dl-card p { margin: 4px 0 0; }
.dl-card .dl-row { display: flex; align-items: center; gap: 10px; margin-top: 14px; }
.dl-card a.btn-pill { display: inline-flex; align-items: center; text-decoration: none; box-sizing: border-box; }
.dl-card .dl-row + .caption { margin-top: 12px; }
/* The download banner. It stands in the slot a control would have taken —
   Open a room on the Rooms tab, Go live under the code on Me — so moving
   between the web and the app finds the same thing in the same place. The
   button carries the Download view's own arrow. */
/* Until the module lands the mount is empty; an empty card would flash a
   bordered box in the slot. */
#rooms-banner:empty, #me-banner:empty { display: none; }
.app-banner .btn-pill { display: inline-flex; align-items: center; gap: 8px; }
.app-banner .btn-pill .icon { width: 20px; height: 20px; }
#rooms-banner { margin-top: 4px; }
#me-banner { text-align: left; }

#waitlist { display: flex; align-items: center; gap: 6px; height: 48px; padding: 0 2px 0 18px; box-sizing: border-box; border-radius: var(--r-pill); margin-top: 14px; }
#waitlist .input { flex: 1; min-width: 0; height: 100%; padding: 0; border: 0; background: transparent; outline: none; box-shadow: none; }
#waitlist .input::placeholder { color: var(--text-secondary); }
#waitlist-send[disabled] { opacity: 0.35; cursor: default; }

/* proof of life: the same pill-shaped field as the waitlist, and the tribe
   chip the app draws beside a name. `human` is a tribe, so it looks like one. */
#life-form { display: flex; align-items: center; gap: 6px; height: 48px; padding: 0 2px 0 18px; box-sizing: border-box; border-radius: var(--r-pill); margin-top: 14px; }
#life-form .input { flex: 1; min-width: 0; height: 100%; padding: 0; border: 0; background: transparent; outline: none; box-shadow: none; font: 400 12px/1.2 ui-monospace, SFMono-Regular, Menlo, monospace; }
#life-form .input::placeholder { color: var(--text-secondary); }
#life-register, #life-assert { display: inline-flex; align-items: center; gap: 8px; }
#life-register[disabled], #life-assert[disabled] { opacity: 0.35; cursor: default; }

/* An earned badge arriving. A chip that snaps into place does not read as a
   moment, and the moment is the whole of Proof of Life — nothing is unlocked
   by it. The badge springs in under a ring that opens once and fades.

   The resting style is the finished state, so a browser that refuses the
   animation still shows the badge. tokens.css turns every animation off under
   prefers-reduced-motion, which takes the ring with it. */
.badge-arrival { position: relative; display: inline-flex; }
.badge-arrival.arriving > .tribe { animation: badge-land 420ms cubic-bezier(0.2, 1.5, 0.4, 1) both; }
.badge-arrival.arriving::after {
  content: ""; position: absolute; inset: 0; border-radius: var(--r-pill);
  border: 2px solid var(--accent); pointer-events: none;
  animation: badge-ring 550ms ease-out both;
}
@keyframes badge-land {
  from { transform: scale(0.7); opacity: 0; }
  to { transform: scale(1); opacity: 1; }
}
@keyframes badge-ring {
  from { transform: scale(0.92); opacity: 0.7; }
  to { transform: scale(1.5); opacity: 0; }
}
.tribe { display: inline-flex; align-items: center; height: 24px; padding: 0 9px; border-radius: var(--r-pill); background: var(--glass); border: 1px solid var(--hairline); font: 500 12px/1 var(--font); }
/* One badge component for every tribe. A chosen tribe is its words on glass
   and takes a tap to remove it; an earned one carries its symbol and its tint
   and is not a button at all. */
.tribe { color: var(--text-secondary); gap: 4px; }
button.tribe { border-width: 1px; cursor: pointer; font: 500 12px/1 var(--font); }
.tribe.earned { font-weight: 600; }
.tribe.earned.accent { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, transparent); background: color-mix(in srgb, var(--accent) 10%, transparent); }
.tribe.earned.live { color: var(--live-ink); border-color: color-mix(in srgb, var(--live-ink) 45%, transparent); background: color-mix(in srgb, var(--live-ink) 10%, transparent); }
.tribe-icon { width: 11px; height: 11px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; flex: none; }
/* The row, and the Add tribe button under it. */
.tribe-row-wrap { display: flex; flex-wrap: wrap; gap: 6px; margin: 10px 0; }
.tribe-row-wrap:empty { display: none; }
#me-tribes-title { display: block; }
#me-add-tribe { align-self: flex-start; }

/* the tribe picker: a search field, a capped scrolling list, and Done */
.picker { border: 0; padding: 0; background: transparent; max-width: 420px; width: calc(100vw - 32px); }
.picker::backdrop { background: rgba(20, 16, 40, 0.32); backdrop-filter: blur(6px); }
.picker-body { display: flex; flex-direction: column; gap: 12px; padding: 20px; border-radius: var(--r-card); background: var(--glass-strong); backdrop-filter: blur(24px); border: 1px solid var(--hairline); box-shadow: var(--shadow); }
.picker-title { margin: 0; font: 500 22px/1.2 var(--font); letter-spacing: -0.3px; color: var(--text); }
.picker-list { display: flex; flex-direction: column; max-height: 240px; overflow-y: auto; overscroll-behavior: contain; }
.tribe-row { appearance: none; border: 0; background: transparent; text-align: left; padding: 10px 2px; font: 400 16px/1.25 var(--font); color: var(--text); cursor: pointer; min-height: 44px; box-sizing: border-box; }
/* What you typed leads the list in the accent colour, so inventing a tribe
   and choosing one already there are the same gesture. */
.tribe-row.accent { color: var(--accent); }
.picker-foot { display: flex; justify-content: flex-end; }
#waitlist-status { min-height: 1.2em; }

/* sheet */
#sheet { position: absolute; padding: 20px; box-sizing: border-box; overflow: auto; overscroll-behavior: contain; visibility: hidden; opacity: 0; transition: transform 240ms var(--ease), opacity 240ms var(--ease), visibility 0s linear 240ms; }
#sheet.open { visibility: visible; opacity: 1; transform: none; transition: transform 240ms var(--ease), opacity 240ms var(--ease); }
#sheet .handle { width: 36px; height: 4px; border-radius: var(--r-pill); background: var(--hairline); margin: -8px auto 12px; }
.sheet-head { display: flex; align-items: center; gap: 12px; min-height: 56px; }
.sheet-head .head-spacer { width: 44px; flex: none; }
#close { flex: none; }
#sheet .title { flex: 1; min-width: 0; margin: 0; text-align: center; overflow-wrap: anywhere; }
#host { margin: 4px 0 0; overflow-wrap: anywhere; }
#meta { display: flex; flex-wrap: wrap; gap: 8px 16px; margin: 16px 0 0; }
.meta-item { display: inline-flex; align-items: center; gap: 6px; font: 400 16px/1 var(--font); }
.meta-item .label { line-height: 1; }
.actions { display: flex; align-items: center; gap: 10px; margin: 20px 0 0; }
#listen { display: inline-flex; align-items: center; gap: 10px; }
/* The meter rides inside the live pill, so it is drawn in that pill's ink. */
#listen .meter { display: none; width: 40px; height: 4px; border-radius: var(--r-pill); background: color-mix(in srgb, var(--on-live) 22%, transparent); overflow: hidden; }
#listen .meter-fill { display: block; height: 100%; width: calc(var(--level, 0) * 100%); background: var(--on-live); border-radius: inherit; transition: width 80ms linear; }
#listen.live .meter { display: block; }
#status { margin: 12px 0 0; min-height: 1.4em; overflow-wrap: anywhere; }
#join { margin: 16px 0 0; }
.join-row { display: flex; align-items: center; gap: 10px; margin-top: 6px; }
#join-line { flex: 1; min-width: 0; font: 400 12px/1.4 var(--font); letter-spacing: 0.2px; color: var(--text-secondary); overflow-wrap: anywhere; }
#events { list-style: none; padding: 0; margin: 16px 0 0; display: flex; flex-direction: column; gap: 6px; }
#events li { background: var(--glass); border: 1px solid var(--hairline); border-radius: var(--r-card); padding: 10px 14px; font: 400 16px/1.35 var(--font); overflow-wrap: anywhere; }
#events li .caption { display: block; margin-bottom: 2px; }
#events li.own { background: color-mix(in srgb, var(--accent) 12%, transparent); border-color: transparent; }
#events li.system { background: transparent; border-color: transparent; padding: 2px 14px; color: var(--text-secondary); font-size: 12px; letter-spacing: 0.2px; }

/* composer */
#composer { position: sticky; bottom: 0; margin: 16px -8px -8px; padding: 8px; display: flex; flex-direction: column; gap: 8px; }
.input-row { display: flex; align-items: center; gap: 6px; height: 48px; padding: 0 2px 0 18px; box-sizing: border-box; border-radius: var(--r-pill); }
.input-row .input { flex: 1; min-width: 0; height: 100%; padding: 0; border: 0; background: transparent; outline: none; box-shadow: none; }
.input-row .input::placeholder { color: var(--text-secondary); }
#send { transition: opacity 120ms var(--ease), transform 120ms var(--ease); }
#send[disabled] { opacity: 0.35; cursor: default; }
.nick { padding: 12px 16px; border-radius: 24px; }
.nick-row { display: flex; gap: 8px; margin-top: 8px; }
.nick-row .input { flex: 1; min-width: 0; height: 44px; background: transparent; }

/* ---- phone stack ---- */
@media (max-width: 899px) {
  .view { left: 12px; right: 12px; top: 80px; bottom: 88px; }
  body.not-map .view { top: 12px; }
  #sheet { left: 0; right: 0; bottom: 0; max-height: 72%; border-radius: var(--r-card) var(--r-card) 0 0; padding-top: 16px; padding-bottom: 24px; transform: translateY(24px); }
  /* the composer owns the bottom edge while a room is open, as in the app */
  body.sheet-open #bar { display: none; }
  /* map attribution clears the floating bar */
  #map .maplibregl-ctrl-bottom-right, #map .maplibregl-ctrl-bottom-left { margin-bottom: 84px; }
}

/* ---- two pane: list pane on glass left, sheet on glass-strong right, 24px margins ---- */
@media (min-width: 900px) {
  #topbar { left: 24px; transform: none; width: min(612px, calc(100% - 472px)); }
  .view { left: 24px; top: 88px; bottom: 24px; width: 380px; }
  body.not-map .view { top: 24px; }
  #sheet { right: 24px; top: 24px; bottom: 24px; width: 400px; transform: translateX(24px); }
  #sheet .handle { display: none; }
}
#chat { display: flex; gap: 6px; margin-top: 8px; }
#chat #nick { width: 80px; }
#chat #text { flex: 1; min-width: 0; }

#rail .btn-icon .icon.glyph, .icon.glyph { width: 31px; height: 31px; background: currentColor; -webkit-mask: url(/glyph.svg) center / contain no-repeat; mask: url(/glyph.svg) center / contain no-repeat; display: block; }

/* ---- Me: the identity this browser holds ---- */
.me-code { text-align: center; }
#me-canvas { width: min(220px, 62%); height: auto; aspect-ratio: 1; display: block; margin: 4px auto 0; }
.me-flagname { font: 500 22px/1.2 var(--font); letter-spacing: -0.3px; margin: 10px 0 0; overflow-wrap: anywhere; }
.me-code .join-row { justify-content: center; margin-top: 8px; }
#me-address { font: 400 12px/1.4 var(--font); letter-spacing: 0.2px; color: var(--text-secondary); overflow-wrap: anywhere; }
#me-create-row { justify-content: center; }
#me-buddies .label { margin-top: 14px; }
.buddy-list { margin-top: 8px; }
.buddy-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; min-height: 44px; border-top: 1px solid var(--hairline); }
.buddy-row code { min-width: 0; overflow-wrap: anywhere; color: var(--text-secondary); font: 400 12px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace; }

#me .label { display: block; margin: 0 0 8px; }
#me-profile .label + .label, #me-profile .caption + .label { margin-top: 20px; }
.name-row { display: flex; gap: 8px; }
.name-row .input { min-width: 0; }
.name-row #me-name { flex: 1; }
/* The native picker gives the full country list and the keyboard; the face is
   what people see, because a select shows an option's whole text or none of it. */
.flag-picker { position: relative; flex: none; width: 64px; height: 48px; border-radius: var(--r-pill); background: var(--glass-strong); border: 1px solid var(--hairline); display: grid; place-items: center; }
.flag-face { font-size: 24px; line-height: 1; }
.flag-native { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; border: 0; cursor: pointer; font-size: 16px; }
.flag-picker:focus-within { outline: 2px solid var(--accent); outline-offset: 2px; }

.reserved { display: flex; align-items: center; gap: 6px; margin-top: 12px; font: 600 16px/1.2 var(--font); }
.reserved .seal { width: 18px; height: 18px; color: var(--accent); }
/* A room reservation owns its row. */
.btn-pill.reserve { font-size: 16px; font-weight: 600; height: 44px; padding: 0 18px; color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent); border-color: color-mix(in srgb, var(--accent) 28%, transparent); width: 100%; display: flex; align-items: center; justify-content: center; }
.dl-row > .btn-pill.reserve { flex: 1 1 100%; }
.btn-pill.danger { color: var(--on-danger); background: var(--danger-fill); border-color: transparent; }
/* The off state of a control that turns green when it goes live: Open a room,
   Go live. The stroke carries the token colour, the fill stays out of the way,
   and adding .live over it gives the filled broadcasting state from tokens.css. */
/* --live is the badge green: bright enough to fill with, too bright to write
   with on the light ground (1.97:1). --live-ink is the same signal darkened
   until it carries, and stays the bright value in dark. */
.btn-pill.outline { background: transparent; border: 1.5px solid var(--live-ink); color: var(--live-ink); font-weight: 600; }
.btn-pill[disabled] { opacity: 0.4; cursor: default; }
.handoff { margin-top: 12px; padding: 14px; border-radius: var(--r-inner); background: var(--glass-strong); border: 1px solid var(--hairline); }
.handoff .join-row { margin-top: 10px; }
.handoff code { flex: 1; min-width: 0; font: 400 12px/1.4 var(--font); letter-spacing: 0.2px; overflow-wrap: anywhere; }
.handoff .label { flex: none; margin: 0; }


.switch-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; min-height: 44px; font: 400 16px/1.3 var(--font); }
.switch-row + .switch-row { border-top: 1px solid var(--hairline); margin-top: 4px; padding-top: 4px; }
.switch { flex: none; position: relative; display: inline-block; width: 50px; height: 30px; cursor: pointer; }
.switch input { position: absolute; opacity: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; }
.switch .track { position: absolute; inset: 0; border-radius: var(--r-pill); background: var(--hairline); border: 1px solid var(--hairline); transition: background 160ms var(--ease); }
.switch .track::after { content: ""; position: absolute; top: 3px; left: 3px; width: 22px; height: 22px; border-radius: var(--r-pill); background: var(--on-accent); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25); transition: transform 160ms var(--ease); }
.switch input:checked + .track { background: var(--accent-fill); border-color: transparent; }
.switch input:checked + .track::after { transform: translateX(20px); }
.switch input:focus-visible + .track { outline: 2px solid var(--accent); outline-offset: 2px; }

.row-button { display: flex; align-items: center; justify-content: space-between; width: 100%; min-height: 44px; padding: 0; border: 0; background: transparent; color: var(--text); font: 400 16px/1.3 var(--font); cursor: pointer; }
.row-button .chev { color: var(--text-secondary); transition: transform 160ms var(--ease); }
#me-advanced.open .row-button .chev { transform: rotate(180deg); }
#me-advanced-body { margin-top: 12px; }
#me-advanced-body .dl-row { flex-wrap: wrap; }
.input.key { width: 100%; box-sizing: border-box; height: auto; padding: 12px 16px; border-radius: var(--r-inner); font: 400 12px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace; resize: vertical; overflow-wrap: anywhere; margin-bottom: 12px; }
/* A destructive step asks in the panel. A native confirm() would sit outside
   the page's own surface and block it. */
.confirm { margin: 12px 0; padding: 14px 16px; border-radius: var(--r-inner); background: color-mix(in srgb, var(--danger) 10%, transparent); border: 1px solid color-mix(in srgb, var(--danger) 30%, transparent); }
.confirm p { margin: 0; line-height: 1.45; }
.confirm .dl-row { margin-top: 12px; }
#me-advanced-status { min-height: 1.2em; }

.lockup { width: min(260px, 70%); display: block; margin: 4px auto 16px; }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) .lockup { filter: invert(1); } }
:root[data-theme="dark"] .lockup { filter: invert(1); }

/* The one warning a browser identity needs, so it reads before the body does:
   a browser key lives in site data and does not survive it being cleared.
   Sized as a heading rather than a card title, and in the one red the product
   uses. */
.me-truth-heading {
  font: 700 22px/1.2 var(--font);
  color: var(--on-air);
  margin: 0 0 6px;
  text-wrap: balance;
}
