/*
 * TownSquare embed stylesheet.
 *
 * Scoped to the widget mount point so external host pages are not restyled.
 * TownSquare host pages also load page.css for full-page chrome.
 */
@import url("./tokens.css");

#townsquare-root {
  color-scheme: light;
  font-family: var(--font);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
}

#townsquare-root[data-townsquare-theme="auto"] {
  color-scheme: light dark;
}

#townsquare-root[data-townsquare-theme="dark"] {
  color-scheme: dark;
}

#townsquare-root,
#townsquare-root * {
  box-sizing: border-box;
}

/* ============================================================================
   LAYOUT SYSTEM

   The widget has three independent vertical zones:

   ┌─────────────────────────┐
   │    Controls (UI)         │  z-index: overlay
   │                         │
   │ ┌─────────────────────┐ │
   │ │  STAGE (sky)        │ │  180px (flex in expanded mode)
   │ │  [scene + figures]  │ │
   │ │                     │ │
   │ └─────────────────────┘ │
   │ ───────────────────────── │  ground line (1px border-top)
   │       GROUND (nameplate) │  40px
   │ ─────────────────────────┤
   │    ACTION-ZONE (toolbar) │  60px
   │     [composer + actions] │
   └─────────────────────────┘

   Key coordinate systems:
   - Props/figures position relative to stage: bottom: 0 = ground line
   - Controls/toolbar position absolutely within zones
   - Ground line: stage bottom edge = --ts-zone-ground-y

   ============================================================================ */

.townsquare {
  /* Zone heights: stage (flexible in expanded), ground (fixed nameplate band),
     action-zone (fixed toolbar area). */
  --ts-zone-stage-height: 180px;
  /* 44px, not a round 40: the self nameplate needs top offset (9px) + badge
     height (~34px) = 43px of room, or it clips into the action-zone below. */
  --ts-zone-ground-height: 44px;
  --ts-zone-action-height: 60px;

  /* Total height reserve (stage + action, ground not counted as it overlays stage). */
  --ts-layout-height-reserve: calc(var(--ts-zone-stage-height) + var(--ts-zone-action-height));

  /* Ground line coordinate system: props bottom-anchor to this plane. */
  --ts-ground-level: 0px;  /* Anchored to stage bottom edge (relative to stage) */

  /* Prop positioning details. */
  --ts-bench-seat-height: 10px;  /* Bench plank centerline offset from ground plane */

  /* UI spacing: control/toolbar/status positioning within zones. */
  --ts-controls-inset: 8px;      /* Controls panel from top-right */
  --ts-controls-gap: 6px;        /* Gap between control buttons */
  --ts-status-inset: 8px;        /* Status row from top-left */
  --ts-toolbar-inset: 10px;      /* Toolbar horizontal margin in action-zone */
  --ts-toolbar-bottom: 7px;      /* Toolbar vertical position from action-zone bottom */
  --ts-toolbar-gap: 8px;         /* Gap between toolbar items */

  /* Z-index layers (semantic naming). */
  --ts-z-base: 1;
  --ts-z-ui: 2147483001;         /* Controls, actions, toolbar: above scene */
  --ts-z-ui-above: 2147483002;   /* Toolbar (above floating actions) */

  position: relative;
  min-height: var(--ts-layout-height-reserve);
  overflow: hidden;
  background: transparent;
}

.townsquare__controls {
  position: absolute;
  z-index: var(--ts-z-ui);
  top: var(--ts-controls-inset);
  right: 4px;
  display: flex;
  align-items: center;
  flex-wrap: nowrap;
  gap: var(--ts-controls-gap);
}

/* Unified button base: shared styles for all buttons */
.townsquare__button {
  display: inline-grid;
  place-items: center;
  margin: 0;
  padding: 0;
  border: 1px solid color-mix(in oklab, var(--hair-2) 76%, transparent);
  border-radius: 8px;
  background: color-mix(in oklab, var(--surface) 78%, transparent);
  color: var(--ink-soft);
  box-shadow: 0 4px 12px -10px rgba(42, 41, 38, 0.38);
  backdrop-filter: blur(8px);
  cursor: pointer;
}

.townsquare__button:hover,
.townsquare__button:focus-visible,
.townsquare__button--active {
  color: var(--ink);
  border-color: var(--hair-2);
  background: var(--surface);
}

.townsquare__button:focus-visible {
  outline: 3px solid color-mix(in oklab, var(--you) 48%, transparent);
  outline-offset: 2px;
}

.townsquare__button:disabled {
  opacity: var(--ts-opacity-disabled);
  cursor: default;
}

/* Small buttons: 30×30 (expand, help, edit) */
.townsquare__button--sm {
  width: 30px;
  height: 30px;
}

.townsquare__button--sm svg {
  width: 16px;
  height: 16px;
}

/* Medium buttons: 42×42 (jump, high-five actions) */
.townsquare__button--md {
  width: 42px;
  height: 42px;
  box-shadow: 0 8px 20px -14px rgba(42, 41, 38, 0.42);
  background: color-mix(in oklab, var(--surface) 84%, transparent);
  font: 700 18px/1 var(--font);
}

.townsquare__button--md svg {
  width: 19px;
  height: 19px;
}

.townsquare__enable-toggle {
  position: relative;
  display: inline-flex;
  align-items: center;
  width: 36px;
  height: 30px;
  margin: 0;
  cursor: pointer;
}

.townsquare__enable-toggle-input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

.townsquare__enable-toggle-track {
  position: relative;
  display: block;
  width: 36px;
  height: 20px;
  border: 1px solid color-mix(in oklab, var(--hair-2) 76%, transparent);
  border-radius: 999px;
  background: color-mix(in oklab, var(--surface) 78%, transparent);
  box-shadow: 0 4px 12px -10px rgba(42, 41, 38, 0.38);
  backdrop-filter: blur(8px);
  transition: background 0.15s ease, border-color 0.15s ease;
}

.townsquare__enable-toggle-track::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 3px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--ink-soft);
  transform: translateY(-50%);
  transition: transform 0.15s ease, background 0.15s ease;
}

.townsquare__enable-toggle-input:checked + .townsquare__enable-toggle-track {
  background: color-mix(in oklab, var(--you) 32%, var(--surface));
  border-color: color-mix(in oklab, var(--you) 48%, var(--hair-2));
}

.townsquare__enable-toggle-input:checked + .townsquare__enable-toggle-track::after {
  transform: translate(16px, -50%);
  background: var(--you-deep);
}

.townsquare__enable-toggle-input:focus-visible + .townsquare__enable-toggle-track {
  outline: 3px solid color-mix(in oklab, var(--you) 48%, transparent);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  .townsquare__enable-toggle-track,
  .townsquare__enable-toggle-track::after {
    transition: none;
  }
}

/* ACTION-ZONE: toolbar and action buttons area at widget bottom.
   Positioned relative so toolbar anchors within it (shows zone background).
   Min-height: toolbar ~42px + bottom inset ~7px + safety margin ~2px ≈ 60px. */
.townsquare__action-zone {
  position: relative;
  background: transparent;
  min-height: var(--ts-zone-action-height);
}

#townsquare-root[data-townsquare-surface] .townsquare__action-zone {
  background: var(--action-zone-fill, var(--page));
}

/* Action buttons: jump and high-five (desktop floating position, hide on touch). */
.townsquare__actions {
  position: absolute;
  z-index: var(--ts-z-ui);
  right: 8px;
  bottom: 12px;
  display: flex;
  gap: var(--ts-toolbar-gap);
}


.townsquare__help-scrim {
  position: fixed;
  inset: 0;
  z-index: 2147483646;
  display: grid;
  place-items: center;
  padding: 24px;
  background: color-mix(in oklab, var(--ink) 34%, transparent);
  backdrop-filter: blur(6px);
}

.townsquare__help-scrim[hidden] {
  display: none;
}

.townsquare__help-panel {
  width: min(320px, calc(100vw - 48px));
  max-height: min(80vh, 520px);
  overflow: auto;
  padding: 16px;
  border: 1px solid var(--hair-2);
  border-radius: 12px;
  background: var(--surface);
  color: var(--ink);
  box-shadow: var(--tray-shadow);
}

.townsquare__help-panel strong {
  display: block;
  margin-bottom: 6px;
  font-size: 0.9rem;
}

.townsquare__help-panel p {
  margin: 0 0 8px;
  color: var(--ink-soft);
  font-size: 0.84rem;
  line-height: 1.35;
}

.townsquare__help-panel p:last-of-type {
  margin-bottom: 0;
}

.townsquare__help-links {
  margin-bottom: 0;
}

