/* ══════════════════════════════════════════
   APP PAGE: app.css
   PinPint dark green theme
══════════════════════════════════════════ */

/* ── BASE ── */
body.app-page {
  margin: 0;
  padding: 0;
  background: var(--app-bg);
  color: var(--app-text);
  font-family: var(--font-sans);
  /* index.css opens with a bare `body{display:flex;height:100dvh;
     overflow:hidden}`, the map's app shell. This page loads that stylesheet
     for its tokens and its dialogs, and inherited the shell with it, which
     turned <main> into a flex item that shrank to its content: a 402px column
     on a 1536px screen that no max-width could widen. This page is a scrolling
     document, and says so. */
  display: block;
  height: auto;
  min-height: 100vh;
  overflow-x: hidden;
  overflow-y: auto;
}

/* ── HEADER ── */
.app-header {
  position: fixed;
  top: 0; left: 0; right: 0;
  height: var(--header-h);
  background: linear-gradient(90deg, var(--panel), var(--panel));
  border-bottom: 1px solid var(--app-border);
  display: flex;
  align-items: center;
  /* The inset as padding, not just as height. --header-h already grew to
     cover the status bar, but `align-items:center` would then centre the logo
     and the back button in the taller box, half of the offset above them and
     half below, which puts them a little too high and leaves the bar looking
     bottom-heavy. Padding the top pushes the row clear of the clock and keeps
     it centred in the part of the bar that is actually drawn. */
  padding: env(safe-area-inset-top) 16px 0;
  gap: 12px;
  z-index: 100;
  box-shadow: 0 2px 20px rgba(0,0,0,0.5);
}

.app-back-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px; height: 36px;
  border-radius: 50%;
  background: rgba(var(--brand-rgb),0.1);
  border: 1px solid var(--app-border);
  color: var(--app-green);
  text-decoration: none;
  font-size: 18px;
  flex-shrink: 0;
  transition: background 0.2s;
}
.app-back-btn:hover { background: rgba(var(--brand-rgb),0.2); }

/* An <a> since it became the way home. It should not look like a link, it
   should look exactly like the wordmark it already was, but it does have to
   behave like a target. */
.app-header-logo {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1;
  text-decoration: none;
  min-height: 44px;
}
.app-header-logo:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 8px; }
.app-logo-icon { font-size: 22px; }
/* The logo in the header, on every page that has one. A raster mark, so
   it takes an explicit box and the radius of the tile inside it: a 192px
   icon drawn at 30 shows the PNG's own square corners otherwise. */
.app-logo-mark { width: 30px; height: 30px; flex-shrink: 0; border-radius: 8px; display: block; }
.app-logo-text {
  font-size: 18px;
  font-weight: 800;
  color: var(--app-green);
  letter-spacing: -0.3px;
}

.app-header-right { display: flex; align-items: center; gap: 10px; }

.app-user-avatar {
  width: 34px; height: 34px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--brand-600), var(--accent));
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  color: var(--on-accent);
  font-size:14px;
  overflow: hidden;
  cursor: pointer;
  border: 2px solid rgba(var(--brand-rgb),0.3);
}

/* ── MAIN ── */
.app-main {
  margin-top: var(--header-h);
  padding-bottom: calc(var(--nav-h) + env(safe-area-inset-bottom) + 24px);
  min-height: calc(100vh - var(--header-h));
  overflow-y: auto;
}
/* On a page carrying the shared .tabbar, shared.css has already reserved the
   bar's height on <body>. Reserving it again here left a screen's worth of
   empty parchment under the last event on What's On. */
body:has(.tabbar) .app-main{padding-bottom:24px}

/* ── TABS ── */
.app-tab-content {
  display: none;
  padding: 16px;
  padding-bottom: 32px;
  animation: tabFadeIn 0.25s ease;
}
.app-tab-content.active { display: block; }

/* Opacity only, deliberately. A transform on the pane makes it the containing
   block for every position:fixed thing inside it, which is how the chat
   thread, an inset:0 full-screen sheet on a phone, ended up trapped in a
   396px box below the header for the length of the animation and for as long
   as a throttled tab left it unfinished. The eight-pixel rise was not worth a
   whole class of misplaced overlays. */
@keyframes tabFadeIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@media (prefers-reduced-motion:reduce){
  .app-tab-content{animation:none}
}

/* ── SECTION HEADER ── */
.app-section-header { margin-bottom: 16px; }
.app-section-title {
  font-size: 22px;
  font-weight: 800;
  color: var(--app-text);
  margin: 0 0 4px;
}
.app-section-sub {
  font-size:14px;
  color: var(--app-muted);
  margin: 0;
}

/* ── CITY SELECT ── */
.app-pub-stats {
  font-size:13px;
  color: var(--app-muted);
  font-weight: 600;
  letter-spacing: 0.3px;
  text-transform: uppercase;
  margin-bottom: 10px;
  padding: 0 2px;
}
.app-pub-stats span { color: var(--app-green); }

/* ── PUB LIST ── */
.app-pub-list { display: flex; flex-direction: column; gap: 8px; }

.app-pub-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  background: var(--app-card);
  border: 1px solid var(--app-border);
  border-radius: var(--app-radius);
  cursor: pointer;
  transition: background 0.18s, border-color 0.18s, transform 0.15s;
  -webkit-tap-highlight-color: transparent;
}
.app-pub-item:hover, .app-pub-item:active {
  background: rgba(var(--brand-rgb),0.07);
  border-color: rgba(var(--brand-rgb),0.3);
  transform: translateX(2px);
}

.app-pub-item.visited { border-left: 3px solid var(--app-green); }

.app-pub-number {
  font-size:13px;
  color: var(--app-muted);
  font-weight: 700;
  min-width: 24px;
  text-align: center;
}

.app-pub-visited-dot {
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--app-green);
  flex-shrink: 0;
  box-shadow: 0 0 6px rgba(var(--brand-rgb),0.5);
}
.app-pub-unvisited-dot {
  width: 8px; height: 8px;
  border-radius: 50%;
  background: rgba(255,255,255,0.15);
  flex-shrink: 0;
}

