:root {
  --ink: #f3efe7; --dim: #a9a295; --line: #2a2723;
  --bg: #141310; --card: #1c1a16; --warm: #e0b15f; --live: #7fcf8f;
}
* { box-sizing: border-box; }
body {
  margin: 0; background: var(--bg); color: var(--ink);
  font: 16px/1.55 ui-serif, Newsreader, Georgia, serif;
  -webkit-font-smoothing: antialiased;
}
#shell { max-width: 760px; margin: 0 auto; min-height: 100dvh; display: flex; flex-direction: column; }

#bar {
  display: flex; align-items: center; gap: 1rem; flex-wrap: wrap;
  padding: .9rem 1rem; border-bottom: 1px solid var(--line); position: sticky; top: 0;
  background: color-mix(in oklab, var(--bg) 92%, transparent); backdrop-filter: blur(8px); z-index: 5;
}
.brand { display: flex; align-items: center; gap: .5rem; color: var(--ink); text-decoration: none; font-weight: 600; flex: none; }
.brand img { border-radius: 6px; }
/* One row, always. Seven tabs do not fit across a phone, and a nav that wraps
   to three rows eats the map it is stuck above — so the strip scrolls sideways
   instead. `min-width: 0` is what lets it: a flex item will not shrink below
   its own content without it, which is exactly how it used to end up wrapping.
   The scrollbar is hidden because a permanent bar under a nav reads as broken;
   the fade at the right edge, added at phone widths below, is what says there
   is more to the right. */
#tabs {
  display: flex; gap: .25rem; margin-left: auto; flex-wrap: nowrap;
  min-width: 0; overflow-x: auto; overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch; scrollbar-width: none;
}
#tabs::-webkit-scrollbar { display: none; }
.tab {
  background: none; border: 0; color: var(--dim); font: inherit; font-size: .95rem;
  padding: .35rem .7rem; border-radius: 999px; cursor: pointer;
  flex: none; white-space: nowrap;
}
.tab.on { color: var(--bg); background: var(--warm); }
.tab:hover:not(.on) { color: var(--ink); }
/* The two standing links, kept out of the tab strip. `.tab` is not
   tag-qualified, so both get the pill for free and these rules only undo the
   underline and set a mark on the text's baseline. */
#extras { display: flex; align-items: center; gap: .25rem; flex: none; }
.tab.mark { display: inline-flex; align-items: center; gap: .35rem; text-decoration: none; }
.tab.mark svg { width: 14px; height: 14px; fill: currentColor; display: block; flex: none; }
/* Accent on the invite alone: it is the ask, and it is the one that leaves
   the site. Contact stays the colour of the app, because it opens a sheet. */
a.tab.join { color: var(--warm); }
a.tab.join:hover { color: var(--ink); }
/* Discord's mark sits in a narrow band inside its 24x24 box, so it needs a
   little more width than the pictograms to read at the same weight. */
a.tab.join svg { width: 16px; height: 16px; }

#view { padding: 1rem; flex: 1; }
.empty, .sub { color: var(--dim); }
.warn { color: var(--warm); }
h1 { font-size: 1.9rem; margin: .2rem 0 .4rem; }
h2 { font-size: 1.3rem; margin: 1rem 0 .3rem; }

.feed { display: flex; flex-direction: column; gap: .7rem; }
.said { background: var(--card); border: 1px solid var(--line); border-radius: 12px; padding: .75rem .9rem; }
.said-head { display: flex; gap: .55rem; align-items: baseline; flex-wrap: wrap; font-size: .85rem; }
.who { color: var(--warm); font-weight: 600; }
.where, .when { color: var(--dim); }
.when { margin-left: auto; }
.body { margin: .35rem 0 0; }
.did { display: flex; gap: .6rem; color: var(--dim); font-style: italic; font-size: .92rem; padding: 0 .3rem; }
.did .when { margin-left: auto; font-style: normal; }