.townsquare__help-panel a {
  color: var(--you-deep);
  font-size: 0.84rem;
  font-weight: 650;
  text-decoration: none;
}

.townsquare__help-panel a:hover,
.townsquare__help-panel a:focus-visible {
  text-decoration: underline;
}

/* STATUS ROW: contains nameplate and other status elements. Positioned at top-left. */
.townsquare__status {
  position: absolute;
  z-index: var(--ts-z-ui);
  top: var(--ts-status-inset);
  left: 4px;
  right: 124px;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 0;
  color: var(--muted);
  font-size: 0.9rem;
  line-height: 1.3;
  pointer-events: none;
}

.townsquare__status[hidden] {
  display: none;
}

.townsquare__stage {
  position: relative;
  height: var(--ts-zone-stage-height);
  border-radius: var(--r) var(--r) 0 0;
  background: transparent;
  /* Horizontal touch swipes walk; vertical gestures still pan the host page. */
  touch-action: pan-y pinch-zoom;
  /* Size speech bubbles/trays/composers against stage width (cqw), not viewport,
     so long lines don't bleed past the clipped stage edge. */
  container-type: inline-size;
}

/* GROUND ZONE: nameplate band between stage and action-zone.
   Fixed height with ground line as top border (marks walkable plane for props).
   Independent zone, painted only with data-townsquare-surface (preview or pasted CSS). */
.townsquare__ground {
  height: var(--ts-zone-ground-height);
  background: transparent;
  border-top: 1px solid transparent;
}

#townsquare-root[data-townsquare-surface] .townsquare__ground {
  background: var(--ground-fill, var(--page));
  border-top: 1px solid var(--ground-line, var(--ground));
}

/* ZONE THEMING: each zone colored when data-townsquare-surface is active.
   Live preview sets this via applySiteStyle; hosted embeds set it at mount.
   Only palette tokens are pasted (buildSiteCss) — all color paint is here.
   Fallbacks (--page, --ground) maintain compatibility with pre-rename CSS. */
#townsquare-root[data-townsquare-surface] .townsquare__stage {
  background: var(--scene);
}

.townsquare .prop {
  position: absolute;
  bottom: var(--ts-ground-level);
  transform: translateX(-50%);
  color: var(--prop-ink);
  opacity: 0.5;
  pointer-events: none;
}

.townsquare .prop svg {
  width: 100%;
  height: 100%;
  display: block;
  overflow: visible;
}

.townsquare .prop svg * {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

/* Neighbouring-town fingerposts at the stage edges. */
.townsquare .townsquare-signpost {
  position: absolute;
  bottom: var(--ts-ground-level);
  display: block;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--prop-ink);
  cursor: pointer;
  z-index: 3;
}

.townsquare .townsquare-signpost--left {
  left: 12px;
}

.townsquare .townsquare-signpost--right {
  right: 12px;
}

.townsquare-signpost__sign {
  display: block;
  width: 22px;
  height: 38px;
  opacity: 0.6;
  transition: opacity var(--ts-transition-short) ease, color var(--ts-transition-short) ease;
}

.townsquare-signpost__sign svg {
  width: 100%;
  height: 100%;
  display: block;
  overflow: visible;
}

.townsquare-signpost__sign svg * {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.3;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

.townsquare-signpost:hover .townsquare-signpost__sign,
.townsquare-signpost:focus-visible .townsquare-signpost__sign,
.townsquare-signpost--near .townsquare-signpost__sign {
  color: var(--ink);
  opacity: 1;
}

.townsquare-signpost:focus-visible {
  outline: none;
}

.townsquare-signpost:focus-visible .townsquare-signpost__sign {
  outline: 3px solid color-mix(in oklab, var(--you) 48%, transparent);
  outline-offset: 2px;
  border-radius: 4px;
}

.townsquare-signpost__hint {
  position: absolute;
  bottom: calc(100% + 4px);
  padding: 2px 7px;
  border-radius: 999px;
  background: var(--you);
  color: #fff;
  font-size: 10px;
  line-height: 1.3;
  white-space: nowrap;
  opacity: 0;
  transform: translateY(2px);
  transition: opacity 120ms ease, transform 120ms ease;
  pointer-events: none;
}

.townsquare-signpost--left .townsquare-signpost__hint {
  left: 0;
}

.townsquare-signpost--right .townsquare-signpost__hint {
  right: 0;
}

.townsquare-signpost--near .townsquare-signpost__hint {
  opacity: 1;
  transform: translateY(0);
}

/* Owner message board: a clickable notice prop with an unread "!" badge. */
.townsquare .townsquare-board {
  --board-accent: var(--you);
  position: absolute;
  bottom: var(--ts-ground-level);
  transform: translateX(-50%);
  display: block;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--prop-ink);
  cursor: pointer;
  z-index: 3;
}

.townsquare-board__art {
  display: block;
  width: 100%;
  height: 100%;
  opacity: 0.6;
  transition: opacity var(--ts-transition-short) ease, color var(--ts-transition-short) ease;
}

.townsquare-board__art svg {
  width: 100%;
  height: 100%;
  display: block;
  overflow: visible;
}

.townsquare-board__art svg * {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

.townsquare-board:hover .townsquare-board__art,
.townsquare-board:focus-visible .townsquare-board__art {
  color: var(--ink);
  opacity: 1;
}

.townsquare-board:focus-visible {
  outline: none;
}

.townsquare-board:focus-visible .townsquare-board__art {
  outline: 3px solid color-mix(in oklab, var(--you) 48%, transparent);
  outline-offset: 2px;
  border-radius: 4px;
}

/* Art variants fill the panel differently; the line-art frame is shared. */
.townsquare-board--corkboard .townsquare-board__art .panel {
  fill: color-mix(in oklab, var(--board-accent) 18%, var(--surface));
}

.townsquare-board--chalkboard .townsquare-board__art .panel {
  fill: color-mix(in oklab, var(--ink) 78%, var(--surface));
}

.townsquare-board--chalkboard .townsquare-board__art .note {
  stroke: var(--surface);
}

/* "sign" keeps the default outline-only panel. */

.townsquare-board__badge {
  position: absolute;
  top: -6px;
  right: -6px;
  display: none;
  width: 16px;
  height: 16px;
  border-radius: 999px;
  background: var(--board-accent);
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  line-height: 16px;
  text-align: center;
  box-shadow: 0 1px 3px -1px rgba(42, 41, 38, 0.5);
}

.townsquare-board--unread .townsquare-board__badge {
  display: block;
}

/* Message board modal — mirrors the connections overlay shape. */
.townsquare-board-modal {
  --board-accent: var(--you);
  position: absolute;
  inset: 0;
  z-index: 2147483005;
  display: flex;
  align-items: center;
  justify-content: center;
}

.townsquare-board-modal__backdrop {
  position: absolute;
  inset: 0;
  background: color-mix(in oklab, var(--ink) 38%, transparent);
}

.townsquare-board-modal__panel {
  position: relative;
  z-index: 1;
  width: min(320px, 88%);
  max-height: 86%;
  overflow: auto;
  padding: 14px 16px;
  border: 1px solid var(--hair-2);
  border-top: 3px solid var(--board-accent);
  border-radius: 14px;
  background: var(--surface);
  color: var(--ink);
  box-shadow: 0 18px 40px -24px rgba(42, 41, 38, 0.6);
}

.townsquare-board-modal__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 10px;
}

.townsquare-board-modal__title {
  font-size: 14px;
  font-weight: 600;
}

.townsquare-board-modal__close {
  border: 0;
  background: transparent;
  color: var(--ink-soft);
  font-size: 20px;
  line-height: 1;
  padding: 0 2px;
  cursor: pointer;
}

.townsquare-board-modal__close:hover,
.townsquare-board-modal__close:focus-visible {
  color: var(--ink);
}

.townsquare-board-modal__body {
  margin: 0;
  font-size: 13px;
  line-height: 1.5;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* "Walk over" modal listing the towns on one side. */
.townsquare-connections {
  position: absolute;
  inset: 0;
  z-index: 2147483005;
  display: flex;
  align-items: center;
  justify-content: center;
}

.townsquare-connections__backdrop {
  position: absolute;
  inset: 0;
  background: color-mix(in oklab, var(--ink) 38%, transparent);
}

.townsquare-connections__panel {
  position: relative;
  z-index: 1;
  width: min(320px, 86%);
  max-height: 86%;
  overflow: auto;
  padding: 14px 16px;
  border: 1px solid var(--hair-2);
  border-radius: 14px;
  background: var(--surface);
  color: var(--ink);
  box-shadow: 0 18px 40px -24px rgba(42, 41, 38, 0.6);
}

.townsquare-connections__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 10px;
}