.app-pub-info { flex: 1; min-width: 0; }
.app-pub-name {
  font-size: 15px;
  font-weight: 700;
  color: var(--app-text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.app-pub-addr {
  font-size:13px;
  color: var(--app-muted);
  margin-top: 2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.app-pub-rating {
  font-size:13px;
  color: var(--warn);
  font-weight: 700;
  flex-shrink: 0;
}
.app-pub-meta {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 3px;
  flex-shrink: 0;
}
.app-pub-price {
  font-size:13px;
  color: var(--app-green);
  font-weight: 700;
  white-space: nowrap;
}
.app-pub-arrow {
  font-size: 16px;
  color: var(--app-muted);
  flex-shrink: 0;
}

/* ── EMPTY STATE ── */
.app-empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 48px 24px;
  text-align: center;
  gap: 10px;
}
.app-empty-icon { font-size: 40px; }
.app-empty-text { font-size: 15px; color: var(--app-muted); font-weight: 500; }
.app-cta-btn {
  margin-top: 8px;
  padding: 10px 24px;
  background: var(--app-green);
  color: var(--on-accent);
  border-radius: 99px;
  font-weight: 700;
  font-size:15px;
  text-decoration: none;
  display: inline-block;
}

/* ════════════════════════════
   CHAT
════════════════════════════ */

/* Friend conversation list */
.chat-friend-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  background: var(--app-card);
  border: 1px solid var(--app-border);
  border-radius: var(--app-radius);
  cursor: pointer;
  margin-bottom: 8px;
  transition: background 0.18s;
  position: relative;
}
.chat-friend-row:hover, .chat-friend-row:active {
  background: rgba(var(--brand-rgb),0.07);
}

.chat-friend-avatar {
  width: 46px; height: 46px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--brand-600), var(--accent));
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  color: var(--on-accent);
  font-size: 16px;
  overflow: hidden;
  flex-shrink: 0;
  position: relative;
}
.chat-online-indicator {
  position: absolute;
  bottom: 1px; right: 1px;
  width: 12px; height: 12px;
  border-radius: 50%;
  background: var(--brand-500);
  border: 2px solid var(--app-card);
}

.chat-friend-info { flex: 1; min-width: 0; }
.chat-friend-name {
  font-size: 15px;
  font-weight: 700;
  color: var(--app-text);
}
.chat-friend-preview {
  font-size:13px;
  color: var(--app-muted);
  margin-top: 2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.chat-friend-time {
  font-size:13px;
  color: var(--app-muted);
  flex-shrink: 0;
}

/* Conversation view */
/* A full-screen thread, so it covers the nav rather than stopping short of it,
   --z-sheet is the band for exactly that. It sat at 200 back when this page had
   its own bottom bar at 100; the shared .tabbar sits at 1300, which would have
   left a row of tabs painted across the bottom of an open conversation with the
   message composer underneath it.
   The composer already carries the home-indicator inset itself, so there is
   nothing left to reserve once the bar is out of the way. */
.chat-conversation-view {
  position: fixed;
  inset: 0;
  background: var(--app-bg);
  z-index: var(--z-sheet);
  display: flex;
  flex-direction: column;
}

.chat-conv-header {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  background: linear-gradient(90deg, var(--panel), var(--panel));
  border-bottom: 1px solid var(--app-border);
  flex-shrink: 0;
  padding-top: max(12px, env(safe-area-inset-top));
}
.chat-back-btn {
  background: none;
  border: none;
  color: var(--app-green);
  font-size: 22px;
  cursor: pointer;
  padding: 4px 8px 4px 0;
  font-family: inherit;
}
.chat-conv-avatar {
  width: 40px; height: 40px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--brand-600), var(--accent));
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  color: var(--on-accent);
  font-size: 15px;
  overflow: hidden;
  flex-shrink: 0;
}
.chat-conv-name {
  font-size: 16px;
  font-weight: 700;
  color: var(--app-text);
}
.chat-conv-status {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size:13px;
  color: var(--app-muted);
  margin-top: 2px;
}
.chat-status-dot {
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--text-2);
}
.chat-status-dot.online { background: var(--brand-500); box-shadow: 0 0 5px var(--brand-500); }

/* Messages area */
.chat-messages {
  flex: 1;
  overflow-y: auto;
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.chat-loading { color: var(--app-muted); font-size:14px; text-align: center; padding: 24px; }

.chat-msg {
  max-width: 72%;
  padding: 9px 13px;
  border-radius: 16px;
  font-size:15px;
  line-height: 1.45;
  word-break: break-word;
}
.chat-msg.mine {
  align-self: flex-end;
  background: var(--app-green);
  color: var(--on-accent);
  font-weight: 500;
  border-bottom-right-radius: 4px;
}
.chat-msg.theirs {
  align-self: flex-start;
  background: var(--app-card);
  border: 1px solid var(--app-border);
  color: var(--app-text);
  border-bottom-left-radius: 4px;
}
.chat-msg-time {
  font-size:12px;
  color: rgba(5,46,22,0.6);
  margin-top: 3px;
  text-align: right;
}
.chat-msg.theirs .chat-msg-time { color: var(--app-muted); }

/* ── A CRAWL IN A BUBBLE ──
   The link came out of the sentence and went back as this. It sits beside the
   crawl's name rather than under it, because the two are one offer: what is on,
   and the way in. Full width only when the name has pushed it to its own line. */
.chat-msg-text { display: inline; }
.chat-crawl-join {
  display: inline-flex; align-items: center; gap: 5px;
  margin-left: 8px; padding: 5px 13px;
  border-radius: 999px;
  background: var(--app-green); color: var(--on-accent);
  border: 1px solid var(--app-green);
  font-size: 13px; font-weight: 700; line-height: 1.2;
  text-decoration: none; white-space: nowrap;
  vertical-align: middle;
}
.chat-crawl-join:hover { filter: brightness(1.06); }
/* In your own bubble the ground is already the accent, so a filled pill on it
   is a green square on a green square. Outlined instead. */
.chat-msg.mine .chat-crawl-join {
  background: rgba(255,255,255,0.16);
  border-color: rgba(255,255,255,0.5);
  color: var(--on-accent);
}

.chat-date-divider {
  text-align: center;
  font-size:13px;
  color: var(--app-muted);
  margin: 8px 0;
  font-weight: 600;
  letter-spacing: 0.5px;
  text-transform: uppercase;
}

/* ── A CRAWL SENT OVER FROM THE MAP ──
   Two small notices, one either side of picking a thread. Without them the
   "Send in chat" button on the crawl builder just dumps you on a friend list
   with no explanation and a message box that has mysteriously filled itself
   in. */
.chat-share-pending {
  margin: 0 0 12px;
  padding: 11px 14px;
  background: var(--brand-ghost);
  border: 1px solid var(--brand-ring);
  border-radius: var(--r-md);
  color: var(--accent);
  font-size: var(--t-sm);
  font-weight: 600;
}
.chat-share-pending[hidden] { display: none; }

.chat-share-note {
  padding: 7px 16px;
  background: var(--brand-ghost);
  border-top: 1px solid var(--brand-ring);
  color: var(--accent);
  font-size: var(--t-xs);
  font-weight: 600;
  text-align: center;
  flex-shrink: 0;
}
.chat-share-note[hidden] { display: none; }

/* Input bar */
.chat-input-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 16px;
  padding-bottom: max(10px, env(safe-area-inset-bottom));
  background: var(--app-surface);
  border-top: 1px solid var(--app-border);
  flex-shrink: 0;
}
.chat-input {
  flex: 1;
  padding: 10px 14px;
  background: var(--app-card);
  border: 1px solid var(--app-border);
  border-radius: 99px;
  color: var(--app-text);
  font-size:15px;
  font-family: var(--font-sans);
  outline: none;
}
.chat-input:focus { border-color: rgba(var(--brand-rgb),0.4); }
.chat-send-btn {
  width: 40px; height: 40px;
  border-radius: 50%;
  background: var(--app-green);
  border: none;
  color: var(--on-accent);
  font-size: 16px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  transition: background 0.2s;
}
.chat-send-btn:hover { background: var(--brand-500); }