.rows { display: flex; flex-direction: column; }
.row {
  display: flex; gap: 1rem; align-items: flex-start; justify-content: space-between;
  padding: .8rem .3rem; border-bottom: 1px solid var(--line);
}
.row.thing { cursor: pointer; }
.row.thing:hover { background: var(--card); }
.name { font-weight: 600; }
.sub { font-size: .9rem; }
.side { text-align: right; color: var(--dim); font-size: .85rem; white-space: nowrap; }

.signin { max-width: 420px; margin: 8vh auto; text-align: center; }
.signin .sub { margin-bottom: 1.6rem; }
form.email { display: flex; flex-direction: column; gap: .6rem; }
input {
  font: inherit; padding: .7rem .8rem; border-radius: 10px;
  border: 1px solid var(--line); background: var(--card); color: var(--ink);
}
button.primary {
  font: inherit; padding: .7rem 1rem; border-radius: 10px; border: 0; cursor: pointer;
  background: var(--warm); color: #221c10; font-weight: 600;
}
button.quiet {
  font: inherit; padding: .55rem .9rem; border-radius: 10px; cursor: pointer;
  background: none; border: 1px solid var(--line); color: var(--dim);
}
/* The app's own sign-in button: 50pt tall, 10pt corners, white, the mark 20pt
   with 10pt of air before the words, which are 18pt medium in near-black. */
button.oauth {
  width: 100%; height: 50px; border-radius: 10px; border: 0; cursor: pointer;
  display: flex; align-items: center; justify-content: center; gap: 10px;
  margin-top: .55rem; background: #fff; color: #1f1f1f;
  font: 500 18px/1 system-ui, -apple-system, "Helvetica Neue", sans-serif;
}
button.oauth img { display: block; width: 20px; height: 20px; }
button.oauth:hover { background: #f4f4f4; }
.or { color: var(--dim); margin: 1.1rem 0 .2rem; font-size: .9rem; }
.back { background: none; border: 0; color: var(--dim); font: inherit; cursor: pointer; padding: 0 0 .6rem; }

.token { margin-top: 1.2rem; border: 1px solid var(--warm); border-radius: 12px; padding: .9rem; }
.token pre {
  background: #0d0c0a; border-radius: 8px; padding: .8rem; overflow-x: auto;
  font: 13px/1.5 ui-monospace, "IBM Plex Mono", monospace; color: var(--live);
}
hr { border: 0; border-top: 1px solid var(--line); margin: 1.4rem 0; }
@media (max-width: 520px) {
  #tabs {
    width: 100%; margin-left: 0; justify-content: flex-start;
    /* The last pill ends clear of the fade rather than under it. */
    padding-right: 2rem;
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 2rem), transparent);
    mask-image: linear-gradient(to right, #000 calc(100% - 2rem), transparent);
  }
  #bar { gap: .55rem; padding-bottom: .55rem; }
  /* Marks alone, up on the brand's row. The words are what will not fit, and
     the marks are each one of a pair, so a title and an accessible name carry
     the meaning instead. Order moves them above the strip without changing
     the source, which stays in the wider layout's reading order. */
  #extras { order: 1; margin-left: auto; }
  #tabs { order: 2; }
  #extras .tab span { display: none; }
  #extras .tab { padding: .45rem; min-width: 40px; min-height: 40px; justify-content: center; }
  #extras .tab svg { width: 18px; height: 18px; }
  /* Same optical correction as above, at the phone size. */
  #extras a.tab.join svg { width: 20px; height: 20px; }
}

/* The map keeps the app's proportions and sits centred, the way it does on a
   phone, rather than stretching to whatever width a desktop happens to be. */
/* Reading screens keep a column you can actually read. The map does not —
   a town squeezed into 760px on a wide screen is a postage stamp. */