.townsquare-connections__title {
  font-size: 14px;
  font-weight: 600;
}

.townsquare-connections__close {
  border: 0;
  background: transparent;
  color: var(--ink-soft);
  font-size: 20px;
  line-height: 1;
  padding: 0 2px;
  cursor: pointer;
}

.townsquare-connections__close:hover,
.townsquare-connections__close:focus-visible {
  color: var(--ink);
}

.townsquare-connections__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.townsquare-connections__item {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-areas:
    "name go"
    "host go";
  align-items: center;
  column-gap: 10px;
  padding: 9px 11px;
  border: 1px solid color-mix(in oklab, var(--hair-2) 70%, transparent);
  border-radius: 8px;
  text-decoration: none;
  color: inherit;
  transition: border-color 120ms ease, background 120ms ease;
}

.townsquare-connections__item:hover,
.townsquare-connections__item:focus-visible {
  border-color: var(--you);
  background: var(--you-tint);
  outline: none;
}

.townsquare-connections__item-name {
  grid-area: name;
  font-size: 13px;
}

.townsquare-connections__item-host {
  grid-area: host;
  font-size: 11px;
  color: var(--ink-soft);
}

.townsquare-connections__item-go {
  grid-area: go;
  font-size: 12px;
  color: var(--you-deep);
  white-space: nowrap;
}

.townsquare--quiet {
  min-height: 46px;
  overflow: visible;
}

.townsquare--quiet .townsquare__status,
.townsquare--quiet .townsquare__actions,
.townsquare--quiet .townsquare__toolbar,
.townsquare--quiet .townsquare__stage {
  display: none;
}

.townsquare--quiet .townsquare__button--expand {
  display: none;
}

.townsquare--expanded {
  position: fixed;
  inset: 0;
  z-index: 2147483000;
  width: 100vw;
  height: 100dvh;
  /* Reserve room at the bottom for the docked toolbar (absolute, bottom:7px) so
     the flex stage — and the figures' name tags at its base — never sit under
     the chat input. Mirrors --ts-toolbar-reserve in non-expanded mode. No
     horizontal padding: it would expose the container background as a colored
     strip down each side of the full-bleed stage. */
  padding: 0 0 var(--ts-toolbar-reserve);
  border-top: 0;
  border-radius: 0;
  /* Opaque backdrop so the fullscreen overlay always covers the host page. The
     flat stage paints --scene over the whole scene, so this only shows through
     where --scene is transparent (then reading as neutral Canvas, never the
     ground color) or behind the docked toolbar. */
  background: Canvas;
  display: flex;
  flex-direction: column;
  overflow-x: hidden;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}

.townsquare--expanded::after {
  content: "";
  display: block;
  flex: 0 0 var(--ts-keyboard-scroll-room, 0px);
}

/* EXPANDED MODE: stage flex-grows, controls/status repositioned with larger insets. */
.townsquare--expanded {
  --ts-controls-inset: 10px;
  --ts-status-inset: 10px;
}

.townsquare--expanded .townsquare__controls {
  right: 18px;
}

.townsquare--expanded .townsquare__status {
  left: 18px;
  right: 148px;
}

.townsquare--expanded .townsquare__stage {
  flex: 1 1 auto;
  min-height: 0;
}

.townsquare .prop--tree .canopy {
  fill: var(--tree-canopy);
  stroke: var(--tree-canopy);
}

.townsquare .prop--tree .trunk {
  fill: var(--tree-trunk);
  stroke: var(--tree-trunk);
}

.townsquare .prop--lamp {
  z-index: 1;
}

.townsquare .prop__light {
  position: absolute;
  top: 16%;
  left: 75%;
  width: 90px;
  height: 88%;
  transform: translateX(-50%);
  clip-path: polygon(38% 0%, 62% 0%, 100% 100%, 0% 100%);
  background: linear-gradient(
    to bottom,
    rgba(253, 230, 160, 0.5) 0%,
    rgba(253, 230, 160, 0.2) 42%,
    rgba(253, 230, 160, 0.04) 100%
  );
  opacity: 0.34;
}

.townsquare__birds {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 0;
}

/* Ambient drifting clouds — only painted in expanded mode, where the taller
   sky gives them room. Kept display:none while collapsed so the drift keyframes
   don't tick in the background. */
.townsquare__clouds {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
  z-index: 0;
  display: none;
}

.townsquare--expanded .townsquare__clouds {
  display: block;
}

.cloud {
  position: absolute;
  top: var(--cloud-top, 12%);
  left: 0;
  width: calc(64px * var(--cloud-scale, 1));
  color: rgba(255, 255, 255, 0.82);
  filter: drop-shadow(0 2px 6px rgba(70, 90, 120, 0.12));
  will-change: transform;
  animation: cloud-drift var(--cloud-duration, 60s) linear infinite;
  animation-delay: var(--cloud-delay, 0s);
}

.cloud svg {
  display: block;
  width: 100%;
  height: auto;
}

.cloud svg path {
  fill: currentColor;
}

@keyframes cloud-drift {
  from { transform: translateX(-140px); }
  to { transform: translateX(calc(100cqw + 40px)); }
}

/* Freeze the drift but keep the clouds in place — the negative start delays
   leave them spread across the sky rather than parked off-screen. */
@media (prefers-reduced-motion: reduce) {
  .cloud { animation-play-state: paused; }
}

.bird {
  position: absolute;
  left: calc(var(--bird-x) * 100%);
  bottom: calc(var(--ts-ground-level) + var(--bird-lift, 0px));
  width: 12px;
  height: 8px;
  transform: translateX(-50%);
  color: var(--prop-ink);
  opacity: 0.45;
  pointer-events: none;
}

.bird svg {
  width: 100%;
  height: 100%;
  display: block;
  overflow: visible;
}