/* ════════════════════════════
   SETTINGS
════════════════════════════ */
.settings-card {
  background: var(--app-card);
  border: 1px solid var(--app-border);
  border-radius: var(--app-radius);
  padding: 16px;
  margin-bottom: 12px;
}
.settings-card-title {
  font-size:15px;
  font-weight: 700;
  color: var(--app-green);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  margin-bottom: 14px;
}

/* Avatar row */
.settings-avatar-row {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 16px;
}
.settings-avatar {
  width: 64px; height: 64px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--brand-600), var(--accent));
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 24px;
  font-weight: 700;
  color: var(--on-accent);
  cursor: pointer;
  position: relative;
  overflow: hidden;
  flex-shrink: 0;
  border: 2px solid rgba(var(--brand-rgb),0.3);
}
.settings-avatar-hint {
  position: absolute;
  inset: 0;
  background: rgba(0,0,0,0.5);
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 20px;
  opacity: 0;
  transition: opacity 0.2s;
}
.settings-avatar:hover .settings-avatar-hint { opacity: 1; }
.settings-avatar-name { font-size: 17px; font-weight: 700; color: var(--app-text); }
.settings-avatar-sub { font-size:13px; color: var(--app-muted); margin-top: 2px; }

/* Form fields */
.settings-field { margin-bottom: 12px; }
.settings-label {
  display: block;
  font-size:13px;
  font-weight: 700;
  color: var(--app-muted);
  text-transform: uppercase;
  letter-spacing: 0.4px;
  margin-bottom: 6px;
}
.settings-input {
  width: 100%;
  padding: 11px 14px;
  background: rgba(255,255,255,0.04);
  border: 1px solid var(--app-border);
  border-radius: 10px;
  color: var(--app-text);
  font-size:15px;
  font-family: var(--font-sans);
  outline: none;
  box-sizing: border-box;
}
.settings-input:focus {
  border-color: rgba(var(--brand-rgb),0.4);
  background: rgba(var(--brand-rgb),0.04);
}
.settings-save-btn {
  width: 100%;
  padding: 11px;
  background: rgba(var(--brand-rgb),0.15);
  border: 1px solid rgba(var(--brand-rgb),0.3);
  border-radius: 10px;
  color: var(--app-green);
  font-size:15px;
  font-weight: 700;
  font-family: var(--font-sans);
  cursor: pointer;
  transition: background 0.2s;
}
.settings-save-btn:hover { background: rgba(var(--brand-rgb),0.25); }

/* Toggle rows */
.settings-toggle-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 0;
  border-bottom: 1px solid var(--app-border);
}
.settings-toggle-row:last-child { border-bottom: none; }
.settings-toggle-label { font-size:15px; font-weight: 600; color: var(--app-text); }
.settings-toggle-sub { font-size:13px; color: var(--app-muted); margin-top: 2px; }

/* Toggle switch */
.settings-toggle { position: relative; display: inline-block; width: 44px; height: 24px; flex-shrink: 0; }
.settings-toggle input { opacity: 0; width: 0; height: 0; }
.settings-toggle-slider {
  position: absolute;
  inset: 0;
  background: rgba(255,255,255,0.1);
  border-radius: 99px;
  cursor: pointer;
  transition: background 0.2s;
}
.settings-toggle-slider::before {
  content: '';
  position: absolute;
  width: 18px; height: 18px;
  left: 3px; top: 3px;
  background: var(--panel);
  border-radius: 50%;
  transition: transform 0.2s;
}
.settings-toggle input:checked + .settings-toggle-slider { background: var(--app-green); }
.settings-toggle input:checked + .settings-toggle-slider::before { transform: translateX(20px); }

/* Danger zone */
.settings-danger-card { border-color: rgba(var(--danger-rgb),0.2); }
.settings-danger-text { font-size:14px; color: var(--app-muted); margin: 0 0 14px; line-height: 1.5; }
.settings-delete-btn {
  width: 100%;
  padding: 11px;
  background: var(--app-danger-bg);
  border: 1px solid rgba(var(--danger-rgb),0.3);
  border-radius: 10px;
  color: var(--app-danger);
  font-size:15px;
  font-weight: 700;
  font-family: var(--font-sans);
  cursor: pointer;
  transition: background 0.2s;
}
.settings-delete-btn:hover { background: rgba(var(--danger-rgb),0.15); }

/* ════════════════════════════
   MODAL
════════════════════════════ */
/* Dialogs on this page are the same kind of thing as dialogs on the map, so
   they use the same band. 500 was below the shared .tabbar's 1300, which meant
   the delete-account and claim-your-pub dialogs opened with a live, clickable
   row of navigation sitting on top of them. */
.app-modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.8);
  z-index: var(--z-dialog);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  backdrop-filter: blur(6px);
}
.app-modal {
  background: var(--app-card);
  border: 1px solid var(--app-border);
  border-radius: 18px;
  padding: 24px;
  width: 100%;
  max-width: 360px;
}
.app-modal-title {
  font-size: 18px;
  font-weight: 800;
  color: var(--app-text);
  margin-bottom: 10px;
}
.app-modal-text {
  font-size:14px;
  color: var(--app-muted);
  line-height: 1.55;
  margin-bottom: 16px;
}
.app-modal-actions { display: flex; gap: 10px; }
.app-modal-cancel {
  flex: 1;
  padding: 11px;
  background: rgba(255,255,255,0.06);
  border: 1px solid var(--app-border);
  border-radius: 10px;
  color: var(--app-muted);
  font-size:15px;
  font-weight: 700;
  cursor: pointer;
  font-family: var(--font-sans);
}
.app-modal-confirm {
  flex: 1;
  padding: 11px;
  background: var(--app-danger-bg);
  border: 1px solid rgba(var(--danger-rgb),0.3);
  border-radius: 10px;
  color: var(--app-danger);
  font-size:15px;
  font-weight: 700;
  cursor: pointer;
  font-family: var(--font-sans);
}