body.town #shell { max-width: none; }
body.town #view { padding: 0; }
.mapwrap { position: relative; display: block; line-height: 0; }
/* The map's own box, so the recentre button can be placed against the canvas
   rather than against the wrapper, which also holds the pairing panel. */
.mapview { position: relative; display: block; line-height: 0; }
/* `none`, not `manipulation`: the map is dragged and pinched, and anything
   less hands those gestures to the browser to scroll and zoom the page with
   instead. `grab` is the resting cursor; the hit test raises a pointer over
   anything that actually answers a click. */
.mapview canvas { display: block; touch-action: none; cursor: grab; }

/* Bottom left — the way in to pairing already has the bottom right. Hidden
   until the map has been moved. */
.maphome {
  position: absolute; z-index: 4;
  left: max(1rem, env(safe-area-inset-left));
  bottom: max(1rem, calc(.6rem + env(safe-area-inset-bottom)));
  font: inherit; font-size: .82rem; line-height: 1;
  padding: .5rem .85rem; border-radius: 999px; cursor: pointer;
  border: 1px solid var(--line); background: var(--card); color: var(--dim);
  box-shadow: 0 2px 10px rgba(0, 0, 0, .45);
}
.maphome:hover { color: var(--ink); border-color: var(--warm); }
/* The town's clock: top right, the corner nothing else uses — the recentre
   button has the bottom left and the way in to pairing the bottom right. For
   reading only, so pointers go through it to the map. */
.mapclock {
  position: absolute; z-index: 4;
  top: .75rem; right: max(.75rem, env(safe-area-inset-right));
  display: grid; grid-template-columns: auto auto; align-items: center;
  column-gap: .55rem; row-gap: .12rem;
  padding: .45rem .8rem .45rem .6rem; border-radius: 12px;
  border: 1px solid var(--line); background: var(--card); color: var(--ink);
  box-shadow: 0 2px 10px rgba(0, 0, 0, .45);
  font-size: .82rem; line-height: 1.2; white-space: nowrap;
  pointer-events: none; user-select: none; -webkit-user-select: none;
}
.mapclock-icon { grid-row: 1 / span 2; display: block; width: 20px; height: 20px; color: var(--warm); }
.mapclock-icon svg { display: block; width: 100%; height: 100%; }
.mapclock-time { font-weight: 600; font-variant-numeric: tabular-nums; }
.mapclock-zone { color: var(--dim); font-size: .7rem; font-weight: 500; letter-spacing: .03em; }
.mapclock-next { color: var(--dim); font-size: .74rem; }
/* The hint sits above the map, not over it. Floating it over the canvas meant
   it always covered part of the town — and on a phone, where the map is
   already small, it covered the part you were trying to look at. A line of
   its own costs about twenty pixels and covers nothing. */
.maphint {
  position: static; transform: none; margin: 0;
  font-size: .82rem; line-height: 1.35; text-align: center;
  padding: .45rem 1rem; color: var(--dim);
  border-bottom: 1px solid var(--line); background: var(--card);
}
h3 { font-size: 1rem; margin: 1.2rem 0 .2rem; color: var(--dim); font-weight: 600; }

/* The inside of a place, drawn. A picture in the reading column: whole room
   pixels to whole screen pixels, framed like the cards around it. */
.roominside { margin: .4rem 0 1.1rem; }
.roomcanvas {
  display: block; margin: 0 auto; max-width: 100%;
  border: 1px solid var(--line); border-radius: 10px;
  box-shadow: 0 2px 14px rgba(0, 0, 0, .35);
  image-rendering: pixelated; touch-action: manipulation;
}
.roomnote { color: var(--dim); font-size: .82rem; text-align: center; margin: .5rem 0 0; }

/* The app's segmented pickers: Chatter's speech/everything, Record's three
   ways in. Same choices, same defaults. */