.bird svg * {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.4;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

.bird--arriving svg,
.bird--fleeing svg {
  transform: scaleX(var(--bird-facing, 1));
  transform-origin: 50% 50%;
}

.bird--arriving .bird-wing,
.bird--fleeing .bird-wing {
  transform-box: fill-box;
  animation-duration: 0.16s;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
  animation-direction: alternate;
}

.bird--arriving .bird-wing--left,
.bird--fleeing .bird-wing--left {
  transform-origin: 100% 0%;
  animation-name: bird-wing-left;
}

.bird--arriving .bird-wing--right,
.bird--fleeing .bird-wing--right {
  transform-origin: 0% 0%;
  animation-name: bird-wing-right;
}

.bird--arriving {
  animation: bird-arrive 1.2s ease-out forwards;
}

.bird--perched {
  width: 16px;
  height: 11px;
  animation: bird-bob 3s ease-in-out infinite;
}

.bird--perched svg {
  transform: scaleX(var(--bird-facing, 1));
  transform-origin: 50% 100%;
}

.bird--fleeing {
  animation: bird-flee 0.7s ease-in forwards;
}

@keyframes bird-wing-left {
  from {
    transform: rotate(16deg);
  }

  to {
    transform: rotate(-22deg);
  }
}

@keyframes bird-wing-right {
  from {
    transform: rotate(-16deg);
  }

  to {
    transform: rotate(22deg);
  }
}

@keyframes bird-arrive {
  from {
    left: calc(var(--bird-from) * 100%);
    bottom: calc(var(--ts-ground-level) + var(--bird-lift, 0px) + 24px);
    opacity: 0;
  }

  25% {
    bottom: calc(var(--ts-ground-level) + var(--bird-lift, 0px) + 20px);
  }

  50% {
    bottom: calc(var(--ts-ground-level) + var(--bird-lift, 0px) + 26px);
  }

  75% {
    bottom: calc(var(--ts-ground-level) + var(--bird-lift, 0px) + 18px);
  }

  to {
    left: calc(var(--bird-to) * 100%);
    bottom: calc(var(--ts-ground-level) + var(--bird-lift, 0px));
    opacity: 0.45;
  }
}

@keyframes bird-bob {
  0%,
  100% {
    transform: translateX(-50%) translateY(0);
  }

  50% {
    transform: translateX(-50%) translateY(-2px);
  }
}

@keyframes bird-flee {
  from {
    left: calc(var(--bird-x) * 100%);
    bottom: calc(var(--ts-ground-level) + var(--bird-lift, 0px));
    opacity: 0.45;
  }

  35% {
    bottom: calc(var(--ts-ground-level) + var(--bird-lift, 0px) + 28px);
  }

  to {
    left: calc((var(--bird-x) + var(--bird-dir, 1) * 0.18) * 100%);
    bottom: calc(var(--ts-ground-level) + var(--bird-lift, 0px) + 42px);
    opacity: 0;
  }
}

.townsquare-avatar {
  --walk-cycle: 0.8s;
  --townsquare-avatar-walk-filter: ;
  --townsquare-avatar-shade-filter: ;
  --townsquare-avatar-light-filter: ;
  /* Horizontal position rides the compositor: --avatar-x (0–100, set per frame
     by renderAvatar) is a percentage of the stage width via cqw, and the
     trailing translateX(-50%) centres the figure on it. Keeping position off
     `left` means walking never dirties layout. Jump/high-five keyframes reuse
     --avatar-pos so their hop composes on top of the live position. */
  --avatar-pos: translateX(calc(var(--avatar-x, 50) * 1cqw)) translateX(-50%);
  position: absolute;
  bottom: var(--ts-ground-level);
  left: 0;
  width: 18px;
  height: 46px;
  transform: var(--avatar-pos);
  transition: transform var(--ts-transition-micro) linear;
  /* --speak-order: most recent speaker stacks on top when columns must overlap */
  z-index: var(--speak-order, 0);
}

.townsquare-avatar.townsquare-avatar--flipped svg {
  transform: scaleX(-1);
  transform-origin: 50% 50%;
}

.townsquare-avatar svg {
  width: 100%;
  height: 100%;
  display: block;
  overflow: visible;
}

.townsquare-avatar svg * {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

.townsquare-avatar svg circle.head {
  fill: currentColor;
}

.townsquare-avatar--self {
  color: var(--you);
  z-index: 2147483001;
}

.townsquare-avatar--profile-open {
  z-index: 2147483004;
}

.townsquare-avatar--peer { color: var(--other); }

/* peers are clickable (recent-message tray); the cursor also marks the slim
   figure as interactive, and on iOS Safari makes taps produce click events */
.townsquare-avatar--peer { cursor: pointer; }

/* widen the 18px figure's click/tap zone so "click a person" doesn't need
   precision — clicks here open their tray instead of walking */
.townsquare-avatar--peer::before {
  content: "";
  position: absolute;
  inset: -8px -14px;
}

/* a character's lingering bubbles are part of "them": clicking one opens
   their tray (the container keeps pointer-events: none for walk taps) */
.townsquare-avatar--peer .townsquare-avatar__bubble {
  pointer-events: auto;
  cursor: pointer;
}

/* while the tray is open the faded-out bubbles must not block clicks on it */
.townsquare-avatar--tray-open .townsquare-avatar__bubble {
  pointer-events: none;
}

/* Reading a character's tray outranks everything in the scene — neighbours'
   speak-order stacking AND the self figure (2147483001), so the open tray is
   never painted over by your own figure/bubbles. Stays below the overlay panels
   (message board / connections, 2147483005) and the help modal. */
.townsquare-avatar--has-history:hover,
.townsquare-avatar--has-history:focus-within,
.townsquare-avatar--tray-open { z-index: 2147483003; }
.townsquare-avatar:is(.townsquare-avatar--walking, .townsquare-avatar--shaded, .townsquare-avatar--lit) {
  filter: var(--townsquare-avatar-shade-filter) var(--townsquare-avatar-walk-filter) var(--townsquare-avatar-light-filter);
}
.townsquare-avatar.townsquare-avatar--walking { --townsquare-avatar-walk-filter: drop-shadow(0 2px 5px rgba(0,0,0,0.12)); }
.townsquare-avatar--jumping {
  animation: presence-jump var(--ts-transition-long) cubic-bezier(0.2, 0.8, 0.2, 1);
}
.townsquare-avatar--high-five {
  animation: presence-high-five-root 760ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.townsquare-avatar--reading-away > svg,
.townsquare-avatar--reading-away .townsquare-avatar__below {
  opacity: 0.22;
}

/* Long-idle figures fade away entirely; returning to the tab brings them back. */
.townsquare-avatar--asleep {
  opacity: 0;
  pointer-events: none;
  transition: opacity 600ms ease;
}

.townsquare-avatar--reading-away::after {
  content: "Zz";
  position: absolute;
  left: 50%;
  top: -12px;
  transform: translateX(-50%) rotate(-8deg);
  padding: 1px 4px;
  border-radius: 999px;
  background: color-mix(in oklab, var(--surface) 70%, transparent);
  color: color-mix(in oklab, currentColor 58%, transparent);
  font-size: 10px;
  font-weight: 720;
  line-height: 1;
  letter-spacing: 0;
  pointer-events: none;
}

.townsquare-avatar--typing:not(.townsquare-avatar--raised-hand)::after {
  content: "";
  position: absolute;
  left: 50%;
  top: -16px;
  width: 26px;
  height: 15px;
  padding: 0;
  transform: translateX(-50%);
  border-radius: 999px;
  background-color: color-mix(in oklab, var(--surface) 88%, transparent);
  background-image:
    radial-gradient(circle, currentColor 2px, transparent 2.5px),
    radial-gradient(circle, currentColor 2px, transparent 2.5px),
    radial-gradient(circle, currentColor 2px, transparent 2.5px);
  background-position: 5px 6px, 11px 8px, 17px 8px;
  background-repeat: no-repeat;
  background-size: 5px 5px;
  color: color-mix(in oklab, currentColor 62%, transparent);
  box-shadow: 0 1px 3px rgba(0,0,0,0.12);
  animation: presence-typing 900ms ease-in-out infinite;
  pointer-events: none;
}

@keyframes presence-typing {
  0%, 80%, 100% { background-position: 5px 8px, 11px 8px, 17px 8px; }
  20% { background-position: 5px 4px, 11px 8px, 17px 8px; }
  40% { background-position: 5px 8px, 11px 4px, 17px 8px; }
  60% { background-position: 5px 8px, 11px 8px, 17px 4px; }
}

@media (prefers-reduced-motion: reduce) {
  .townsquare-avatar--typing::after { animation: none; }
}

.townsquare-avatar--raised-hand::after {
  content: "🙌";
  position: absolute;
  left: 50%;
  top: -18px;
  transform: translateX(-50%);
  font-size: 13px;
  line-height: 1;
  filter: drop-shadow(0 1px 2px rgba(0,0,0,0.18));
  pointer-events: none;
}

/* Pose anchors: each pose sinks the element box below the plane by exactly the
   distance its lowest rotated foot ends above the box bottom, so the feet stay
   planted on the ground line while the pose bends the legs. Derived from the
   figure geometry (figure.mjs, 0.9 px/unit; feet at y=42 = box bottom) and the
   pose rotations below:
   - sitting (bench): hip 70–74°, knee −80/−84° → lowest foot at y≈38.3
     → drop (42 − 38.3) × 0.9 ≈ 3.3px. The hip (torso end, y=26 → 14.4px above
     the box bottom) then lands 14.4 − 3.3 ≈ 11.1px above the plane, resting on
     the 10px bench seat plank (--ts-bench-seat-height + half strokes).
   - resting (under a tree): hip 118/120°, knee −85/−88°, core −10° → lowest
     foot at y≈33.8 → drop (42 − 33.8) × 0.9 ≈ 7.4px. */
.townsquare-avatar--sitting { bottom: calc(var(--ts-ground-level) - 3.3px); }
.townsquare-avatar--resting { bottom: calc(var(--ts-ground-level) - 7.4px); }
.townsquare-avatar--shaded { --townsquare-avatar-shade-filter: brightness(0.88); }
.townsquare-avatar--lit {
  --townsquare-avatar-light-filter: drop-shadow(0 0 3px rgba(253, 230, 160, 0.7)) drop-shadow(0 0 6px rgba(253, 230, 160, 0.25));
}

/* Walk-sandbox preview: fills the preview box instead of standing on a stage. */
.townsquare-avatar--preview {
  position: static;
  width: 100%;
  height: 100%;
  transform: none;
  transition: none;
}

.townsquare-avatar--preview.townsquare-avatar--walking { filter: none; }
.townsquare-avatar--preview svg * { stroke-width: 1.4; }

.townsquare-avatar svg :is(g.figure-core, g.joint) {
  transform-box: view-box;
}

.townsquare-avatar svg g.figure-core { transform-origin: 10px 24px; }
.townsquare-avatar svg g.arm-l { transform-origin: 9.4px 14px; }
.townsquare-avatar svg g.arm-r { transform-origin: 10.6px 14px; }
.townsquare-avatar svg g.elbow-l { transform-origin: 6.1px 20px; }
.townsquare-avatar svg g.elbow-r { transform-origin: 13.9px 20px; }
.townsquare-avatar svg g.leg-l { transform-origin: 9.2px 26px; }
.townsquare-avatar svg g.leg-r { transform-origin: 10.8px 26px; }
.townsquare-avatar svg g.knee-l { transform-origin: 7.1px 34px; }
.townsquare-avatar svg g.knee-r { transform-origin: 12.9px 34px; }

.townsquare-avatar.townsquare-avatar--walking svg :is(g.figure-core, g.joint) {
  animation-duration: var(--walk-cycle);
  /* linear between frames: the easing lives in the keyframe spacing itself,
     so motion doesn't decelerate/re-accelerate at every keyframe. */
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}

.townsquare-avatar.townsquare-avatar--walking svg g.figure-core { animation-name: presence-body-bob; }
.townsquare-avatar.townsquare-avatar--walking svg g.leg-l { animation-name: presence-leg-a; }
.townsquare-avatar.townsquare-avatar--walking svg g.leg-r { animation-name: presence-leg-b; }
.townsquare-avatar.townsquare-avatar--walking svg g.knee-l { animation-name: presence-knee-a; }
.townsquare-avatar.townsquare-avatar--walking svg g.knee-r { animation-name: presence-knee-b; }
.townsquare-avatar.townsquare-avatar--walking svg g.arm-l { animation-name: presence-arm-a; }
.townsquare-avatar.townsquare-avatar--walking svg g.arm-r { animation-name: presence-arm-b; }
.townsquare-avatar.townsquare-avatar--walking svg g.elbow-l { animation-name: presence-forearm-a; }
.townsquare-avatar.townsquare-avatar--walking svg g.elbow-r { animation-name: presence-forearm-b; }

.townsquare-avatar--jumping svg :is(g.figure-core, g.joint) {
  animation-duration: 560ms;
  animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1);
  animation-iteration-count: 1;
}

.townsquare-avatar--jumping svg g.figure-core { animation-name: presence-jump-body; }
.townsquare-avatar--jumping svg g.leg-l { animation-name: presence-jump-leg-a; }
.townsquare-avatar--jumping svg g.leg-r { animation-name: presence-jump-leg-b; }
.townsquare-avatar--jumping svg g.knee-l { animation-name: presence-jump-knee-a; }
.townsquare-avatar--jumping svg g.knee-r { animation-name: presence-jump-knee-b; }
.townsquare-avatar--jumping svg g.arm-l { animation-name: presence-jump-arm-a; }
.townsquare-avatar--jumping svg g.arm-r { animation-name: presence-jump-arm-b; }
.townsquare-avatar--jumping svg g.elbow-l { animation-name: presence-jump-forearm-a; }
.townsquare-avatar--jumping svg g.elbow-r { animation-name: presence-jump-forearm-b; }

.townsquare-avatar.townsquare-avatar--high-five svg :is(g.figure-core, g.joint) {
  animation-duration: 760ms;
  animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1);
  animation-iteration-count: 1;
}

.townsquare-avatar.townsquare-avatar--high-five svg g.figure-core { animation-name: presence-high-five-body; }
.townsquare-avatar.townsquare-avatar--high-five svg g.arm-l { animation-name: presence-high-five-arm-idle; }
.townsquare-avatar.townsquare-avatar--high-five svg g.elbow-l { animation-name: presence-high-five-forearm-idle; }
.townsquare-avatar.townsquare-avatar--high-five svg g.arm-r { animation-name: presence-high-five-arm; }
.townsquare-avatar.townsquare-avatar--high-five svg g.elbow-r { animation-name: presence-high-five-forearm; }

/*
  Walk cycle, figure facing right (the flipped state mirrors it for left —
  the pose data itself never changes with direction).

  Sign conventions in the un-mirrored pose:
    legs/arms  positive deg = limb swung backward, negative = forward
    knees      positive deg = flexion (heel toward butt); never negative
    elbows     negative deg = flexion (hand swings forward); never positive

  Phase layout: left-leg heel strike at 0%, right-leg heel strike at 50%.
  Each leg spends ~60% of the cycle in stance and ~40% in swing, with peak
  knee flexion mid-swing for ground clearance.
*/
@keyframes presence-body-bob {
  0%, 50%, 100% { transform: translateY(0.4px) rotate(0.6deg); }
  6%, 56% { transform: translateY(0.7px) rotate(0.9deg); }
  28%, 78% { transform: translateY(-0.6px) rotate(1.2deg); }
}

@keyframes presence-leg-a {
  0%, 100% { transform: rotate(-20deg); }
  8% { transform: rotate(-18deg); }
  30% { transform: rotate(-2deg); }
  45% { transform: rotate(10deg); }
  55% { transform: rotate(14deg); }
  62% { transform: rotate(15deg); }
  75% { transform: rotate(-4deg); }
  88% { transform: rotate(-17deg); }
  94% { transform: rotate(-21deg); }
}

@keyframes presence-leg-b {
  0%, 100% { transform: rotate(12deg); }
  5% { transform: rotate(14deg); }
  12% { transform: rotate(15deg); }
  25% { transform: rotate(-4deg); }
  38% { transform: rotate(-17deg); }
  44% { transform: rotate(-21deg); }
  50% { transform: rotate(-20deg); }
  58% { transform: rotate(-18deg); }
  80% { transform: rotate(-2deg); }
  95% { transform: rotate(10deg); }
}

@keyframes presence-knee-a {
  0%, 100% { transform: rotate(4deg); }
  8% { transform: rotate(12deg); }
  20% { transform: rotate(6deg); }
  40% { transform: rotate(5deg); }
  55% { transform: rotate(18deg); }
  65% { transform: rotate(32deg); }
  72% { transform: rotate(38deg); }
  80% { transform: rotate(28deg); }
  90% { transform: rotate(8deg); }
  96% { transform: rotate(3deg); }
}

@keyframes presence-knee-b {
  0%, 100% { transform: rotate(14deg); }
  5% { transform: rotate(18deg); }
  15% { transform: rotate(32deg); }
  22% { transform: rotate(38deg); }
  30% { transform: rotate(28deg); }
  40% { transform: rotate(8deg); }
  46% { transform: rotate(3deg); }
  50% { transform: rotate(4deg); }
  58% { transform: rotate(12deg); }
  70% { transform: rotate(6deg); }
  90% { transform: rotate(5deg); }
}

@keyframes presence-arm-a {
  0%, 100% { transform: rotate(12deg); }
  12% { transform: rotate(10deg); }
  30% { transform: rotate(1deg); }
  50% { transform: rotate(-10deg); }
  60% { transform: rotate(-11deg); }
  75% { transform: rotate(-5deg); }
  90% { transform: rotate(7deg); }
}

@keyframes presence-arm-b {
  0%, 100% { transform: rotate(-10deg); }
  10% { transform: rotate(-11deg); }
  25% { transform: rotate(-5deg); }
  40% { transform: rotate(7deg); }
  50% { transform: rotate(12deg); }
  62% { transform: rotate(10deg); }
  80% { transform: rotate(1deg); }
}

@keyframes presence-forearm-a {
  0%, 100% { transform: rotate(-6deg); }
  15% { transform: rotate(-7deg); }
  35% { transform: rotate(-10deg); }
  55% { transform: rotate(-14deg); }
  68% { transform: rotate(-15deg); }
  85% { transform: rotate(-9deg); }
}

@keyframes presence-forearm-b {
  0%, 100% { transform: rotate(-13deg); }
  5% { transform: rotate(-14deg); }
  18% { transform: rotate(-15deg); }
  35% { transform: rotate(-9deg); }
  50% { transform: rotate(-6deg); }
  65% { transform: rotate(-7deg); }
  85% { transform: rotate(-10deg); }
}

@keyframes presence-jump {
  0%, 100% { transform: var(--avatar-pos) translateY(0); }
  12% { transform: var(--avatar-pos) translateY(1px) scaleY(0.96); }
  45% { transform: var(--avatar-pos) translateY(-30px) scaleY(1.03); }
  78% { transform: var(--avatar-pos) translateY(-6px); }
}

@keyframes presence-jump-body {
  0%, 100% { transform: translateY(0) rotate(0); }
  12% { transform: translateY(1.8px) rotate(-2deg); }
  36%, 62% { transform: translateY(-0.8px) rotate(2deg); }
  82% { transform: translateY(1px) rotate(-1deg); }
}

@keyframes presence-jump-leg-a {
  0%, 100% { transform: rotate(-4deg); }
  12% { transform: rotate(20deg); }
  38%, 62% { transform: rotate(-28deg); }
  82% { transform: rotate(12deg); }
}

@keyframes presence-jump-leg-b {
  0%, 100% { transform: rotate(4deg); }
  12% { transform: rotate(24deg); }
  38%, 62% { transform: rotate(24deg); }
  82% { transform: rotate(14deg); }
}

@keyframes presence-jump-knee-a {
  0%, 100% { transform: rotate(6deg); }
  12% { transform: rotate(34deg); }
  38%, 62% { transform: rotate(48deg); }
  82% { transform: rotate(18deg); }
}

@keyframes presence-jump-knee-b {
  0%, 100% { transform: rotate(6deg); }
  12% { transform: rotate(38deg); }
  38%, 62% { transform: rotate(42deg); }
  82% { transform: rotate(18deg); }
}

@keyframes presence-jump-arm-a {
  0%, 100% { transform: rotate(6deg); }
  12% { transform: rotate(24deg); }
  38%, 62% { transform: rotate(118deg); }
  82% { transform: rotate(28deg); }
}

@keyframes presence-jump-arm-b {
  0%, 100% { transform: rotate(-6deg); }
  12% { transform: rotate(-24deg); }
  38%, 62% { transform: rotate(-118deg); }
  82% { transform: rotate(-28deg); }
}

@keyframes presence-jump-forearm-a {
  0%, 100% { transform: rotate(-8deg); }
  12% { transform: rotate(-18deg); }
  38%, 62% { transform: rotate(10deg); }
  82% { transform: rotate(-14deg); }
}

@keyframes presence-jump-forearm-b {
  0%, 100% { transform: rotate(-8deg); }
  12% { transform: rotate(-18deg); }
  38%, 62% { transform: rotate(10deg); }
  82% { transform: rotate(-14deg); }
}

@keyframes presence-high-five-root {
  0%, 100% { transform: var(--avatar-pos) translateY(0); }
  42% { transform: var(--avatar-pos) translateY(-2px); }
  56% { transform: var(--avatar-pos) translateY(1px); }
}

@keyframes presence-high-five-body {
  0%, 100% { transform: translateY(0) rotate(1deg); }
  28%, 62% { transform: translateY(-0.8px) rotate(5deg); }
}

@keyframes presence-high-five-arm {
  0%, 100% { transform: rotate(-132deg); }
  30% { transform: rotate(-112deg); }
  46%, 58% { transform: rotate(-82deg); }
  72% { transform: rotate(-118deg); }
}

@keyframes presence-high-five-forearm {
  0%, 100% { transform: rotate(16deg); }
  30% { transform: rotate(6deg); }
  46%, 58% { transform: rotate(-8deg); }
  72% { transform: rotate(10deg); }
}

@keyframes presence-high-five-arm-idle {
  0%, 100% { transform: rotate(8deg); }
  42%, 62% { transform: rotate(16deg); }
}

@keyframes presence-high-five-forearm-idle {
  0%, 100% { transform: rotate(-8deg); }
  42%, 62% { transform: rotate(-14deg); }
}

.townsquare-avatar--sitting svg :is(g.figure-core, g.joint),
.townsquare-avatar--resting svg :is(g.figure-core, g.joint) {
  animation: none !important;
  transition: transform var(--ts-transition-standard) ease;
}

.townsquare-avatar--sitting svg g.figure-core { transform: translateY(0) rotate(0); }
.townsquare-avatar--sitting svg g.leg-l { transform: rotate(74deg); }
.townsquare-avatar--sitting svg g.leg-r { transform: rotate(70deg); }
.townsquare-avatar--sitting svg g.knee-l { transform: rotate(-84deg); }
.townsquare-avatar--sitting svg g.knee-r { transform: rotate(-80deg); }
.townsquare-avatar--sitting svg g.arm-l,
.townsquare-avatar--sitting svg g.arm-r { transform: rotate(16deg); }
.townsquare-avatar--sitting svg g.elbow-l { transform: rotate(24deg); }
.townsquare-avatar--sitting svg g.elbow-r { transform: rotate(-24deg); }

.townsquare-avatar--resting svg g.figure-core { transform: translateY(0) rotate(-10deg); }
.townsquare-avatar--resting svg g.leg-l { transform: rotate(120deg); }
.townsquare-avatar--resting svg g.leg-r { transform: rotate(118deg); }
.townsquare-avatar--resting svg g.knee-l { transform: rotate(-88deg); }
.townsquare-avatar--resting svg g.knee-r { transform: rotate(-85deg); }
.townsquare-avatar--resting svg g.arm-l,
.townsquare-avatar--resting svg g.arm-r { transform: rotate(45deg); }
.townsquare-avatar--resting svg g.elbow-l { transform: rotate(10deg); }
.townsquare-avatar--resting svg g.elbow-r { transform: rotate(-30deg); }

/* ── Ghost stack: history lives in the scene as fading bubbles above the figure ── */
.townsquare-avatar__above {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 12px);
  /* --bubble-shift: collision-avoidance nudge; --bubble-scale / --bubble-fade:
     proximity prominence — all set per frame by bubble-layout */
  transform: translateX(calc(-50% + var(--bubble-shift, 0px))) scale(var(--bubble-scale, 1));
  transform-origin: bottom center;
  opacity: var(--bubble-fade, 1);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  width: max-content;
  max-width: min(240px, 72cqw);
  pointer-events: none;
  transition: opacity 140ms ease, transform 160ms ease;
}