/* ── TOAST ── */
/* .app-toast is styled in shared.css, with .toast, because they are the same
   object and were drifting apart. */

/* ── ACROSS DEVICES ──────────────────────────────────────────────────────
   One width for all three panes was the compromise that made this page look
   like a phone on a laptop. They want different things: a directory of 5,700
   pubs wants every pixel, a settings form wants a readable column, and a chat
   wants two panes. So the pane decides, and the header follows it so the
   wordmark and the avatar line up with the content underneath. */
@media (min-width: 640px) {
  .app-main { max-width: 640px; margin-left: auto; margin-right: auto; }
  .chat-conversation-view { max-width: 640px; left: 50%; transform: translateX(-50%); }
  .app-header { justify-content: center; }
  .app-header-logo { max-width: 640px; width: 100%; }
}

/* ── PUB LIST TOOLS ───────────────────────────────────────────────────────
   71 pubs in a flat list is a scroll, not a browse. */
.app-list-tools{
  display:flex;align-items:center;gap:10px;
  margin:0 0 12px;
}
.app-list-tools[hidden]{display:none}
.app-list-search{
  flex:1;min-width:0;display:flex;align-items:center;gap:8px;
  height:40px;padding:0 12px;
  background:var(--panel-2);border:1px solid var(--border);
  border-radius:999px;
  transition:border-color .15s ease, box-shadow .15s ease;
}
.app-list-search:focus-within{border-color:var(--accent);box-shadow:0 0 0 3px var(--brand-ring)}
.app-list-search-ico{font-size:14px;flex-shrink:0;opacity:.75}
.app-list-search-input{
  flex:1;min-width:0;background:transparent;border:none;outline:none;
  color:var(--text);font-family:inherit;font-size:15px;
}
.app-list-search-input::placeholder{color:var(--text-3)}
.app-list-search-input::-webkit-search-cancel-button{filter:grayscale(1);opacity:.6}

.app-list-toggle{
  display:flex;align-items:center;gap:7px;flex-shrink:0;
  height:40px;padding:0 13px;
  background:var(--panel-2);border:1px solid var(--border);
  border-radius:999px;cursor:pointer;
  font-size:14px;font-weight:600;color:var(--text-2);
  white-space:nowrap;user-select:none;
}
.app-list-toggle:hover{border-color:var(--border-strong);color:var(--text)}
.app-list-toggle input{accent-color:var(--accent-solid);width:15px;height:15px;cursor:pointer}
.app-list-toggle:has(input:checked){
  background:var(--brand-ghost);border-color:var(--accent);color:var(--text);
}

/* Progress under the counts, the list is a checklist, so show the score. */
.app-pub-stats-line{margin-bottom:7px}
.app-pub-progress{
  height:5px;border-radius:999px;overflow:hidden;
  background:var(--panel-2);border:1px solid var(--border);
}
.app-pub-progress-fill{
  height:100%;background:var(--accent-solid);
  border-radius:999px;transition:width .45s var(--ease-out,ease);
}

/* Rows became <button> for keyboard access; strip the UA button styling. */
.app-pub-item{
  width:100%;text-align:left;font:inherit;color:inherit;
  background:var(--panel-2);border:1px solid var(--border);
  cursor:pointer;
}
.app-pub-item .app-pub-info{display:flex;flex-direction:column;min-width:0;flex:1}
.app-pub-item .app-pub-meta{display:flex;flex-direction:column;align-items:flex-end;flex-shrink:0}
.app-pub-item .app-pub-name,
.app-pub-item .app-pub-addr{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

@media (min-width:900px){
  .app-main{max-width:900px}
  .app-header-logo{max-width:900px}

  /* ── CHAT, WITH ROOM ──
     The thread was position:fixed;inset:0, a full-screen takeover, which is
     the right answer on a phone and the wrong one anywhere you can see the
     list and the conversation at once. Here it is simply the second pane. */
  #tab-chat.active{
    display:grid;
    grid-template-columns:330px minmax(0,1fr);
    grid-template-rows:auto minmax(0,1fr);
    column-gap:20px;
    /* Both panes scroll inside themselves, so the page does not. */
    height:calc(100vh - var(--header-h) - var(--tabbar-h) - 32px);
  }
  #tab-chat .app-section-header{grid-column:1 / -1}
  #chat-conversation-list{
    grid-column:1;grid-row:2;min-height:0;overflow-y:auto;
    padding-right:4px;
  }
  /* Both live in the same cell, so the thread covers the placeholder the
     moment one is opened, no JS beyond the display toggle it already does. */
  .chat-pane-empty,
  #tab-chat .chat-conversation-view{grid-column:2;grid-row:2}
  #tab-chat .chat-conversation-view{
    position:static;inset:auto;max-width:none;transform:none;
    border:1px solid var(--app-border);border-radius:var(--app-radius);
    overflow:hidden;min-height:0;
  }
  #tab-chat .chat-conv-header{padding-top:12px;border-radius:var(--app-radius) var(--app-radius) 0 0}
  /* Going "back" is what the list beside it is for. */
  #tab-chat .chat-back-btn{display:none}
  .chat-pane-empty{
    display:flex;flex-direction:column;align-items:center;justify-content:center;
    gap:8px;text-align:center;padding:24px;
    border:1px dashed var(--app-border);border-radius:var(--app-radius);
    color:var(--text-3);
  }
  .chat-pane-empty-ico{font-size:30px;opacity:.8}
  .chat-pane-empty-txt{font-size:var(--t-base);max-width:34ch;line-height:1.5}
}

/* Below that the thread is a full-screen sheet, as it was, and the placeholder
   has nothing to sit beside. */
@media (max-width:899px){
  .chat-pane-empty{display:none}
}

/* ══════════════════════════════════════════════════════════════════════════
   EXPLORE: search, towns, pubs

   One search box over two views: a grid of towns, or the pubs in one of them.
   Everything here is fluid, the grids are auto-fill, so the same markup is
   one column on a 320px phone and four on a 1440px laptop without a single
   width being named per device.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── The box ── */