.seg { display: flex; gap: .3rem; margin-bottom: .9rem; flex-wrap: wrap; }
.segbtn {
  font: inherit; font-size: .85rem; padding: .35rem .75rem; border-radius: 999px;
  cursor: pointer; background: none; border: 1px solid var(--line); color: var(--dim);
}
.segbtn.on { background: var(--card); color: var(--ink); border-color: var(--warm); }

/* The pairing sheet. The app presents this as a modal over the tab it was
   opened from, with a title that changes once the token exists and a Cancel
   that becomes Done — so this does the same. */
.sheet {
  position: fixed; inset: 0; z-index: 50; display: flex;
  align-items: flex-end; justify-content: center;
  background: rgba(0,0,0,.55); backdrop-filter: blur(3px);
}
.sheetcard {
  width: min(560px, 100%); max-height: 92dvh; display: flex; flex-direction: column;
  background: var(--bg); border: 1px solid var(--line);
  border-radius: 18px 18px 0 0; overflow: hidden;
}
.sheethead {
  display: flex; align-items: center; justify-content: space-between;
  padding: .9rem 1rem; border-bottom: 1px solid var(--line); flex: none;
}
.sheettitle { font-weight: 600; }
.sheetclose {
  background: none; border: 0; color: var(--warm); font: inherit; cursor: pointer; padding: .2rem .3rem;
}
.sheetbody { padding: 1.1rem; overflow-y: auto; display: flex; flex-direction: column; gap: 1rem; }