/* Narrow stages (phones / small embeds): cap bubbles tighter so a long line
   wraps to more rows (taller) instead of eating the width — that lets the
   column solver pack more speakers side by side. Short lines are unaffected
   (width: max-content). Container query, so it tracks the widget's own width. */
@container (max-width: 460px) {
  .townsquare-avatar__above {
    max-width: min(190px, 52cqw);
  }
}

.townsquare-avatar__above:empty { display: none; }

/* hovering to read history yields the live stack to the tray */
.townsquare-avatar--has-history:hover .townsquare-avatar__above,
.townsquare-avatar--has-history:focus-within .townsquare-avatar__above,
.townsquare-avatar--tray-open .townsquare-avatar__above { opacity: 0; }

.townsquare-avatar__bubble {
  position: relative;
  max-width: 100%;
  padding: 11px 16px 12px;
  border-radius: var(--r);
  background: var(--surface);
  color: var(--ink);
  font-size: var(--fs-speech);
  line-height: 1.28;
  font-weight: var(--fw-speech);
  letter-spacing: -0.005em;
  box-shadow: var(--bshadow);
  transform-origin: bottom center;
  transition: opacity 280ms ease, transform 280ms ease, box-shadow 280ms ease;
}

.townsquare-avatar__bubble-text {
  display: block;
  overflow-wrap: anywhere;
}