.ex-search-wrap{position:relative;margin:0 0 18px;z-index:20}
.ex-search{
  display:flex;align-items:center;gap:9px;
  height:46px;padding:0 14px;
  background:var(--panel-2);border:1px solid var(--border);
  border-radius:var(--r-full);
  transition:border-color var(--dur-fast) ease, box-shadow var(--dur-fast) ease;
}
.ex-search:focus-within{border-color:var(--accent);box-shadow:0 0 0 3px var(--brand-ring)}
.ex-search-ico{font-size:15px;flex-shrink:0;opacity:.75}
.ex-search-input{
  flex:1;min-width:0;background:transparent;border:none;outline:none;
  color:var(--text);font-family:inherit;
  /* 16px, or iOS zooms the page the moment this takes focus. */
  font-size:16px;
}
.ex-search-input::placeholder{color:var(--text-3)}
.ex-search-input::-webkit-search-cancel-button{display:none}
.ex-search-clear{
  flex-shrink:0;width:28px;height:28px;border-radius:50%;
  display:flex;align-items:center;justify-content:center;
  background:var(--hover);border:none;color:var(--text-2);
  font-size:18px;line-height:1;cursor:pointer;
}
.ex-search-clear:hover{color:var(--text)}
.ex-search-clear[hidden]{display:none}