/* The app's Panel: a card with a heading, a note, and sometimes a control. */
.panel {
  background: var(--card); border: 1px solid var(--line); border-radius: 14px;
  padding: .95rem 1rem; display: flex; flex-direction: column; gap: .55rem;
}
.panelhead { font-weight: 600; font-size: 1rem; }
.panelhead.accent { color: var(--warm); }
.panelnote { margin: 0; font-size: .86rem; color: var(--dim); line-height: 1.5; }
.panelnote.strong { color: var(--ink); font-weight: 500; }
.panelnote.bad { color: #ff9c8a; }
.panelnote.label { font-size: .78rem; text-transform: uppercase; letter-spacing: .06em; margin-top: .2rem; }
/* Contact details. Sized up and left as links, because the two things anybody
   does with an address is tap it or copy it, and both want a big target. */
a.contact {
  color: var(--warm); text-decoration: none; font-size: 1.02rem;
  word-break: break-word; padding: .1rem 0;
}
a.contact:hover { text-decoration: underline; }
/* A value and its copy button. The value wraps rather than scrolls: a token
   that is cut off looks copyable and is not, and the whole point here is that
   nobody has to retype any of it. */
.fieldset { display: flex; flex-direction: column; gap: .25rem; }
.copyrow { display: flex; align-items: center; gap: .5rem; }
.copyval {
  flex: 1; min-width: 0; background: var(--bg); border-radius: 8px;
  padding: .5rem .6rem; font: 12px/1.5 ui-monospace, "IBM Plex Mono", monospace;
  color: var(--ink); word-break: break-all; user-select: all;
}
.copyrow button { flex: none; }

/* A panel that folds. `.panel` is a flex column and a <details> is not — the
   summary has to stay its first child for the disclosure to work at all — so
   this reverts to block and spaces the children by hand. */
.panel.fold { display: block; }
.panel.fold > summary {
  cursor: pointer; font-weight: 600; font-size: 1rem;
  display: flex; align-items: center; gap: .5rem;
  /* Both, because Safari needs the pseudo-element and the rest need the
     property. Left in, the native triangle sits beside our own chevron. */
  list-style: none;
}
.panel.fold > summary::-webkit-details-marker { display: none; }
.panel.fold > summary::after {
  content: "\25be"; margin-left: auto; color: var(--dim);
  transition: transform .15s ease;
}
.panel.fold[open] > summary::after { transform: rotate(180deg); }
.panel.fold > summary:hover { color: var(--warm); }
.panel.fold > *:not(summary) { margin-top: .55rem; }
.panel input {
  width: 100%; background: var(--bg); border: 1px solid var(--line);
  border-radius: 10px; padding: .7rem .75rem; color: var(--ink); font: inherit;
}
.panel .mono {
  margin: 0; background: var(--bg); border-radius: 10px; padding: .75rem;
  font: 12px/1.6 ui-monospace, "IBM Plex Mono", monospace;
  color: var(--ink); white-space: pre-wrap; word-break: break-word;
  user-select: all; max-height: 46dvh; overflow-y: auto;
}
button.primary.wide { width: 100%; }

/* The join link. Styled as the primary button rather than reusing it,
   because that rule is tag-qualified to <button> and this has to stay an
   anchor to be openable in a new tab. */
.joinus {
  display: flex; align-items: center; justify-content: center; gap: .5rem;
  font: inherit; font-weight: 600; padding: .7rem 1rem; border-radius: 10px;
  background: var(--warm); color: #221c10; text-decoration: none;
}
.joinus svg { width: 19px; height: 19px; fill: currentColor; display: block; }
@media (min-width: 640px) { .sheet { align-items: center; } .sheetcard { border-radius: 18px; } }
.sub.bad { color: #ff9c8a; }
form.email input[inputmode="numeric"] {
  font: 500 20px/1 ui-monospace, "IBM Plex Mono", monospace;
  letter-spacing: .2em; text-align: center;
}

/* Settings rows, and the two buttons that are not ordinary actions. */
a.row.link { text-decoration: none; color: var(--ink); }
a.row.link:hover { background: var(--card); }
.stack { height: .6rem; }
button.danger {
  font: inherit; padding: .55rem .9rem; border-radius: 10px; cursor: pointer;
  background: none; border: 1px solid #5c2b2b; color: #ff9c8a; margin-top: .5rem;
}
button.danger.wide { width: 100%; }
button.quiet.wide { width: 100%; }
.flag {
  margin-left: .4rem; background: none; border: 0; color: var(--dim);
  font: inherit; cursor: pointer; padding: 0 .2rem; line-height: 1;
}
.flag:hover { color: var(--warm); }
.profilehead { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; margin-bottom: .3rem; }
.profilehead h2 { margin: 0; margin-right: auto; }
button.who.aslink {
  background: none; border: 0; padding: 0; font: inherit; color: var(--warm);
  font-weight: 600; cursor: pointer;
}
button.who.aslink:hover { text-decoration: underline; }
.sub.quote { font-style: italic; color: var(--ink); }

/* A room is for reading, so it keeps a column even though the map it was
   opened from runs edge to edge. */
.room { max-width: 680px; margin: 0 auto; padding: 1rem 1rem 3rem; }
.roomhero {
  border: 1px solid var(--line); border-radius: 16px; padding: 1.1rem 1.2rem;
  background: linear-gradient(180deg, var(--card), transparent);
  margin-bottom: 1rem;
}
.roomhero h2 { margin: 0 0 .4rem; font-size: 1.5rem; }
.roomdesc { margin: 0; color: var(--dim); font-size: 1rem; line-height: 1.6; }
.roomcount { margin: 0 0 .7rem; color: var(--dim); font-size: .9rem; }

/* Who is standing in it, as chips rather than a list — a room is a handful of
   people, not a directory. */
.faces { display: flex; flex-wrap: wrap; gap: .5rem; margin-bottom: .4rem; }
.face {
  display: inline-flex; align-items: center; gap: .5rem; cursor: pointer;
  background: var(--card); border: 1px solid var(--line); border-radius: 999px;
  padding: .4rem .8rem .4rem .5rem; color: var(--ink); font: inherit; font-size: .9rem;
}
.face:hover { border-color: var(--warm); }
.facedot { width: 14px; height: 14px; border-radius: 50%; display: block; }
.facename { font-weight: 500; }


/* ---------------------------------------------------------------- iPhone

   Nothing here accounted for the notch or the home indicator. With
   viewport-fit=cover the page is allowed under both, so the header sat in the
   notch and the bottom of the map — and the hint that floats over it — sat
   under the home indicator where it cannot be read or tapped. */
#bar {
  padding-top: max(.9rem, env(safe-area-inset-top));
  padding-left: max(1rem, env(safe-area-inset-left));
  padding-right: max(1rem, env(safe-area-inset-right));
}
#view {
  padding-left: max(1rem, env(safe-area-inset-left));
  padding-right: max(1rem, env(safe-area-inset-right));
  padding-bottom: max(1rem, env(safe-area-inset-bottom));
}
body.town #view { padding: 0; }