.townsquare-avatar__tail {
  position: absolute;
  left: 50%;
  bottom: -11px;
  width: 160px;
  height: 12px;
  background: var(--surface);
  /* The base slides along the bubble's flat bottom (--tail-shift) and the tip
     leans the rest of the way to the speaker (--tail-tip), so the pointer
     always lands on whoever spoke even when the column was nudged aside. */
  transform: translateX(calc(-50% + var(--tail-shift, 0px)));
  clip-path: polygon(
    calc(50% - 8px) 0,
    calc(50% + 8px) 0,
    calc(50% + var(--tail-tip, 0px) + 1.5px) 100%,
    calc(50% + var(--tail-tip, 0px) - 1.5px) 100%
  );
  filter: drop-shadow(2px 3px 2px rgba(60, 40, 20, 0.14));
  transition: transform 160ms ease, clip-path 160ms ease;
}

/* only the live (bottom) bubble keeps its tail */
.townsquare-avatar__bubble--ghost .townsquare-avatar__tail { display: none; }

.townsquare-avatar__bubble--ghost {
  opacity: 0.72;
  transform: scale(0.92);
  box-shadow: none;
}

.townsquare-avatar__bubble--ghost.townsquare-avatar__bubble--far {
  opacity: 0.54;
  transform: scale(0.88);
}

.townsquare--expanded .townsquare-avatar__bubble--ghost {
  opacity: 0.82;
  transform: scale(0.94);
}

.townsquare--expanded .townsquare-avatar__bubble--ghost.townsquare-avatar__bubble--far {
  opacity: 0.66;
  transform: scale(0.9);
}

/* each line fades out on its own timer, oldest first */
.townsquare-avatar__bubble--expiring {
  opacity: 0;
  transform: scale(0.8);
}

/* ── History tray: hover a character to recover their recent lines ── */
.townsquare-avatar__tray {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 12px);
  z-index: 3;
  width: max-content;
  min-width: min(220px, 88cqw);
  max-width: min(360px, 88cqw);
  max-height: min(280px, 58vh);
  padding: 5px 4px;
  border-radius: 16px;
  background: var(--surface);
  box-shadow: var(--tray-shadow);
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-gutter: stable;
  opacity: 0;
  pointer-events: none;
  transform: translate(calc(-50% + var(--tray-shift, 0px)), 4px);
  transition: opacity 140ms ease, transform 140ms ease;
}