/* ── The results panel ── */
.ex-results{
  position:absolute;top:calc(100% + 6px);left:0;right:0;
  max-height:min(60vh,420px);overflow-y:auto;overscroll-behavior:contain;
  background:var(--panel);border:1px solid var(--border);
  border-radius:var(--r-lg);box-shadow:var(--shadow-3);
  padding:6px;
}
.ex-results[hidden]{display:none}
.ex-res-head{
  padding:8px 10px 4px;
  font-size:var(--t-2xs);font-weight:800;letter-spacing:.09em;text-transform:uppercase;
  color:var(--text-3);
}
.ex-res{
  display:flex;align-items:center;gap:11px;width:100%;
  padding:9px 10px;min-height:52px;
  background:none;border:none;border-radius:var(--r-md);
  text-align:left;font:inherit;color:var(--text);cursor:pointer;
}
.ex-res:hover,.ex-res.is-active{background:var(--hover)}
.ex-res-ico{font-size:19px;flex-shrink:0;width:22px;text-align:center}
.ex-res-main{display:flex;flex-direction:column;min-width:0;flex:1;gap:1px}
.ex-res-name{font-size:var(--t-base);font-weight:700;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ex-res-sub{font-size:var(--t-xs);color:var(--text-3);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ex-res-go{color:var(--text-3);font-size:18px;flex-shrink:0}
.ex-res-none{padding:16px 12px;font-size:var(--t-base);color:var(--text-2);line-height:1.5}
.ex-res-none span{display:block;color:var(--text-3);font-size:var(--t-xs);margin-top:3px}
.ex-res-more{
  padding:9px 12px 6px;font-size:var(--t-2xs);color:var(--text-3);
  border-top:1px solid var(--border);margin-top:4px;
}

/* ── The town grid ── */
.ex-group-head{
  display:flex;align-items:baseline;justify-content:space-between;gap:12px;
  flex-wrap:wrap;margin:6px 0 10px;
}
.ex-group-head[hidden]{display:none}
.ex-group-title{
  margin:0;font-size:var(--t-2xs);font-weight:800;
  letter-spacing:.09em;text-transform:uppercase;color:var(--text-3);
}
.ex-group-note{font-size:var(--t-2xs);color:var(--text-3);opacity:.8}

.ex-town-grid{
  display:grid;gap:8px;margin-bottom:22px;
  grid-template-columns:repeat(auto-fill,minmax(148px,1fr));
}
.ex-town{
  display:flex;flex-direction:column;align-items:flex-start;gap:2px;
  min-height:74px;padding:11px 13px;
  background:var(--panel-2);border:1px solid var(--border);
  border-radius:var(--r-lg);
  text-align:left;font:inherit;color:var(--text);cursor:pointer;
  transition:border-color var(--dur-fast) ease, background var(--dur-fast) ease,
             transform var(--dur-fast) var(--ease-out);
}
.ex-town:hover{border-color:var(--accent);background:var(--hover)}
.ex-town:active{transform:scale(.985)}
.ex-town:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.ex-town-emoji{font-size:17px;line-height:1.2}
.ex-town-label{
  font-size:var(--t-base);font-weight:700;line-height:1.25;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%;
}
.ex-town-count{font-size:var(--t-2xs);color:var(--text-3);font-variant-numeric:tabular-nums}

.ex-more-btn{
  display:block;width:100%;max-width:280px;margin:0 auto 24px;
  min-height:44px;padding:0 18px;
  background:transparent;border:1px solid var(--border);border-radius:var(--r-full);
  color:var(--text-2);font:inherit;font-weight:700;cursor:pointer;
}
.ex-more-btn:hover{border-color:var(--accent);color:var(--text)}
.ex-more-btn[hidden]{display:none}

/* ── One town's pubs ── */
.ex-town-bar{
  display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  margin:0 0 12px;
}
.ex-back{
  display:inline-flex;align-items:center;gap:6px;
  min-height:38px;padding:0 13px;
  background:var(--panel-2);border:1px solid var(--border);border-radius:var(--r-full);
  color:var(--text-2);font:inherit;font-size:var(--t-xs);font-weight:700;cursor:pointer;
  flex-shrink:0;
}
.ex-back:hover{border-color:var(--accent);color:var(--text)}
.ex-town-name{
  margin:0;flex:1;min-width:0;
  font-family:var(--font-display);font-size:var(--t-xl);font-weight:700;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
#ex-pubs[hidden],#ex-towns[hidden]{display:none}

/* On a narrow phone the back button and the visited toggle leave about forty
   pixels for the town, which turned Galway into "G…". The name takes its own
   line instead of being the thing that gives way. */
@media (max-width:520px){
  .ex-town-name{order:3;flex:1 0 100%;font-size:var(--t-lg);margin-top:2px}
}

/* The list is the one thing on this page worth all the width there is: one
   column on a phone, and as many as fit after that. */
@media (min-width:560px){
  .app-pub-list{display:grid;grid-template-columns:repeat(auto-fill,minmax(268px,1fr));gap:8px}
}

/* Explore is a directory, so it gets the room a directory needs. Chat and
   Settings stay at their reading width, they are columns of prose and
   controls, and stretching those to 1200px helps nobody. */
@media (min-width:1000px){
  /* The directory takes the room; the settings form keeps a column, because a
     1200px-wide row with a toggle at each end is a form nobody can scan. */
  .app-main:has(#tab-pubs.active),
  .app-main:has(#tab-chat.active){max-width:1240px}
  body:has(#tab-pubs.active) .app-header-logo,
  body:has(#tab-chat.active) .app-header-logo{max-width:1240px}
  .app-main:has(#tab-settings.active){max-width:760px}
  body:has(#tab-settings.active) .app-header-logo{max-width:760px}
  .ex-town-grid{grid-template-columns:repeat(auto-fill,minmax(168px,1fr))}
}

/* Footer links were hardcoded #4ade80 / #2a3f2e, so they stayed dark-theme
   green on the parchment light theme. */
.app-footer-link{color:var(--accent);text-decoration:none;opacity:.8}
.app-footer-link:hover{opacity:1;text-decoration:underline}
.app-footer-sep{color:var(--border-strong);margin:0 8px}

/* ════════════════════════════════════════════════════════════════════════
   PUB ACCOUNTS: claiming a pub, and the pub's own inbox

   A publican had no account here at all: anybody could list an event at
   their venue and the pub found out when somebody walked in and asked what
   time the quiz started.
   ════════════════════════════════════════════════════════════════════════ */

.settings-card-note{
  font-size: 14px;
  line-height: 1.55;
  color: var(--app-muted);
  margin: 0 0 14px;
}

/* Three states, three colours, and each one says what the user is waiting on
   rather than only what happened. */
.pub-claim-state{
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 13px 15px;
  border-radius: 12px;
  border: 1px solid var(--app-border);
  background: rgba(255,255,255,0.04);
  margin-bottom: 12px;
}
.pub-claim-state strong{ font-size: 15px; color: var(--app-text); font-weight: 700; }
.pub-claim-state span{ font-size: 13px; color: var(--app-muted); line-height: 1.5; }
.pub-claim-state-pending{ border-color: rgba(var(--warn-rgb),.45); background: rgba(var(--warn-rgb),.08); }
.pub-claim-state-live{ border-color: rgba(var(--brand-rgb),.45); background: rgba(var(--brand-rgb),.08); }
.pub-claim-state-off{ border-color: rgba(var(--danger-rgb),.4); background: rgba(var(--danger-rgb),.07); }

/* ── The inbox ── */
.pub-inbox{ display: flex; flex-direction: column; gap: 6px; }
.pub-inbox-empty{
  font-size: 14px;
  line-height: 1.55;
  color: var(--app-muted);
  padding: 14px 2px;
}
.pub-inbox-head{
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 13px;
  font-weight: 700;
  color: var(--app-muted);
  text-transform: uppercase;
  letter-spacing: .5px;
  margin-bottom: 4px;
}
.pub-inbox-badge{
  min-width: 20px;
  padding: 1px 6px;
  border-radius: 99px;
  background: var(--app-green);
  color: #04150C;
  font-size: 12px;
  font-weight: 800;
  text-align: center;
}
.pub-inbox-readall{
  margin-left: auto;
  background: none;
  border: none;
  color: var(--app-green);
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 600;
  text-transform: none;
  letter-spacing: 0;
  cursor: pointer;
  padding: 6px 2px;
  min-height: 32px;
}
.pub-inbox-readall:hover{ text-decoration: underline; }

.pub-inbox-row{
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 11px 12px;
  border-radius: 11px;
  border: 1px solid transparent;
  background: rgba(255,255,255,0.04);
  text-decoration: none;
  transition: border-color .15s ease, background .15s ease;
}
a.pub-inbox-row:hover{ border-color: var(--app-green); background: rgba(var(--brand-rgb),.07); }
.pub-inbox-row.is-unread{ background: rgba(var(--brand-rgb),.09); }

/* The dot is the only thing distinguishing read from unread at a glance, so
   it is not the only thing, the row tints too. Colour alone is not a state. */
.pub-inbox-dot{
  width: 8px; height: 8px;
  border-radius: 50%;
  background: transparent;
  flex-shrink: 0;
  margin-top: 6px;
}
.pub-inbox-row.is-unread .pub-inbox-dot{ background: var(--app-green); }

.pub-inbox-text{ display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.pub-inbox-title{ font-size: 15px; font-weight: 700; color: var(--app-text); line-height: 1.35; }
.pub-inbox-body{ font-size: 14px; color: var(--app-muted); line-height: 1.5; }
.pub-inbox-when{ font-size: 12px; color: var(--app-muted); opacity: .75; }

/* ── The publican's diary ──
   The inbox says what happened; this says what is coming. Kept in the same
   card because they are two halves of the same question, a notification that
   somebody listed a quiz at your pub is only useful beside the list of what
   else you have on that night. */
.pub-schedule{ margin-top: 16px; border-top: 1px solid var(--app-border); padding-top: 14px; }
.pub-schedule-head{
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; flex-wrap: wrap; margin-bottom: 10px;
}
.pub-schedule-title{
  font-size: 13px; font-weight: 700; letter-spacing: .6px;
  text-transform: uppercase; color: var(--app-muted);
}
.pub-schedule-add{
  display: inline-flex; align-items: center;
  min-height: 36px; padding: 0 14px;
  border-radius: 99px;
  background: rgba(var(--brand-rgb),.12);
  border: 1px solid rgba(var(--brand-rgb),.35);
  color: var(--app-green);
  font-size: 14px; font-weight: 700; text-decoration: none;
}
.pub-schedule-add:hover{ background: rgba(var(--brand-rgb),.2); }

.pub-schedule-list{ display: flex; flex-direction: column; gap: 6px; }
.pub-schedule-row{
  display: flex; align-items: center; gap: 10px;
  padding: 10px 12px;
  border: 1px solid var(--app-border);
  border-radius: 10px;
  background: rgba(255,255,255,.03);
}
/* A night you planned, marked apart from a night somebody else has announced
   is happening in your pub. Left border rather than a background tint: the
   unread inbox rows already use the tint and two greens meaning two things in
   one card is how a card stops being readable. */
.pub-schedule-row.is-mine{ border-left: 3px solid var(--app-green); }
.pub-schedule-ico{ font-size: 20px; line-height: 1; flex-shrink: 0; }
.pub-schedule-text{ display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.pub-schedule-name{
  font-size: 15px; font-weight: 700; color: var(--app-text); line-height: 1.35;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pub-schedule-meta{ font-size: 13px; color: var(--app-muted); line-height: 1.4; }
.pub-schedule-tag{
  flex-shrink: 0;
  font-size: 12px; font-weight: 700;
  padding: 2px 8px; border-radius: 99px;
  background: rgba(255,255,255,.08); color: var(--app-muted);
}

/* ── Picking which pub is yours ── */
.claim-results{ display: flex; flex-direction: column; gap: 4px; margin-top: 6px; }
.claim-result{
  display: flex;
  flex-direction: column;
  gap: 1px;
  text-align: left;
  padding: 9px 12px;
  border-radius: 10px;
  border: 1px solid var(--app-border);
  background: rgba(255,255,255,0.04);
  cursor: pointer;
  font-family: var(--font-sans);
  min-height: 44px;
}
.claim-result:hover{ border-color: var(--app-green); }
.claim-result-name{ font-size: 15px; font-weight: 600; color: var(--app-text); }
.claim-result-addr{ font-size: 13px; color: var(--app-muted); }
.claim-result-empty{ font-size: 14px; color: var(--app-muted); padding: 8px 2px; }
.claim-chosen{
  margin-top: 8px;
  padding: 9px 12px;
  border-radius: 10px;
  border: 1px solid rgba(var(--brand-rgb),.45);
  background: rgba(var(--brand-rgb),.08);
  font-size: 14px;
  font-weight: 600;
  color: var(--app-text);
}

/* The claim dialog is the tallest modal in the app, four fields and a search
   result list. It has to scroll on a phone rather than run off the screen. */
#pub-claim-modal .app-modal{
  max-width: 460px;
  max-height: calc(100dvh - 48px);
  overflow-y: auto;
  overscroll-behavior: contain;
}
#pub-claim-modal textarea.settings-input{
  resize: vertical;
  min-height: 76px;
  font-family: var(--font-sans);
}

/* ════════════════════════════════════════════════════════════════════════
   THE PUBLIC PASSPORT CARD  (Settings)

   The one card in Settings that publishes something, so it is the one card
   that has to be legible about what it publishes. The two-column disclosure
   below is the whole idea: "goes out" and "stays private" side by side, read
   in one glance, instead of a sentence nobody finishes.
   ════════════════════════════════════════════════════════════════════════ */

/* The handle field reads as an address rather than as a name, because that is
   what it becomes. The prefix is part of the control's box, not a label above
   it, so what you are typing is visibly the tail of a URL. */
.settings-handle-row{
  display: flex;
  align-items: stretch;
  border: 1px solid var(--app-border);
  border-radius: 10px;
  background: rgba(255,255,255,0.04);
  overflow: hidden;
}
.settings-handle-row:focus-within{
  border-color: rgba(var(--brand-rgb),0.4);
  background: rgba(var(--brand-rgb),0.04);
}
.settings-handle-prefix{
  display: flex;
  align-items: center;
  padding: 0 2px 0 12px;
  font-size: 15px;
  color: var(--app-muted);
  white-space: nowrap;
  user-select: none;
}
.settings-input.settings-handle-input{
  border: none;
  background: none;
  border-radius: 0;
  padding-left: 0;
  /* An address is lowercase or it is a different address. The database
     rejects uppercase outright rather than folding it, so the field should
     never let somebody type a handle that will be refused. */
  text-transform: lowercase;
}
.settings-input.settings-handle-input:focus{ background: none; }

.settings-field-hint{
  font-size: 13px;
  line-height: 1.45;
  color: var(--app-muted);
  margin: 6px 0 0;
  overflow-wrap: anywhere;
}
.settings-field-hint.is-bad { color: var(--danger); }
.settings-field-hint.is-good { color: var(--app-green); }

.settings-disclosure{
  display: grid;
  grid-template-columns: 1fr;
  gap: 14px;
  margin: 16px 0;
  padding: 14px;
  border-radius: 12px;
  background: rgba(255,255,255,0.03);
  border: 1px solid var(--app-border);
}
@media (min-width: 420px){
  .settings-disclosure{ grid-template-columns: 1fr 1fr; gap: 18px; }
}
.settings-disclosure-head{
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 1px;
  text-transform: uppercase;
  margin-bottom: 7px;
}
.settings-disclosure-out{ color: var(--warn); }
.settings-disclosure-in { color: var(--app-green); }
.settings-disclosure ul{
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 5px;
  font-size: 13px;
  line-height: 1.4;
  color: var(--app-muted);
}
.settings-disclosure li{ padding-left: 14px; position: relative; }
.settings-disclosure li::before{
  content: '·';
  position: absolute;
  left: 4px;
  font-weight: 700;
}

.settings-live-link[hidden]{ display: none; }
.settings-live-link{
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 14px;
  padding: 11px 14px;
  border-radius: 10px;
  background: rgba(var(--brand-rgb),0.08);
  border: 1px solid rgba(var(--brand-rgb),0.3);
}
.settings-live-link a{
  flex: 1;
  min-width: 0;
  color: var(--app-green);
  font-size: 14px;
  font-weight: 600;
  text-decoration: none;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.settings-live-link a:hover{ text-decoration: underline; }
.settings-copy-btn{
  flex: none;
  padding: 7px 12px;
  border-radius: 8px;
  background: rgba(var(--brand-rgb),0.16);
  border: 1px solid rgba(var(--brand-rgb),0.34);
  color: var(--app-green);
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
}
.settings-copy-btn:hover{ background: rgba(var(--brand-rgb),0.26); }

/* ════════════════════════════════════════════════════════════════════════
   EXPLORE: THE DISCOVERY RAILS

   The front of the Explore tab. Horizontal rails of cards, because the
   question each one answers ("what's near me", "what's cheap", "what's busy")
   is a short list you scan rather than a long list you read, and because a
   phone has one screen of height and six vertical lists would put the sixth
   below three scrolls of the first.

   The price is the first thing in every card and the largest, which is the
   same order the map pins use. Somebody who has learnt to read one has learnt
   to read the other.
   ════════════════════════════════════════════════════════════════════════ */

#ex-discover[hidden]{ display: none; }

.ex-rail{ margin: 0 0 22px; }
.ex-rail[hidden]{ display: none; }

.ex-rail-head{
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin: 0 0 10px;
}
.ex-rail-title{
  font-family: var(--font-display);
  font-size: 18px;
  font-weight: 700;
  color: var(--app-text);
  margin: 0;
}
.ex-rail-more{
  flex: none;
  font-size: 13px;
  font-weight: 600;
  color: var(--app-green);
  text-decoration: none;
  white-space: nowrap;
}
.ex-rail-more:hover{ text-decoration: underline; }

/* Edge-to-edge, so the last card is visibly cut off rather than ending
   neatly at the margin. A rail that ends flush looks like a rail that has
   nothing more in it. */
.ex-rail-scroll{
  display: flex;
  gap: 10px;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x proximity;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  padding: 2px 16px 4px;
  margin: 0 -16px;
}
.ex-rail-scroll::-webkit-scrollbar{ display: none; }

.ex-card{
  flex: none;
  scroll-snap-align: start;
  width: 148px;
  min-height: 106px;
  display: flex;
  flex-direction: column;
  padding: 12px 13px;
  border-radius: 14px;
  background: var(--app-card);
  border: 1px solid var(--app-border);
  text-decoration: none;
  color: inherit;
  transition: border-color 0.18s ease, background 0.18s ease;
}
.ex-card:hover{ border-color: rgba(var(--brand-rgb),0.45); background: var(--hover); }
.ex-card:focus-visible{ outline: 2px solid var(--app-green); outline-offset: 2px; }

.ex-card-price{
  font-family: var(--font-num);
  font-size: 22px;
  font-weight: 700;
  color: var(--app-green);
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}
/* No price on record. Grey, and deliberately off the price ramp entirely,
   the same rule the map pins follow, so "nobody has told us" can never be
   misread as "cheap". */
.ex-card-price.is-none{ color: var(--pin-none); }

.ex-card-name{
  font-size: 14px;
  font-weight: 700;
  color: var(--app-text);
  margin-top: 6px;
  /* Two lines, then clipped. A pub called The Old Ground Hotel Bar And
     Lounge must not make its card twice the height of the one beside it. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  line-clamp: 2;
  overflow: hidden;
}
.ex-card-sub{
  font-size: 12px;
  color: var(--app-muted);
  margin-top: 2px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ex-card-foot{
  margin-top: auto;
  padding-top: 6px;
  font-size: 12px;
  font-weight: 600;
  color: var(--app-muted);
  font-variant-numeric: tabular-nums;
}
.ex-card-badge{
  margin-top: auto;
  padding-top: 6px;
  font-size: 12px;
  font-weight: 700;
  color: var(--live);
}
.ex-card-when{
  font-family: var(--font-num);
  font-size: 20px;
  font-weight: 700;
  color: var(--warn);
  line-height: 1.1;
}
.ex-card-event{ width: 168px; }

/* ── ASK FOR A LOCATION ──
   A button that says what the permission is for, before the browser's own
   prompt says nothing at all. */
.ex-locate{
  display: flex;
  align-items: center;
  gap: 13px;
  width: 100%;
  padding: 14px 16px;
  border-radius: 14px;
  background: rgba(var(--brand-rgb),0.07);
  border: 1px dashed rgba(var(--brand-rgb),0.4);
  color: var(--app-text);
  font-family: var(--font-sans);
  text-align: left;
  cursor: pointer;
}
.ex-locate:hover{ background: rgba(var(--brand-rgb),0.12); }
.ex-locate-ico{ font-size: 24px; line-height: 1; flex: none; }
.ex-locate strong{ display: block; font-size: 15px; font-weight: 700; }
.ex-locate span span{ display: block; font-size: 13px; color: var(--app-muted); margin-top: 2px; }

/* ── PEOPLE ROWS ──
   Vertical, not a rail: three of them, each one a sentence, and a sentence
   read sideways is a sentence read twice. */
.ex-stack{ display: flex; flex-direction: column; gap: 8px; }

.ex-person{
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 10px 13px;
  border-radius: 12px;
  background: var(--app-card);
  border: 1px solid var(--app-border);
  text-decoration: none;
  color: inherit;
}
a.ex-person:hover{ border-color: rgba(var(--brand-rgb),0.45); background: var(--hover); }
a.ex-person:focus-visible{ outline: 2px solid var(--app-green); outline-offset: 2px; }

.ex-person-av{
  flex: none;
  width: 34px; height: 34px;
  border-radius: 50%;
  background: var(--panel-2);
  border: 1px solid var(--app-border);
  display: flex; align-items: center; justify-content: center;
  font-size: 12px; font-weight: 700;
  color: var(--app-muted);
  overflow: hidden;
}
.ex-person-av img{ width: 100%; height: 100%; object-fit: cover; display: block; }
.ex-person-body{ flex: 1; min-width: 0; }
.ex-person-name{
  display: block;
  font-size: 14px;
  font-weight: 700;
  color: var(--app-text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ex-person-sub{
  display: block;
  font-size: 12px;
  color: var(--app-muted);
  margin-top: 1px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ex-person-val{ flex: none; }
.ex-person-price{
  font-family: var(--font-num);
  font-size: 17px;
  font-weight: 700;
  color: var(--app-green);
  font-variant-numeric: tabular-nums;
}
/* The medal says this is a place rather than a count; the metal says which
   place. The same pair the leaderboard uses, for the same reason: one
   glyph three times is one mark repeated, and a colour on its own needs a
   legend nobody is going to read. */
.ex-person-medal{ font-size: 20px; line-height: 1; }
.ex-person-medal.is-gold  { color: var(--brass-400); }
.ex-person-medal.is-silver{ color: var(--text-3); }
.ex-person-medal.is-bronze{ color: var(--brass-600); }


/* ── REPORT OR BLOCK, IN A CONVERSATION HEADER ────────────────────────────
   Pushed to the far end by the auto margin, so it sits opposite the back
   button with the name between them. Sized to the 44px minimum touch target:
   it is small and quiet, but it is not a control anybody should have to aim
   at twice, least of all in the moment they actually want it. */
.chat-conv-safety {
  margin-left: auto;
  flex-shrink: 0;
  background: none;
  border: 0;
  color: var(--text-3);
  font-size: 20px;
  line-height: 1;
  padding: 0 6px;
  min-width: 44px;
  min-height: 44px;
  cursor: pointer;
  border-radius: 10px;
}
.chat-conv-safety:hover {
  color: var(--text);
  background: var(--n-750);
}
.chat-conv-safety:focus-visible {
  outline: 2px solid var(--app-green);
  outline-offset: 2px;
}

/* The overflow button now sits beside the timestamp inside .chat-msg-time. Flex rather than
   inline-flex, and justify-content rather than the text-align it replaces:
   text-align has nothing to align once the children are flex items, and the
   clock would slide back to the left of every bubble in the thread. */
.chat-msg-time {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 2px;
}