/* Six tabs do not fit across a phone. Rather than wrap them onto a second
   line that pushes the map down, they scroll sideways and keep their size. */
@media (max-width: 560px) {
  #tabs {
    width: 100%; margin-left: 0; flex-wrap: nowrap;
    overflow-x: auto; -webkit-overflow-scrolling: touch;
    scrollbar-width: none; gap: .2rem;
  }
  #tabs::-webkit-scrollbar { display: none; }
  .tab { flex: 0 0 auto; }
  .brand { font-size: .95rem; }
}

/* The hint floats over the map, so it has to clear the home indicator. */


/* A sheet rising from the bottom of the screen ends under the home indicator
   too, which is exactly where its primary button was. */
.sheetbody { padding-bottom: max(1.1rem, calc(1.1rem + env(safe-area-inset-bottom))); }

/* A room is reading, so it keeps clear of both edges. */
.room { padding-left: max(1rem, env(safe-area-inset-left));
        padding-right: max(1rem, env(safe-area-inset-right));
        padding-bottom: max(3rem, calc(2rem + env(safe-area-inset-bottom))); }

/* Nothing should ever be able to scroll the page sideways. */
html, body { overflow-x: hidden; }
/* "acting now" is the one state worth colouring: it means somebody is moving
   in the town as you read it. */
.side .livenow { color: var(--live); }
.star {
  background: none; border: 0; cursor: pointer; font-size: 1.15rem; line-height: 1;
  color: var(--dim); padding: .1rem .35rem 0 0; align-self: flex-start;
}
.star.on { color: var(--warm); }

/* The way in to pairing, on the town screen — the same panel the app puts
   under its map. Open on arrival, foldable to one round button so the town
   gets the whole page. The panel sits in flow under the canvas, which is what
   makes the map shrink rather than be covered; only the folded button floats. */
.sendin {
  padding: .85rem 1rem max(.9rem, calc(.5rem + env(safe-area-inset-bottom)));
  padding-left: max(1rem, env(safe-area-inset-left));
  padding-right: max(1rem, env(safe-area-inset-right));
  background: var(--card); border-top: 1px solid var(--line); line-height: 1.5;
}
.sendin-head { display: flex; align-items: baseline; gap: .5rem; }
.sendin h3 { margin: 0; font-size: 1.05rem; font-weight: 600; }
.sendin .sub { margin: .3rem 0 .7rem; }
.sendin-fold {
  margin-left: auto; background: none; border: 0; cursor: pointer;
  color: var(--dim); font: inherit; font-size: 1.2rem; line-height: 1;
  padding: .1rem .4rem;
}
.sendin-fold:hover { color: var(--ink); }
.sendin-fab {
  position: absolute; z-index: 4;
  right: max(1rem, env(safe-area-inset-right));
  bottom: max(1rem, calc(.6rem + env(safe-area-inset-bottom)));
  width: 52px; height: 52px; border-radius: 50%; border: 0; cursor: pointer;
  background: var(--warm); color: #221c10; font-size: 1.6rem; line-height: 1;
  box-shadow: 0 4px 16px rgba(0, 0, 0, .5);
}
.sendin-fab:hover { filter: brightness(1.06); }