.townsquare-avatar--has-history:hover .townsquare-avatar__tray,
.townsquare-avatar--has-history:focus-within .townsquare-avatar__tray,
.townsquare-avatar--tray-open .townsquare-avatar__tray {
  opacity: 1;
  pointer-events: auto;
  transform: translate(calc(-50% + var(--tray-shift, 0px)), 0);
}

/* composing: your tray would sit over neighbours' speech bubbles */
.townsquare-avatar--self.townsquare-avatar--composing .townsquare-avatar__tray,
.townsquare-avatar--self.townsquare-avatar--has-history:focus-within .townsquare-avatar__tray,
.townsquare-avatar--self.townsquare-avatar--has-history:has(.townsquare-avatar__below:hover) .townsquare-avatar__tray {
  opacity: 0;
  pointer-events: none;
  transform: translate(calc(-50% + var(--tray-shift, 0px)), 4px);
}

.townsquare-avatar--self.townsquare-avatar--composing .townsquare-avatar__above,
.townsquare-avatar--self.townsquare-avatar--has-history:focus-within .townsquare-avatar__above,
.townsquare-avatar--self.townsquare-avatar--has-history:has(.townsquare-avatar__below:hover) .townsquare-avatar__above {
  opacity: var(--bubble-fade, 1);
}

/* transparent bridge across the gap so hover survives figure → tray travel */
.townsquare-avatar__tray::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  height: 16px;
}

.townsquare-avatar__tray-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: start;
  column-gap: 14px;
  row-gap: 3px;
  padding: 8px 13px;
}

.townsquare-avatar__tray-row + .townsquare-avatar__tray-row {
  box-shadow: 0 -1px 0 var(--hair);
}

.townsquare-avatar__tray-msg {
  min-width: 0;
  white-space: normal;
  overflow-wrap: anywhere;
  line-height: 1.28;
  font-size: var(--fs-history);
  font-weight: var(--fw-history);
  color: var(--ink);
}

.townsquare-avatar__tray-time {
  font-size: var(--fs-time);
  font-weight: var(--fw-time);
  line-height: 1.28;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
}

@media (max-width: 420px) {
  .townsquare-avatar__tray-row {
    grid-template-columns: minmax(0, 1fr);
  }

  .townsquare-avatar__tray-time {
    justify-self: start;
  }
}

/* ── Nameplate / composer: the persistent way in, below your figure ── */
.townsquare-avatar__below {
  position: absolute;
  left: 50%;
  top: calc(100% + 9px);
  /* --label-shift de-conflicts overlapping name tags (see layoutNameLabels in
     bubble-layout.mjs), so no tag — including your own — covers another. */
  transform: translateX(calc(-50% + var(--label-shift, 0px)));
  /* --label-fade recedes distant tags so crowded narrow stages stay legible;
     near 1 on wide stages (see layoutNameLabels). */
  opacity: var(--label-fade, 1);
  transition: opacity 140ms ease;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 7px;
  white-space: nowrap;
}

/* Hovering or focusing a faded distant character restores its tag to full. */
.townsquare-avatar:hover .townsquare-avatar__below,
.townsquare-avatar:focus-within .townsquare-avatar__below {
  opacity: 1;
}

.townsquare-avatar__below[hidden] { display: none; }

.townsquare-avatar__below--peer {
  top: calc(100% + 7px);
  pointer-events: auto;
}

.townsquare-avatar--peer.townsquare-avatar--has-reading:not(.townsquare-avatar--has-display-name):not(:hover):not(:focus-within):not(.townsquare-avatar--label-open) .townsquare-avatar__below {
  display: none;
}

.townsquare-avatar__plate-row {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.townsquare-avatar__plate,
.townsquare-avatar__plate-chat {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

.townsquare-avatar__plate {
  position: relative;
}

.townsquare-avatar__plate::after,
.townsquare-avatar__self-id::after {
  content: "";
  position: absolute;
  top: 0;
  right: -46px;
  width: 46px;
  height: 100%;
  z-index: 0;
}

.townsquare-avatar__plate-chat {
  padding: 6px 13px 6px 11px;
  border: 0;
  border-radius: 11px;
  background: var(--surface);
  box-shadow: var(--bshadow);
  cursor: pointer;
  font-family: var(--font);
  transition: transform var(--ts-transition-short) ease, box-shadow var(--ts-transition-short) ease;
}

.townsquare-avatar__plate:hover .townsquare-avatar__plate-chat { transform: translateY(-1px); }
.townsquare-avatar__plate-chat:focus-visible,
.townsquare-avatar__color-button:focus-visible,
.townsquare-avatar__swatch:focus-visible {
  outline: 2px solid var(--you);
  outline-offset: 2px;
}

.townsquare-avatar__plate[hidden],
.townsquare-avatar__profile-button[hidden] { display: none; }

.townsquare-avatar__profile-button[aria-expanded="true"] {
  color: var(--ink);
  border-color: var(--hair-2);
  background: var(--surface);
}

.townsquare-avatar__plate-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--you);
  transition: transform 160ms ease, width 160ms ease, height 160ms ease;
}

.townsquare-avatar__plate-name {
  font-size: var(--fs-name);
  font-weight: var(--fw-name);
  letter-spacing: 0.01em;
  color: var(--you);
  text-transform: lowercase;
}

.townsquare-avatar__peer-label {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  max-width: 140px;
  padding: 4px 9px;
  border-radius: 8px;
  background: color-mix(in oklab, var(--surface) 82%, transparent);
  color: currentColor;
  box-shadow: 0 6px 12px -10px rgba(60, 40, 20, 0.34);
  overflow: hidden;
}

.townsquare-avatar__peer-name-row {
  display: inline-flex;
  align-items: baseline;
  max-width: 100%;
  min-width: 0;
}

.townsquare-avatar__peer-name {
  display: inline-block;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  font-size: 12.5px;
  font-weight: var(--fw-name);
  text-transform: lowercase;
}

.townsquare-avatar__peer-name[hidden] { display: none; }

/* Owner badge: a server-verified crown that ordinary visitors cannot fake. */
.townsquare-avatar__owner-crown {
  flex-shrink: 0;
  margin-right: 4px;
  font-size: 0.95em;
  /* Render the emoji in its own colors even when the name is tinted. */
  -webkit-text-fill-color: initial;
  cursor: default;
}

.townsquare-avatar__owner-crown[hidden] { display: none; }

.townsquare-avatar__owner-crown:focus-visible {
  outline: 3px solid color-mix(in oklab, var(--you) 48%, transparent);
  outline-offset: 2px;
  border-radius: 4px;
}

.townsquare-avatar__owner-role {
  display: none;
  align-self: stretch;
  max-width: 100%;
  overflow: hidden;
  color: var(--ink-faint);
  font-size: 10.5px;
  font-weight: 620;
  letter-spacing: 0;
  line-height: 1.15;
  text-align: center;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.townsquare-avatar__owner-role[hidden] { display: none; }

.townsquare-avatar--owner.townsquare-avatar--peer:has(.townsquare-avatar__owner-crown:hover) .townsquare-avatar__owner-role,
.townsquare-avatar--owner.townsquare-avatar--peer:has(.townsquare-avatar__owner-crown:focus-visible) .townsquare-avatar__owner-role {
  display: block;
}

.townsquare-avatar--owner.townsquare-avatar--peer:has(.townsquare-avatar__owner-crown:hover) .townsquare-avatar__reading,
.townsquare-avatar--owner.townsquare-avatar--peer:has(.townsquare-avatar__owner-crown:focus-visible) .townsquare-avatar__reading {
  display: none !important;
}

.townsquare-avatar--owner .townsquare-avatar__peer-label,
.townsquare-avatar--owner .townsquare-avatar__plate {
  background: var(--owner-badge-bg, color-mix(in oklab, var(--surface) 70%, gold 14%));
  box-shadow: 0 6px 14px -9px rgba(150, 110, 20, 0.5);
}

.townsquare-avatar__reading {
  display: none;
  align-items: baseline;
  gap: 3px;
  align-self: stretch;
  max-width: 100%;
  min-width: 0;
  overflow: hidden;
  color: var(--ink-faint);
  font-size: 10.5px;
  font-weight: 620;
  letter-spacing: 0;
  line-height: 1.15;
  text-decoration: none;
}

.townsquare-avatar--peer:hover .townsquare-avatar__reading--available,
.townsquare-avatar--peer:focus-within .townsquare-avatar__reading--available,
.townsquare-avatar--label-open .townsquare-avatar__reading--available {
  display: inline-flex;
}

.townsquare-avatar__reading-prefix {
  flex: 0 0 auto;
  font-weight: 520;
}

.townsquare-avatar__reading-label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.townsquare-avatar__reading:hover,
.townsquare-avatar__reading:focus-visible {
  color: currentColor;
  text-decoration: underline;
  text-underline-offset: 2px;
}

.townsquare-avatar__reading[hidden] { display: none; }

.townsquare-avatar__plate-hint {
  font-size: 12.5px;
  font-weight: 500;
  color: var(--ink-faint);
}

.townsquare-avatar__profile {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  min-width: 142px;
}

.townsquare-avatar__profile[hidden] { display: none; }

.townsquare-avatar__profile-input {
  min-width: 0;
  width: 112px;
  height: 30px;
  padding: 0 2px;
  border: 1px solid var(--hair);
  border-radius: 7px;
  outline: 0;
  background: color-mix(in oklab, var(--surface-2) 82%, transparent);
  color: var(--ink);
  font-family: var(--font);
  font-size: 13px;
  font-weight: 520;
}

.townsquare-avatar__profile-input::placeholder { color: var(--ink-faint); }

.townsquare-avatar__color-button {
  width: 24px;
  height: 24px;
  padding: 0;
  border: 2px solid var(--surface);
  border-radius: 999px;
  background: var(--swatch, var(--you));
  box-shadow: 0 0 0 1px var(--hair), inset 0 0 0 1px rgba(42, 41, 38, 0.18);
  cursor: pointer;
  animation: townsquare-color-dot-grow 180ms cubic-bezier(0.2, 0.8, 0.2, 1);
}

.townsquare-avatar__color-button:hover,
.townsquare-avatar__color-button[aria-expanded="true"] {
  box-shadow: 0 0 0 2px var(--ink), inset 0 0 0 1px rgba(42, 41, 38, 0.18);
}

.townsquare-avatar__color-menu {
  position: absolute;
  z-index: 2;
  bottom: calc(100% + 10px);
  left: 12px;
  padding: 8px;
  border-radius: 9px;
  background: var(--surface);
  box-shadow: var(--tray-shadow);
  transform: translateX(-50%);
}

.townsquare-avatar__color-menu[hidden] { display: none; }

.townsquare-avatar__swatches {
  display: grid;
  grid-template-columns: repeat(4, 20px);
  gap: 6px;
}

.townsquare-avatar__swatch {
  width: 20px;
  height: 20px;
  padding: 0;
  border: 2px solid transparent;
  border-radius: 999px;
  background: var(--swatch);
  box-shadow: inset 0 0 0 1px rgba(42, 41, 38, 0.18);
  cursor: pointer;
}

.townsquare-avatar__swatch[aria-pressed="true"] {
  border-color: var(--surface);
  box-shadow: 0 0 0 2px var(--ink), inset 0 0 0 1px rgba(42, 41, 38, 0.18);
}


.townsquare-avatar__profile-button {
  position: absolute;
  z-index: 1;
  top: 50%;
  left: calc(100% + 8px);
  opacity: 0;
  pointer-events: none;
  transform: translate(-4px, -50%);
  transition: opacity var(--ts-transition-short) ease, transform var(--ts-transition-short) ease;
}

.townsquare-avatar__profile-button svg {
  width: 13px;
  height: 13px;
}

.townsquare-avatar__self-id:hover .townsquare-avatar__profile-button,
.townsquare-avatar__self-id:focus-within .townsquare-avatar__profile-button,
.townsquare-avatar__plate:hover .townsquare-avatar__profile-button,
.townsquare-avatar__plate:focus-within .townsquare-avatar__profile-button {
  opacity: 1;
  pointer-events: auto;
  transform: translate(0, -50%);
}

@media (pointer: coarse) {
  .townsquare-avatar__profile-button {
    opacity: 1;
    pointer-events: auto;
    transform: translate(0, -50%);
  }

  .townsquare-avatar__profile-button svg {
    width: 14px;
    height: 14px;
  }
}

.townsquare-avatar--profile-open .townsquare-avatar__self-id {
  gap: 7px;
}

.townsquare-avatar--profile-open .townsquare-avatar__plate::after,
.townsquare-avatar--profile-open .townsquare-avatar__self-id::after {
  display: none;
}

.townsquare-avatar--profile-open .townsquare-avatar__self-id > :is(.townsquare-avatar__plate-dot, .townsquare-avatar__owner-crown, .townsquare-avatar__plate-name, .townsquare-avatar__profile-button),
.townsquare-avatar--profile-open .townsquare-avatar__plate > :is(.townsquare-avatar__plate-chat, .townsquare-avatar__profile-button) {
  display: none;
}

@keyframes townsquare-color-dot-grow {
  from {
    transform: scale(0.36);
  }
  to {
    transform: scale(1);
  }
}

.townsquare-avatar__composer {
  position: relative;
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 7px 7px 7px 19px;
  min-width: 300px;
  max-width: min(340px, 80cqw);
  border-radius: 26px;
  background: var(--surface);
  box-shadow: var(--tray-shadow);
}

.townsquare-avatar__composer[hidden] { display: none; }

/* Slow-mode "Wait Ns…" caption, floating just above the composer pill. */
.townsquare-avatar__composer-hint {
  position: absolute;
  left: 19px;
  bottom: calc(100% + 6px);
  margin: 0;
  padding: 3px 10px;
  border-radius: 12px;
  background: var(--surface);
  box-shadow: var(--tray-shadow);
  color: var(--ink-soft);
  font-family: var(--font);
  font-size: 13px;
  font-weight: 500;
  white-space: nowrap;
  pointer-events: none;
}

.townsquare-avatar__composer-hint[hidden] { display: none; }

.townsquare-avatar__input {
  flex: 1;
  min-width: 0;
  min-height: 30px;
  border: 0;
  outline: 0;
  background: transparent;
  color: var(--ink);
  font-family: var(--font);
  font-size: 17px;
  font-weight: 460;
}

.townsquare-avatar__input::placeholder { color: var(--ink-faint); }

.townsquare-avatar__send {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--surface-2);
  color: var(--ink-soft);
  box-shadow: inset 0 0 0 1px var(--hair);
  cursor: pointer;
  transition: background 140ms ease, color 140ms ease, box-shadow 140ms ease;
}

.townsquare-avatar__send svg { width: 17px; height: 17px; display: block; }

.townsquare-avatar__send--ready {
  background: var(--you);
  color: #fff;
  box-shadow: none;
}

@media (max-width: 640px) {
  .townsquare__status { flex-direction: column; gap: 4px; }
  .townsquare-avatar__composer { min-width: min(260px, 80cqw); }
}

/* TOOLBAR: fixed bar at action-zone bottom (replaces floating composer).
   Always visible, houses composer input and action buttons.
   Figure keeps compact identity; peer nameplates never covered; virtual
   keyboard can't clip any UI. Touch-optimized with 42px target sizes. */
.townsquare__toolbar {
  position: absolute;
  z-index: var(--ts-z-ui-above);
  left: var(--ts-toolbar-inset);
  right: var(--ts-toolbar-inset);
  bottom: var(--ts-toolbar-bottom);
  display: flex;
  align-items: center;
  gap: var(--ts-toolbar-gap);
}

/* Actions (jump / high-five) are docked into the toolbar, so the floating
   container that once held them bottom-right is always empty. */
.townsquare__actions {
  display: none;
}

/* The docked composer fills the bar instead of floating on its own.
   Maintains its visual styling (background and shadow) while filling the toolbar.
   Reduce padding to fit within the action-zone without overflow. */
.townsquare__toolbar .townsquare-avatar__composer--docked {
  position: static;
  flex: 1 1 auto;
  min-width: 0;
  max-width: none;
  padding: 3px 7px 3px 19px;
}

/* Compact identity under the figure: display name, or "you" when unset. */
.townsquare-avatar__self-id {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 5px 11px;
  border-radius: 11px;
  background: var(--surface);
  box-shadow: var(--bshadow);
}

/* In expanded (fixed) mode the page can't scroll, so ride above the keyboard. */
.townsquare--expanded .townsquare__toolbar {
  bottom: calc(7px + var(--ts-keyboard, 0px));
}

@media (pointer: coarse) {
  .townsquare__button--sm {
    width: 40px;
    height: 40px;
  }

  .townsquare__button--sm svg {
    width: 18px;
    height: 18px;
  }

  .townsquare__status {
    right: 152px;
  }

  /* fingers need an even wider tap zone than mouse pointers */
  .townsquare-avatar--peer::before {
    inset: -10px -20px;
  }
}
