/* Pawball 5.0 - components (light park theme) */

/* ---------- panels ---------- */
.panel {
  background: var(--panel-light);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-panel);
  box-shadow: var(--shadow-panel);
  padding: var(--dashboard-card-padding);
  backdrop-filter: blur(var(--panel-blur));
  -webkit-backdrop-filter: blur(var(--panel-blur));
}
/* Side panels use the approved translucent material without a foggy blur. */
.col .panel {
  background: var(--panel-dash);
  border-color: var(--shell-border, rgba(84, 105, 88, .24));
  backdrop-filter: blur(1px);
  -webkit-backdrop-filter: blur(1px);
}
/* ---------- the one small-label recipe ----------
   Every uppercase gold label on the interface - panel titles, the Next Match
   eyebrow, the rail's section heading - previously ran its own combination
   of size, weight and tracking (.12em/800 here, .07em/600 on the eyebrow,
   .1em/800 on the status rows, .04em on the league head). They now share one
   set of values so that a "small label" is recognisably one thing, and only
   colour and opacity say how important a given instance is. */
.panel h2 {
  margin: 0 0 10px;
  font-size: var(--fs-label);
  font-weight: 800;
  letter-spacing: .1em;
  color: var(--label-gold);
  text-transform: uppercase;
}
.panel h2 { display: flex; align-items: center; }
.panel h2 .section-icon { width: 15px; height: 15px; margin-right: 7px; flex: 0 0 auto; color: var(--gold-deep); }
.panel-title-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 10px;
}
.panel-title-row h2 { margin: 0; }

.panel-btn {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  width: 100%;
  margin-top: 10px;
  padding: 8px 12px;
  background: var(--green-btn);
  border-radius: var(--r-card);
  color: #FBF7EA;
  font-weight: 600;
  transition: filter .12s ease;
}
.panel-btn:hover { filter: brightness(1.1); }
.panel-btn:active { filter: brightness(.94); }
/* quiet secondary variant: contextual actions (e.g. incident details) sit a
   step below the primary green "View X" buttons */
.panel-btn.quiet {
  background: #FFFDF4;
  color: var(--green-deep);
  border: 1px solid var(--line-soft);
  transition: border-color .14s ease, filter .12s ease;
}
.panel-btn.quiet:hover { filter: none; border-color: var(--line-gold); }
.panel-btn.quiet:active { filter: brightness(.97); }

/* ---------- hud date/status group ---------- */
.hud-divider {
  flex: 0 0 auto;
  width: 1px;
  align-self: stretch;
  margin: 9px 2px;
  background: linear-gradient(180deg, transparent, var(--line-soft) 20%, var(--line-soft) 80%, transparent);
}
.hud-date {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 2px;
  overflow: hidden;
  padding: 0 6px;
}
.hud-date-main {
  font-family: var(--serif);
  font-weight: 700;
  font-size: 1.06em;
  line-height: 1.15;
  color: var(--green-deep);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.hud-date-sub {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
}
.hud-date #status-time {
  flex: 0 0 auto;
  display: inline-block;
  font-size: .72em;
  font-weight: 800;
  letter-spacing: .05em;
  text-transform: uppercase;
  padding: 1px 8px;
  border-radius: var(--r-pill);
  background: var(--gold-tint);
}
.hud-date #status-season {
  flex: 0 0 auto;
  font-size: .76em;
  font-weight: 500;
  color: var(--ink-faint);
  overflow: hidden;
  text-overflow: ellipsis;
}
.hud-date-sep { color: var(--ink-faint); flex: 0 0 auto; font-size: .76em; }

/* shared value-tone colours, used by Club Mood and Manager Reputation */
.status-tone-positive { color: var(--ok); }
.status-tone-neutral { color: var(--ink-dim); }
.status-tone-caution { color: var(--warn); }
.status-tone-negative { color: var(--bad); }
.status-tone-gold { color: var(--gold-deep); }

/* ---------- Club Mood & Manager Reputation cards ---------- */
/* The headline of each card is its VALUE, not its label: "Steady" and
   "64/100" are what the player scans for, so they take the serif feature
   step while the uppercase label stays quiet. */
.mood-panel h2, .rep-panel h2 { margin: 0; }
/* Club Mood / Trust states are dashboard DATA, not a heading or a piece of
   branding, so they sit in the sans stack with everything else that reports
   a number or a state. The serif feature step is reserved for the club
   title, section headings and major match information. */
.mood-value, .rep-value {
  font-family: var(--sans);
  font-weight: 700;
  line-height: 1;
}
.mood-list, .rep-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 7px;
}
.mood-item, .rep-item {
  font-size: .88em;
  line-height: 1.35;
  color: var(--ink-dim);
  padding-left: 14px;
  position: relative;
  overflow-wrap: break-word;
}
.mood-item {
  padding-left: 15px;
}
.mood-item::before {
  content: "−";
  position: absolute;
  left: 0;
  top: 0;
  font-weight: 800;
  color: var(--bad);
}
.mood-item.positive::before { content: "+"; color: var(--ok); }
.mood-item.neutral::before { content: "·"; color: var(--ink-faint); }
.rep-item { padding-left: 15px; }
.rep-item::before {
  content: "−";
  position: absolute;
  left: 0;
  top: 0;
  font-weight: 800;
  color: var(--bad);
}
.rep-item.positive::before { content: "+"; color: var(--ok); }
.rep-item.neutral::before { content: "·"; color: var(--ink-faint); }
.inline-name-link {
  color: var(--green-deep);
  font-weight: 700;
  text-decoration: underline;
  text-decoration-color: var(--green-deep);
  text-underline-offset: 2px;
  transition: color .12s ease;
}
.inline-name-link:hover { color: var(--gold-deep); }

/* ---------- league position ---------- */
/* No standalone League card - the position rides inside the League Table
   navigation row itself, right-aligned and slightly emphasised against the
   row's own label. */
.league-position-value {
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

.col-right .panel h2 { margin-bottom: 8px; }
.col-right .panel-btn { margin-top: 10px; padding-block: 9px; }

/* ---------- messenger ---------- */
.messenger {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  padding: 0;
  overflow: hidden;
  overflow-anchor: none;
  /* One light shell contains the header, thread and composer. Its fine
     green stroke replaces the former cream frame without obscuring the
     wallpaper visible through the conversation body. */
  background: rgba(253, 248, 235, .05);
  border: 1px solid var(--shell-border, rgba(84, 105, 88, .24));
  border-radius: var(--r-panel);
  box-shadow: 0 3px 12px rgba(40, 60, 35, .12);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
.messenger-body, .convo { overflow-anchor: none; }
/* The Messenger is one horizontal stage. Its conversation owns the full
   width normally; a contextual sidecar can claim the right-hand portion
   without replacing the thread or composer. */
.messenger-body {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: row;
  overflow: hidden;
}

/* Human avatar files are individually cropped from their full profile PNGs.
   Render those authored crops unchanged so every surface keeps the same face
   scale and framing. */
.portrait-frame {
  --portrait-zoom: 1;
  --portrait-x: 0%;
  --portrait-y: 0%;
  position: relative;
  display: block;
  flex: 0 0 auto;
  overflow: hidden;
  border: 2px solid var(--line-gold);
  border-radius: 50%;
  background: var(--panel-light);
  box-shadow: 0 1px 4px rgba(40, 60, 35, .2);
}
/* The manager without a chosen image. Same circle, same gold border and
   shadow as every character portrait - only the contents differ - so the
   manager never appears as a gap in a list of faces.
   Warm cream fill with dark green initials, NOT a solid green disc: at a
   glance a green circle read as one more instance of the player's own
   green message bubble/primary-action colour rather than as a face, and it
   sat noticeably heavier than the staff portraits beside it. */
.portrait-frame.is-initial {
  display: grid;
  place-items: center;
  background: var(--cream-2);
  color: var(--green-deep);
  font-family: var(--serif);
  font-weight: 700;
  line-height: 1;
}
.portrait-frame.is-initial > span { font-size: .95em; }
.portrait-frame img {
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
  object-position: 50% 50%;
  transform: translate(var(--portrait-x), var(--portrait-y)) scale(var(--portrait-zoom));
  transform-origin: 50% 50%;
}
.portrait-frame.portrait-neil { --portrait-zoom: 1.1; --portrait-x: 2%; --portrait-y: 5%; }

.convo {
  /* The one reading measure the conversation and its composer both align to.
     Capped rather than fluid: past roughly this width a chat line stops
     being easier to read and simply becomes a longer eye movement. */
  --thread-measure: 860px;
  flex: 1 1 auto;
  order: 1;
  width: auto;
  min-width: 0;
  min-height: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  overflow: hidden;
  /* head / scene-notice / scene-schedule / thread / composer.
     Meetup quick actions live inside the composer footer so the outer
     Messenger panel never changes size when a scene starts or ends. */
  grid-template-rows: auto auto auto minmax(0, 1fr) auto;
  transition: width .36s cubic-bezier(.22, .61, .36, 1);
}
/* Explicit grid-row placement for every .convo child (head / scene-notice /
   scene-schedule / thread / composer) lives in
   ambient.css, which loads after this file and already owned overlapping
   placement for the thread-scroll fade cues - see ".convo > #convo-head"
   there. Keeping one source of truth avoids the two files silently fighting
   over the same five-row grid. */
.convo-head {
  flex: 0 0 auto;
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: 8px;
  height: 54px;
  min-height: 54px;
  padding: 5px 10px;
  /* A single hairline seam attaches the header directly to the thread. */
  border-bottom: 1px solid rgba(58, 84, 54, .13);
  background: rgba(253, 248, 235, .70);
  border-radius: calc(var(--r-panel) - 1px) calc(var(--r-panel) - 1px) 0 0;
  box-shadow: none;
}
.convo-id { display: flex; align-items: center; gap: 8px; flex: 1 1 auto; min-width: 0; border-radius: var(--r-card); }
.convo-id > div { min-width: 0; }
.convo-id:hover .name { text-decoration: underline; }
.convo-id:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.convo-head .portrait-frame { width: 44px; height: 44px; }
.convo-head .name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: var(--serif); font-weight: 700; font-size: var(--fs-feature); color: var(--green-deep); }
.convo-head .role { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: .85em; color: var(--ink-dim); }
.convo-head .presence-label { white-space: nowrap; }
/* job title sits on the same line as "View profile"/"typing…", separated by
   a middot - the banner previously dropped the role entirely once "View
   profile" took over this line. */
.convo-subline { display: flex; align-items: center; min-width: 0; }
.convo-role {
  min-width: 0;
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: .85em;
  color: var(--ink-dim);
}
.convo-role::after { content: "·"; margin: 0 5px; color: var(--ink-faint); }
/* the last (or only) thing on the subline carries no trailing separator */
.convo-role.is-plain::after { content: none; }
.convo-subline .presence-label { flex: 0 0 auto; }
/* Width trimmed slightly and a small right inset added (final UI precision
   pass) - the block was pinned flush to the header's own edge, which with
   its previously tiny type read as a separate island rather than part of
   the same header as the identity block beside it. */
.convo-rel { flex: 0 1 168px; width: clamp(88px, 32%, 168px); min-width: 88px; margin-left: auto; margin-right: 6px; text-align: right; }

/* ---------- group identity in the Messenger header ----------
   A group states what it is HERE, in the main banner - not by taking over
   the left rail, which is the cast. The faces are a compact overlapping
   cluster rather than a bespoke group image: at 44px a real group photo
   would be unreadable, and the members are the identity anyway.
   Sized to the same 44px band the single-portrait direct header uses, so
   switching between a DM and a group never changes the header height. */
.group-faces {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  height: 44px;
  padding-left: 6px; /* the first portrait's own negative margin, reclaimed */
}
/* Faces stepped down slightly (30px -> 27px) and given a shallower overlap
   (-9px -> -6px), so a full cluster of four plus the +N chip reads as
   individual portraits at a glance rather than a solid overlapping disc. */
.group-faces .portrait-frame,
.group-faces-more {
  box-sizing: border-box;
  width: 27px;
  height: 27px;
  border-width: 1.5px;
  margin-left: -6px;
  box-shadow: 0 1px 3px rgba(40, 60, 35, .18);
  align-self: center;
}
/* In the Messenger header this same selector ties in specificity with the
   generic ".convo-head .portrait-frame { width:44px; height:44px }" rule and
   was only winning by source order - a fragile tie that let a member face
   and the +N chip land on very slightly different boxes. This scoped repeat
   outranks it outright, so the header cluster's sizing is never in doubt. */
#convo-head .group-faces .portrait-frame { width: 27px; height: 27px; }
.group-faces-more {
  min-width: 27px;
  /* the +N chip is a count, not a face - a touch more room from its
     neighbour than the faces keep from each other keeps it reading as a
     distinct "and N more" marker rather than one more portrait in the row */
  margin-left: -3px;
  padding: 0 5px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1.5px solid var(--line-gold);
  background: var(--cream-2);
  color: var(--ink-dim);
  font-size: .66em;
  font-weight: 800;
}
.convo-id-group { flex: 1 1 auto; }
/* A group's identity block states what the room IS; the action that opens
   the member sheet is the labelled Members control on the right, so the
   block itself is not a second, unlabelled button doing the same job. */
.convo-id.is-static { cursor: default; }

/* ---------- conversation header controls ----------
   Only controls that act on the OPEN conversation live here now (Members,
   Back). The old "Groups" chip - a jump to a second list of the same rooms
   the rail already shows, and vague to boot - is gone; groups are entered
   from their own permanent rows at the top of the people rail. */
.convo-head-actions {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 5px;
  margin-left: auto;
}
.convo-rel + .convo-head-actions { margin-left: 8px; }
.convo-chip-btn {
  position: relative;
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  height: 28px;
  padding: 0 10px;
  border-radius: var(--r-pill);
  border: 1px solid var(--line-gold-soft);
  background: rgba(253, 248, 235, .82);
  color: var(--green-deep);
  font-size: .76em;
  font-weight: 700;
  white-space: nowrap;
  transition: background .14s ease, border-color .14s ease, transform .09s ease;
}
.convo-chip-btn:hover { background: var(--gold-tint); border-color: var(--line-gold); }
.convo-chip-btn:active { transform: scale(.96); }
.convo-chip-btn:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.convo-chip-icon { width: 14px; height: 14px; }
/* Members is a secondary utility, not a call to action competing with the
   conversation it sits above: shorter, lighter fill, hairline border. It
   only needs to be findable, and the group's own identity block beside it
   already says what the room is. */
.convo-head-actions .convo-chip-btn {
  height: 24px;
  padding: 0 9px;
  background: rgba(253, 248, 235, .45);
  border-color: rgba(150, 118, 54, .3);
  color: var(--ink-dim);
  font-weight: 650;
  font-size: .72em;
}
.convo-head-actions .convo-chip-btn:hover {
  background: rgba(253, 248, 235, .75);
  border-color: var(--line-gold-soft);
  color: var(--green-deep);
}
.convo-head-actions .convo-chip-icon { width: 13px; height: 13px; }
.convo-chip-btn .unread-badge.on-chip { top: -6px; right: -6px; }
/* the same badge sitting in normal flow inside a list row, not pinned to a
   corner - the only host that needs it un-positioned */
.unread-badge.on-chip-inline { position: static; justify-self: end; }

/* ---------- integrated Messenger panels (Groups / Create group) ----------
   These render into the SAME #convo-head/#convo-thread the open conversation
   uses - never a modal, never a second surface - so the rail, scene chrome
   and right dashboard never move while one is open. */
.messenger-panel-title {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-family: var(--serif);
  font-weight: 700;
  font-size: var(--fs-feature);
  color: var(--green-deep);
}
.messenger-panel-subtitle { margin: 2px 0 10px; font-size: .85em; color: var(--ink-dim); }
.messenger-panel-empty { margin-top: 8px; font-size: .9em; color: var(--ink-dim); }
.messenger-panel-actions { display: flex; justify-content: flex-end; margin-top: 14px; }
.messenger-panel-members { margin-bottom: 12px; }
/* compact face cluster for a Groups-list row or the Create Group name step -
   smaller than the 44px header cluster, so it sits comfortably beside two
   lines of row text instead of dominating it. */
.group-faces.is-compact { height: 34px; padding-left: 4px; }
.group-faces.is-compact .group-faces-more { min-width: 34px; height: 34px; font-size: .62em; }

/* ---------- new chat / create group sheet ----------
   Generic list-row/selection styling, originally built for the New Chat and
   Create Group MODALS and now reused unmodified for the same content
   rendered inline inside the Groups/Create Group panel above. */
.chat-picker { display: grid; gap: 7px; }
.chat-picker-option {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 10px;
  padding: 8px 11px;
  text-align: left;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-card);
  background: rgba(253, 248, 235, .72);
  transition: background .14s ease, border-color .14s ease;
}
.chat-picker-option:hover { background: var(--gold-tint); border-color: var(--line-gold-soft); }
.chat-picker-option:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.chat-picker-option .portrait-frame { width: 34px; height: 34px; }
.chat-picker-name { font-weight: 700; color: var(--green-deep); }
.chat-picker-sub { font-size: .8em; color: var(--ink-dim); }
.chat-picker-check {
  position: relative;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: 1.5px solid var(--line-gold-soft);
  background: var(--cream);
  transition: background .14s ease, border-color .14s ease;
}
/* Restrained Pawball-green selected state: a pale tint and a slightly
   stronger green border on the row, a small solid-green circular tick on
   the right - never the brown/gold "muddy" treatment this replaces, and
   never a solid-green replacement for the character's own portrait. */
.chat-picker-option.is-picked { background: var(--green-tint); border-color: var(--green-mid); }
.chat-picker-option.is-picked .chat-picker-check { background: var(--green-mid); border-color: var(--green-mid); }
.chat-picker-option.is-picked .chat-picker-check::after {
  content: "";
  position: absolute;
  left: 5px;
  top: 2px;
  width: 5px;
  height: 9px;
  border: solid var(--cream);
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}
.chat-picker-name-field {
  width: 100%;
  margin-top: 10px;
  padding: 8px 11px;
  border: 1px solid var(--line-gold-soft);
  border-radius: var(--r-card);
  background: var(--cream);
  color: var(--ink);
  font: inherit;
}
.chat-picker-note { margin-top: 8px; font-size: .8em; color: var(--ink-dim); }
.chat-picker-error { margin-top: 8px; font-size: .8em; color: #A34E3D; }

/* The Create Group member step is always exactly the nine staff - a fixed,
   bounded list, never "history" - so like the left rail's own nine cards
   (see .rail-list .char-card) it scales by viewport height instead of ever
   growing an internal scrollbar. Portrait, name and role stay readable at
   every size this clamps to; only the padding/gap/portrait shrink. */
.member-picker { gap: clamp(2px, .55vh, 7px); }
.member-picker .chat-picker-option { padding: clamp(3px, .9vh, 8px) 11px; }
.member-picker .chat-picker-option .portrait-frame {
  width: clamp(26px, 3.4vh, 34px);
  height: clamp(26px, 3.4vh, 34px);
}
.member-picker .chat-picker-name,
.member-picker .chat-picker-sub { line-height: 1.15; }
.chat-picker-error[hidden] { display: none; }
/* Mute is REAL, implemented behaviour, so the control stays: MR.toggleMute()
   persists `muted` on the conversation record, bubbleHtml() then marks every
   incoming message data-muted-arrival, and ambient.js skips that message's
   arrival glow and its receive tick, while announceUnread() skips the row
   flash and badge spring. Muting a room genuinely makes it stop calling for
   attention, and the setting survives a save. Quieter than before - it is a
   utility beside the Members chip, not a peer of the conversation title -
   and its muted state is now unmistakable rather than a subtle tint. */
.convo-mute-btn {
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  color: var(--ink-faint);
  border: 1px solid transparent;
  transition: background .14s ease, color .14s ease, border-color .14s ease, transform .09s ease;
}
.convo-mute-btn .ui-icon { width: 16px; height: 16px; }
.convo-mute-btn:hover { color: var(--green-deep); background: rgba(58, 84, 54, .09); }
.convo-mute-btn:active { transform: scale(.94); }
.convo-mute-btn:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.convo-mute-btn.is-muted { color: var(--gold-deep); background: var(--gold-tint); border-color: var(--line-gold); }
.row-muted { display: inline-flex; vertical-align: middle; margin-left: 4px; color: var(--ink-faint); }
.row-muted-icon { width: 13px; height: 13px; }
.convo-rel .lbl { display: flex; align-items: center; justify-content: flex-end; gap: 4px; font-size: .9em; color: var(--ink-dim); }
.convo-rel .lbl strong { color: var(--gold-deep); }
.relationship-icon { width: 14px; height: 14px; color: var(--green-mid); }
.relationship-icon.romance { color: var(--gold-deep); }

/* staged relationship bar: shows progress within the current stage.
   Track fill darkened slightly (.15 -> .22 alpha) so the empty portion of
   the bar reads clearly against the header, not just the filled portion. */
.rel-bar { width: 100%; height: 7px; background: rgba(62, 88, 58, .22); border-radius: 3px; margin-top: 4px; overflow: hidden; }
.rel-bar span { display: block; height: 100%; border-radius: 3px; transition: width .4s ease; }
/* one score, one staged palette: amber through green, then gated Partner gold */
.rel-bar.t0 span { background: #BC6A45; } /* Acquaintance */
.rel-bar.t1 span { background: #C2913A; } /* Friendly */
.rel-bar.t2 span { background: #B99A3E; } /* Trusted */
.rel-bar.t3 span { background: #8C9A48; } /* Close */
.rel-bar.t4 span { background: #3F8B4C; } /* Inner Circle */
.rel-bar.t5 span { background: #2F7D42; } /* Partner */
/* romantic bonds read warm, not green: dating/flirting gets the gold tint,
   partners get the full gold-into-rose blend - different in kind at a glance */
.rel-bar.warm span { background: linear-gradient(90deg, #C2913A, #CE8791); }
.rel-bar.romance span { background: linear-gradient(90deg, #D9A93C 25%, #D97F8E); box-shadow: inset 0 0 0 1px rgba(217, 138, 150, .35); }
/* Gap between the Friendship and Attraction rows widened (3px -> 7px) -
   the two bars read as cramped, almost touching. */
.rel-bars { display: grid; gap: 7px; width: 100%; }
.rel-track {
  display: grid;
  grid-template-columns: 68px minmax(72px, 1fr);
  align-items: center;
  gap: 6px;
}
/* Friendship/Attraction labels nudged up from .62em - too small to read
   comfortably at the header's usual size. */
.rel-track > span {
  color: var(--ink-dim);
  font-size: .72em;
  font-weight: 800;
  letter-spacing: .02em;
}
.rel-bars .rel-bar { margin-top: 0; }
/* same height as the Friendship bar above it - previously thinner (3px vs
   6px) with a different label layout (Friendship's own label hidden as
   "duplicate" of the "Relationship: Friendly" heading, Attraction's kept
   visible), which read as two unrelated widgets stacked together rather
   than one matched pair. Both tracks now share the same label + bar
   proportions; only the fill colour tells them apart. */
.rel-bar.attraction { height: 6px; background: rgba(185, 95, 112, .14); }
.rel-bar.attraction span { background: linear-gradient(90deg, #C9A24C, #C45E78); }
.convo-rel .rel-bar { width: 100%; }
.convo-rel.relationship-positive .rel-bar span { animation: relationshipGain .65s ease-out; }
.convo-rel.relationship-negative .rel-bar span { animation: relationshipLoss .65s ease-out; }
@keyframes relationshipGain {
  35% { filter: brightness(1.32); box-shadow: 0 0 7px rgba(217, 169, 60, .68); }
}
@keyframes relationshipLoss {
  35% { filter: saturate(.45) brightness(.78); }
}

.scene-notice {
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 9px 12px;
  color: var(--green-deep);
  background: rgba(253, 248, 235, .94);
  border-bottom: 1px solid var(--line-gold-soft);
}
.scene-notice[hidden] { display: none; }
.scene-notice > div:first-child { min-width: 0; display: grid; gap: 2px; }
.scene-notice strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: .86em; }
.scene-notice span { color: var(--ink-dim); font-size: .75em; }
.scene-notice.due { background: rgba(239, 218, 166, .94); }
.scene-notice-actions { display: flex; gap: 7px; flex: 0 0 auto; }
.scene-notice-actions button { min-height: 32px; padding: 5px 10px; }

/* the rest of the diary, folded away under the strip: same cream/gold band,
   one compact row per meetup so several can be seen and cancelled at once */
.scene-schedule {
  display: grid;
  gap: 1px;
  background: var(--line-gold-soft);
  border-bottom: 1px solid var(--line-gold-soft);
}
.scene-schedule[hidden] { display: none; }
.scene-schedule-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 7px 12px;
  color: var(--green-deep);
  background: rgba(253, 248, 235, .94);
}
.scene-schedule-row > div { min-width: 0; display: grid; gap: 2px; }
.scene-schedule-row strong {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: .82em;
}
.scene-schedule-row span { color: var(--ink-dim); font-size: .72em; }
.scene-schedule-row button { flex: 0 0 auto; min-height: 28px; padding: 4px 9px; }

/* invitation card: reads as a small event strip, not a modal. A soft gold
   tint and a calendar glyph separate it from the plainer due/planned states
   above, while staying inside the same compact .scene-notice footprint. */
.scene-notice.pending {
  align-items: flex-start;
  gap: 10px;
  padding: 10px 12px;
  background: linear-gradient(180deg, rgba(250, 240, 210, .7), rgba(253, 248, 235, .94));
  border-bottom: 1px solid var(--line-gold);
}
.scene-notice.pending .invite-icon {
  flex: 0 0 auto;
  width: 30px;
  height: 30px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: rgba(217, 169, 60, .16);
  color: var(--gold-deep);
  margin-top: 1px;
}
.scene-notice.pending .invite-icon .ui-icon { width: 16px; height: 16px; }
.scene-notice.pending .invite-body {
  min-width: 0;
  flex: 1 1 auto;
  display: grid;
  gap: 1px;
}
.scene-notice.pending .invite-eyebrow {
  font-size: .68em;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--gold-deep);
}
.scene-notice.pending strong {
  font-size: .95em;
  font-weight: 800;
  white-space: normal;
  overflow-wrap: break-word;
  line-height: 1.25;
}
.scene-notice.pending .invite-meta {
  display: block;
  font-size: .76em;
  color: var(--ink-dim);
  font-weight: 600;
}
.scene-notice.pending .invite-desc {
  display: block;
  font-size: .74em;
  color: var(--ink-faint);
  white-space: normal;
  overflow-wrap: break-word;
  line-height: 1.3;
  margin-top: 1px;
}
.scene-notice.pending .scene-notice-actions {
  align-self: center;
  flex-direction: row;
  gap: 8px;
}
.scene-notice.pending .scene-notice-actions button { width: 84px; }
/* one-shot nudge when Continue is pressed while an invitation is still
   pending - draws the eye back to Accept/Decline without looping */
.scene-notice.invite-nudge { animation: pbInviteNudge .5s ease-out; }
@keyframes pbInviteNudge {
  0%, 100% { box-shadow: none; }
  30% { box-shadow: 0 0 0 2px rgba(217, 169, 60, .8), 0 0 16px rgba(217, 169, 60, .6); }
}

/* The actionable invitation is rendered by the shared Messenger bubble
   renderer and linked to the scene by data-scene-card. Respond First briefly
   highlights this exact durable card after scrolling it into view. */
.scene-card {
  display: grid;
  gap: 4px;
  align-self: stretch;
  max-width: min(440px, calc(100% - 12px));
  margin: 4px auto;
  padding: 12px 14px;
  border: 1px solid var(--line-gold-soft);
  border-radius: var(--r-card);
  background: rgba(253, 248, 235, .96);
  color: var(--green-deep);
}
.scene-card .scene-card-eyebrow {
  color: var(--gold-deep);
  font-size: .7em;
  font-weight: 800;
  letter-spacing: .04em;
  text-transform: uppercase;
}
.scene-card .scene-card-meta { color: var(--ink-dim); font-size: .78em; }
.scene-card .scene-notice-actions { margin-top: 5px; }
.scene-card:focus { outline: 2px solid var(--gold); outline-offset: 2px; }
.scene-card.invitation-focus { animation: pbInviteCardFocus 1.1s ease-out; }
@keyframes pbInviteCardFocus {
  25%, 65% { box-shadow: 0 0 0 3px rgba(217, 169, 60, .48), 0 0 20px rgba(217, 169, 60, .35); }
}

/* ---------- Messenger context sidecar ----------
   One reusable presentation surface for live Meetups and compact game tools.
   It enters from the right and takes real layout width, so the thread,
   header and composer compress together rather than being covered by an
   overlay. Nothing outside the Messenger stage moves. */
.scene-panel {
  order: 2;
  flex: 0 0 0;
  width: 0;
  height: 100%;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
  display: block;
  color: var(--ink);
  background: var(--panel-dash);
  backdrop-filter: blur(1px);
  -webkit-backdrop-filter: blur(1px);
  border-left: 1px solid rgba(184, 134, 11, 0);
  box-shadow: -12px 0 28px rgba(30, 48, 32, 0);
  opacity: 0;
  transform: translateX(42px);
  visibility: hidden;
  pointer-events: none;
  transition:
    flex-basis .44s cubic-bezier(.22, .74, .22, 1),
    width .44s cubic-bezier(.22, .74, .22, 1),
    transform .44s cubic-bezier(.22, .74, .22, 1),
    opacity .28s ease,
    border-color .28s ease,
    box-shadow .34s ease,
    visibility 0s linear .44s;
}
.scene-panel:focus { outline: none; }
.messenger-body.sidecar-active .scene-panel {
  flex-basis: clamp(250px, 44%, 430px);
  width: clamp(250px, 44%, 430px);
  opacity: 1;
  transform: translateX(0);
  visibility: visible;
  pointer-events: auto;
  border-left-color: rgba(184, 134, 11, .34);
  box-shadow: -12px 0 28px rgba(30, 48, 32, .17);
  transition:
    flex-basis .5s cubic-bezier(.18, .78, .22, 1),
    width .5s cubic-bezier(.18, .78, .22, 1),
    transform .5s cubic-bezier(.18, .78, .22, 1),
    opacity .34s ease-out,
    border-color .3s ease,
    box-shadow .4s ease,
    visibility 0s linear 0s;
}
/* When a workspace shares the Messenger stage, its left edge meets the
   conversation as one continuous vertical seam. Keep the normal rounded
   Messenger corners for the closed state; only the open sidecar removes the
   header's inner top-right curve and gives the two connected creams a little
   more weight. */
.messenger-body.sidecar-active .convo-head {
  border-top-right-radius: 0;
  background: rgba(246, 239, 219, .78);
}
.messenger-body.sidecar-active .scene-panel {
  border-top-left-radius: 0;
}
.messenger-body.sidecar-active #compose-wrap {
  background: linear-gradient(180deg, rgba(246, 239, 219, .60), rgba(246, 239, 219, .75));
}
.messenger-body.sidecar-active #compose-wrap:focus-within {
  background: linear-gradient(180deg, rgba(247, 231, 190, .64), rgba(246, 239, 219, .79));
}
.sidecar-shell {
  width: 100%;
  height: 100%;
  min-height: 0;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
}
.scene-panel-head {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  min-height: 68px;
  padding: 14px 16px 12px;
  border-bottom: 1px solid rgba(58, 84, 54, .13);
  background: transparent;
}
.scene-panel-title {
  display: flex;
  flex-direction: column;
  gap: 3px;
  flex: 1 1 auto;
  min-width: 0;
}
.scene-panel-eyebrow {
  color: var(--label-gold);
  font-size: var(--fs-label);
  font-weight: 800;
  letter-spacing: .1em;
  text-transform: uppercase;
}
.scene-panel-head h3 {
  overflow: hidden;
  margin: 0;
  color: var(--green-deep);
  font-family: var(--sans);
  font-size: var(--fs-feature);
  font-weight: 750;
  line-height: 1.08;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.scene-panel-head p {
  overflow: hidden;
  margin: 0;
  color: var(--ink-dim);
  font-size: var(--fs-meta);
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sidecar-body {
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  padding: 14px 16px 20px;
}
.sidecar-intro {
  margin: 0 0 14px;
  color: var(--ink-dim);
  font-size: .82em;
  line-height: 1.45;
}
.sidecar-section {
  margin-top: 18px;
}
.sidecar-section:first-child { margin-top: 0; }
.sidecar-label {
  margin-bottom: 8px;
  color: var(--label-gold);
  font-size: .66em;
  font-weight: 800;
  letter-spacing: .1em;
  text-transform: uppercase;
}
.meetup-live-scene {
  display: flex;
  align-items: center;
  gap: 7px;
  min-width: 0;
  padding: 4px 0 12px;
  color: var(--ink-dim);
  font-size: .8em;
  line-height: 1.35;
}
.meetup-live-scene span,
.meetup-live-scene strong {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.meetup-live-scene strong { color: var(--green-deep); font-weight: 800; }
.meetup-live-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
}
.meetup-live-chip {
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
  min-width: 0;
  padding: 6px 9px;
  border: 1px solid rgba(58, 84, 54, .14);
  border-radius: var(--r-pill);
  background: rgba(255, 253, 244, .68);
  color: var(--green-deep);
  font-size: .75em;
  font-weight: 720;
}
.meetup-live-chip[hidden] { display: none; }
.meetup-chip-label { color: var(--ink-faint); font-weight: 650; }
[data-live-field].is-updated {
  animation: meetupValueChanged .58s ease-out;
}
@keyframes meetupValueChanged {
  0% { color: var(--gold-deep); background: rgba(217, 169, 60, .22); transform: translateY(-1px) scale(1.04); }
  100% { color: inherit; background: transparent; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  [data-live-field].is-updated { animation: none; }
}
.sidecar-footer {
  padding: 12px 18px 16px;
  border-top: 1px solid rgba(58, 84, 54, .14);
  background: rgba(248, 242, 222, .72);
}
.sidecar-close {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 50%;
  color: var(--ink-dim);
  background: transparent;
  font-size: 1.2em;
  line-height: 1;
}
.sidecar-close:hover {
  color: var(--green-deep);
  border-color: rgba(58, 84, 54, .14);
  background: rgba(255, 253, 244, .7);
}
.sidecar-close:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
/* Settings > Manager avatar controls */
.mgr-avatar-row { display: flex; gap: 14px; align-items: flex-start; }
.mgr-avatar-preview {
  flex: 0 0 auto; width: 56px; height: 56px; border-radius: 50%;
  border: 2px solid var(--line-gold); background: var(--cream-2, var(--cream)) center/cover no-repeat;
  display: grid; place-items: center; font-weight: 900; font-size: 22px;
  color: var(--green-deep); box-shadow: 0 1px 3px rgba(40, 60, 35, .2);
}
.mgr-avatar-preview.has-image { color: transparent; }
.mgr-avatar-controls { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 8px; }
.mgr-avatar-presets { display: flex; flex-wrap: wrap; gap: 8px; }
.mgr-avatar-chip {
  width: 38px; height: 38px; border-radius: 50%; padding: 0; cursor: pointer;
  border: 2px solid var(--line-gold); background: center/cover no-repeat var(--cream-2, var(--cream));
  transition: .15s;
}
.mgr-avatar-chip:hover { transform: scale(1.05); }
.mgr-avatar-chip.is-active { border-color: var(--green-btn, var(--green-deep)); box-shadow: 0 0 0 2px var(--gold-bright); }
.mgr-avatar-actions { display: flex; flex-wrap: wrap; gap: 8px; }
.mgr-avatar-upload { cursor: pointer; }
.mgr-avatar-error { min-height: 14px; color: var(--red, #C64B3C); font-size: 12px; }
/* Simple inline club-name control in Settings - one label, one field, one
   Save button, consistent with the rest of the panel rather than a second
   settings surface of its own. */
.settings-field-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.settings-field-label { font-size: .86em; color: var(--ink-dim); flex: 0 0 auto; }
.settings-field-input { flex: 1 1 160px; min-width: 120px; margin-top: 0; }
.convo-thread {
  min-height: 0;
  overflow-y: auto;
  overflow-anchor: none; /* new bubbles never trigger browser scroll anchoring */
  overscroll-behavior: contain; /* wheel/touch at the ends never chains to the page */
  scroll-behavior: auto;
  /* A deliberate reading column. The centre panel is now the widest thing on
     screen, so left-padding alone would have thrown the conversation into
     one corner of a very wide box. The inline padding instead centres a
     capped measure: up to --thread-measure of conversation, with the
     scenery still visible either side of it rather than behind a panel.
     --thread-measure is declared on .convo so the composer below can align
     to the very same column. */
  /* Bottom padding widened from 12px (final UI precision pass): the last
     bubble read as glued straight to the composer above it with no
     breathing room at all. */
  padding: 18px max(22px, calc((100% - var(--thread-measure)) / 2 - 12px)) 22px max(34px, calc((100% - var(--thread-measure)) / 2 + 12px));
  display: flex;
  flex-direction: column;
  gap: 9px;
  /* No wash, no tint, no gradient over the wallpaper - a previous readability
     gradient here was a regression and has been removed. The wallpaper shows
     through completely unfiltered; readability comes from the bubbles' own
     solid, opaque colours instead. */
}
/* A short conversation sits near the composer rather than stranded at the
   top of a tall empty box. The auto margin absorbs whatever slack is left;
   the thread's own 18px top padding is what stops the first bubble hugging
   the header, and its 12px bottom padding is what keeps the last one from
   sitting on the input - so nothing is ever vertically centred or floating.
   margin-top:auto is used rather than justify-content:flex-end because it
   keeps the scroll container's own top reachable once the thread overflows -
   at which point the auto margin collapses to nothing and ordering,
   scrolling and the force-to-bottom behaviour are exactly as before. */
.convo-thread > :first-child { margin-top: auto; }
.scene-conversation-narration {
  align-self: center;
  max-width: 72ch;
  padding: 5px 12px;
  border-radius: var(--r-pill);
  color: var(--ink-dim);
  background: var(--cream);
  font-family: var(--serif);
  font-size: .82em;
  line-height: 1.3;
  text-align: center;
}
.bubble {
  /* ONE measure for every thread, DM and group alike. Bubbles still shrink
     to their content, so a two-word reply is a two-word bubble; the ceiling
     only stops a long message running the width of the panel. */
  max-width: min(50%, 54ch);
  padding: 9px 13px;
  border-radius: 14px;
  font-size: var(--fs-body);
  line-height: 1.45;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  position: relative;
  overflow: visible;
}
/* The workspace keeps its own fixed share of the stage, but opening it must
   not halve the reading width of an already-rendered conversation. The thread
   remains a vertical flex flow; only its bubble ceiling stays at the normal
   54-character measure while the sidecar is present. */
.messenger-body.sidecar-active .bubble {
  max-width: min(100%, 54ch);
}
.bubble.them .bubble-text {
  display: block;
  max-height: none;
  overflow: visible;
}
.bubble-measure {
  visibility: hidden;
  pointer-events: none;
  animation: none;
  transition: none;
}
.bubble-measure .bubble-text {
  display: block;
  -webkit-line-clamp: unset;
  line-clamp: unset;
  max-height: none;
  overflow: visible;
}
/* Consecutive messages from the same sender in the same broad period sit as
   a visual group. Messenger timestamps are deliberately not rendered. */
.bubble.grouped { margin-top: -6px; }
/* speech direction reads from the silhouette: incoming bubbles anchor
   bottom-left, replies bottom-right - same language as every messenger the
   player already knows */
.bubble.them {
  align-self: flex-start;
  background: var(--green-deep);
  color: #FAF5E4;
  border-bottom-left-radius: 5px;
  /* Softer still (final UI precision pass, was 0 1px 2px .08) - a whisper
     of depth only. These are dark, saturated blocks against a bright
     wallpaper - they already separate on colour alone, so any real drop
     shadow only made a run of them read as a stack of heavy slabs. Each
     speaker's own colour and the cream text contrast are untouched. */
  box-shadow: 0 1px 1px rgba(40, 60, 35, .06);
}
/* ---------- ONE avatar system for every incoming message ----------
   The face is carried by the MESSAGE, not by the thread: bubbleHtml() writes
   --msg-avatar (and data-has-face) on each incoming bubble, so a DM and a
   group draw the identical circle in the identical gutter from the identical
   rule. A group message is therefore the same row a DM message is - avatar
   left, bubble immediately right - rather than a second component with the
   portrait tucked inside the bubble.
   The gutter is reserved on EVERY incoming bubble, including grouped ones
   and senders with no image, so a run of messages stays flush with the one
   that carries the face and nothing shifts sideways mid-conversation. */
.convo-thread .bubble.them { margin-left: calc(var(--msg-avatar-size) + var(--msg-avatar-gap)); }
.convo-thread .bubble.them[data-has-face]:not(.grouped)::before {
  content: "";
  position: absolute;
  left: calc(-1 * (var(--msg-avatar-size) + var(--msg-avatar-gap)));
  /* Aligned with the top of the bubble, not the bottom (final UI precision
     pass) - the normal incoming-message pattern, where the face sits level
     with the first line of text rather than trailing low beside it. */
  top: 0;
  width: var(--msg-avatar-size);
  height: var(--msg-avatar-size);
  border-radius: 50%;
  background: var(--msg-avatar) center/cover no-repeat, var(--cream);
  border: 2px solid var(--line-gold);
  box-shadow: 0 1px 3px rgba(40, 60, 35, .2);
}
/* The player's own side carries no face at all. Right alignment already says
   whose message it is, and a repeated manager avatar down a run of replies
   was the single noisiest thing in a long thread. */

/* The sender's name: a quiet label at the TOP OF THE BUBBLE, never a second
   portrait. Group-only - a DM has one possible sender, so naming them on
   every message would be noise. */
.bubble-speaker {
  display: block;
  margin-bottom: 3px;
  color: var(--speaker-label, var(--gold-bright));
  font-size: .76em;
  font-weight: 800;
  letter-spacing: .01em;
}
.bubble.me {
  align-self: flex-end;
  background: #FFFDF4;
  border: 1px solid var(--line-gold-soft);
  border-bottom-right-radius: 5px;
  /* same restrained lift as .bubble.them - the thread should not read as a
     stack of cards floating off the wallpaper */
  box-shadow: 0 1px 2px rgba(40, 60, 35, .08);
  color: var(--ink);
}
/* ---------- one colour per colleague, everywhere ----------------------
   With every colleague in the same green, a group read as a wall of
   identical blocks and the eye had to fall back on the name label to work
   out who was talking. Each person therefore owns a MUTED colour, held at
   a similar darkness and saturation so no one voice dominates purely by
   being brighter, and all of them keep the same cream text contrast.

   This is the ONE colour source. It is keyed on [data-speaker], which
   bubbleHtml() writes on every incoming bubble in a DM as well as in a
   group, so a colleague looks the same in their own chat as they do in the
   staff group - Jack is Jack's teal in both. Adding a character means
   adding one line here and nowhere else. */
.bubble.them[data-speaker] { background: var(--speaker-bg, var(--green-deep)); }
/* Male staff: cool, structural tones - navy, steel, teal, forest, bronze. */
.bubble.them[data-speaker="arthur"] { --speaker-bg: #3B4A31; --speaker-label: #C9CFA4; }
.bubble.them[data-speaker="neil"]   { --speaker-bg: #2F3F58; --speaker-label: #A9C0DC; }
.bubble.them[data-speaker="jack"]   { --speaker-bg: #3F5568; --speaker-label: #B9C7D1; }
.bubble.them[data-speaker="oliver"] { --speaker-bg: #5A6B3F; --speaker-label: #D9DFC0; }
/* Female staff: a warm rose-through-plum family, same weight as the above. */
.bubble.them[data-speaker="priya"]   { --speaker-bg: #4C3350; --speaker-label: #D3B0DA; }
.bubble.them[data-speaker="elsie"]   { --speaker-bg: #5B3540; --speaker-label: #E4B4BB; }
.bubble.them[data-speaker="harriet"] { --speaker-bg: #423961; --speaker-label: #BCB2E4; }
.bubble.them[data-speaker="lena"]    { --speaker-bg: #4A2B3B; --speaker-label: #DCACC2; }

/* The player is the one voice that is never a shade of anyone else. The
   gold/ochre treatment still clashed against the autumn wallpaper, so the
   player is now a deep, muted teal instead - held at the same dark weight
   as every colleague's own colour. Jack's own teal shifted a touch more
   blue/steel (#3F5568, see above) so the two stay clearly distinct.
   Deliberately the SAME colour in a DM and in a group - a colleague's own
   colour never changes between their DM and the group, so the player's
   shouldn't either. */
.bubble.me {
  background: #3E6E69;
  border-color: rgba(33, 71, 67, .5);
  color: var(--cream);
}

/* ---------- group threads ----------
   Nothing about the SHAPE of a group message is group-specific any more:
   measure, padding, radius, avatar gutter and shadow all come from the rules
   above, so a group thread and a DM are visibly the same system. The only
   group-only thing left is the sender's name label, and the extra breathing
   room a change of speaker earns - a run from ONE speaker still closes up,
   so consecutive messages read as one person talking. */
.bubble.group-speaker { margin-top: 4px; }

/* system lines float on the wallpaper, so they carry their own solid pill
   of cream - readable over bright grass without a hard panel. Unchanged in
   groups: a system line belongs to the room, not to a speaker. */
.bubble.sys {
  align-self: center;
  /* Deliberately quieter than dialogue: a system line reports on the room,
     it is not someone speaking. Smaller, lower-contrast, no shadow, and only
     as wide as its own words rather than a full-width pill. */
  max-width: min(60%, 48ch);
  margin: 0;
  padding: 3px 11px;
  background: var(--cream);
  border: 0;
  box-shadow: none;
  border-radius: var(--r-pill);
  color: var(--ink-faint);
  font-size: .76em;
  font-style: italic;
  line-height: 1.35;
  text-align: center;
}

/* ---- Messenger emotional feedback -------------------------------------
   One tone per completed exchange, worn by the player's message and by the
   reply that answered it. Every tone lives on one inert overlay pinned to
   the bubble (.bubble-fx): a ring on the edge, a glow outside it and a
   short inset bloom that hugs the inner edge, so the bubble warms from
   within as well as from without. The inset blur is kept tight (12-14px)
   so it never washes over the text in the middle of the bubble, and the
   layer is inert, so it cannot shift layout, scroll the thread or block a
   control. Each tone sets its box-shadow ONCE and animates opacity alone,
   which keeps the accent on the compositor instead of repainting the
   bubble - and the wallpaper behind it - every frame.
   The matching micro-movement is played from js/message-effects.js through
   the Web Animations API with composite:"add", so it layers over the
   bubble's own entrance animation rather than replacing it.
   To add a tone: one rule here plus one entry in that file's TONES table. */
.bubble-fx {
  position: absolute;
  inset: 0;
  z-index: 1;
  border-radius: inherit;
  opacity: 0;
  pointer-events: none;
  will-change: opacity;
  animation-duration: var(--fx-duration, 1400ms);
  animation-delay: var(--fx-delay, 0ms);
  animation-timing-function: ease-out;
  animation-fill-mode: both;
}
/* the player's own message answers a beat earlier and a shade quieter: the
   reply is the emotional event, the outgoing bubble echoes it. --fx-peak is
   the one dial for overall strength - lower it to calm the whole system
   down, raise it to push every tone harder. */
.bubble.fx-active { --fx-peak: 1; }
.bubble.fx-active.fx-outgoing { --fx-peak: .72; }

/* positive: one warm gold breath */
.bubble.fx-positive .bubble-fx {
  box-shadow:
    inset 0 0 13px rgba(226, 198, 132, .5),
    0 0 0 2px rgba(214, 182, 110, .8),
    0 0 26px rgba(226, 198, 132, .6);
  animation-name: pbFxBreath;
}
/* romantic: two rose pulses, slower to arrive and slower to leave, with a
   wider outer halo - the one tone allowed to bloom past its own edge */
.bubble.fx-romantic .bubble-fx {
  box-shadow:
    inset 0 0 13px rgba(217, 127, 142, .42),
    0 0 0 2px rgba(217, 127, 142, .82),
    0 0 28px rgba(217, 127, 142, .56),
    0 0 54px rgba(217, 127, 142, .26);
  animation-name: pbFxHeartbeat;
}
/* neutral: a whisper - acknowledgement, not feedback */
.bubble.fx-neutral .bubble-fx {
  box-shadow:
    inset 0 0 12px rgba(214, 222, 208, .3),
    0 0 0 1.5px rgba(196, 206, 188, .62),
    0 0 16px rgba(170, 182, 162, .42);
  animation-name: pbFxWhisper;
}
/* awkward: hesitates before it appears, then the light goes flat - the wash
   is what separates it from neutral, which never dims the bubble at all */
.bubble.fx-awkward .bubble-fx {
  background: rgba(58, 50, 36, .13);
  box-shadow:
    inset 0 0 16px rgba(178, 152, 96, .5),
    0 0 0 2px rgba(166, 142, 92, .88),
    0 0 22px rgba(168, 140, 88, .55);
  animation-name: pbFxHesitate;
}
/* negative: the bubble briefly loses its light - a cool tint over the
   surface and a hard red edge that leaves quickly */
.bubble.fx-negative .bubble-fx {
  background: rgba(24, 16, 10, .09);
  box-shadow:
    inset 0 0 14px rgba(176, 68, 64, .44),
    0 0 0 2px rgba(176, 68, 64, .8),
    0 0 22px rgba(158, 62, 58, .5);
  animation-name: pbFxCool;
}

@keyframes pbFxBreath {
  0%, 100% { opacity: 0; }
  18%, 62% { opacity: var(--fx-peak, 1); }
}
@keyframes pbFxHeartbeat {
  0% { opacity: 0; }
  9% { opacity: var(--fx-peak, 1); }
  26% { opacity: calc(var(--fx-peak, 1) * .34); }
  40% { opacity: var(--fx-peak, 1); }
  76% { opacity: calc(var(--fx-peak, 1) * .4); }
  100% { opacity: 0; }
}
@keyframes pbFxWhisper {
  0%, 100% { opacity: 0; }
  32%, 56% { opacity: calc(var(--fx-peak, 1) * .78); }
}
@keyframes pbFxHesitate {
  0%, 34% { opacity: 0; }
  56%, 74% { opacity: var(--fx-peak, 1); }
  100% { opacity: 0; }
}
@keyframes pbFxCool {
  0%, 100% { opacity: 0; }
  12%, 54% { opacity: var(--fx-peak, 1); }
}

@media (prefers-reduced-motion: reduce) {
  /* colour still carries the tone; the pulsing and the micro-movement
     (suppressed in message-effects.js) do not */
  .bubble.fx-active .bubble-fx {
    animation-name: pbFxWhisper;
    animation-duration: 1200ms;
    animation-delay: 0ms;
  }
  .bubble.bubble-morph-in { animation: pbBootFade .2s ease-out backwards; }
}

/* period divider: a hairline with a small centred label whenever the
   conversation moves to a new morning/afternoon/evening. Toned down
   further (final UI precision pass) - still readable, but a lighter
   weight and quieter colour keep it plainly secondary to the dialogue
   either side of it rather than competing for attention. */
.thread-divider {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 4px 0 0;
  color: rgba(27, 45, 32, .46);
  font-size: .7em;
  font-weight: 650;
  letter-spacing: .08em;
  text-transform: uppercase;
  user-select: none;
}
.thread-divider::before, .thread-divider::after {
  content: "";
  flex: 1 1 auto;
  height: 1px;
  background: rgba(58, 84, 54, .1);
}
.thread-divider span { flex: 0 0 auto; padding: 1px 9px; background: var(--cream); border-radius: var(--r-pill); }

/* Empty thread: a faint paw above the note.

.convo-empty {
  flex: 1;
  display: grid;
  place-items: center;
  color: var(--ink-faint);
  font-style: italic;
  text-align: center;
  align-content: center;
  gap: 8px;
}
.convo-empty-icon { width: 28px; height: 28px; opacity: .3; color: var(--green-deep); }

/* the composer belongs to the conversation, not to the left rail: it used
   to share almost the same rich, mostly-opaque cream as .char-card
   (--panel-contact, .85 alpha), which read as another floating character
   card stacked under the thread rather than the Messenger's own footer.
   Lighter and more translucent instead - the wallpaper stays gently visible
   through it - and the top edge is a single soft hairline rather than a
   shell-weight border, so the seam against the thread above reads as flat
   and attached, not another card's top edge. Left character cards
   (--panel-contact/.char-card) are untouched. */
#compose-wrap {
  width: 100%;
  margin: 0;
  align-self: stretch;
  min-width: 0;
  overflow: hidden;
  background: linear-gradient(180deg, rgba(253, 248, 235, .5), rgba(253, 248, 235, .68));
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
  border-radius: 0;
}
.compose {
  display: flex;
  align-items: flex-end;
  gap: 8px;
  /* The footer sits on the SAME reading column the thread uses, so the input
     lines up with the conversation above it instead of floating in a wider
     tray. Vertical padding is trimmed to what the input needs - the old
     12/10 plus a 42px input plus the hint made the footer taller than three
     messages. Gap to the send button widened slightly (10px -> 12px, final
     UI precision pass) for clearer separation. */
  gap: 12px;
  padding: 9px max(22px, calc((100% - var(--thread-measure)) / 2 - 12px));
  /* A clearer seam against the thread above (.09 -> .18 alpha) - the
     composer read as barely attached to the conversation frame. */
  border-top: 1px solid rgba(58, 84, 54, .18);
  background: transparent;
}
/* the composer strip warms faintly while it holds focus - "armed to send" */
#compose-wrap:focus-within {
  background: linear-gradient(180deg, rgba(247, 231, 190, .58), rgba(253, 248, 235, .72));
}
.compose-hint { background: transparent; }
.compose textarea {
  flex: 1;
  min-width: 0;
  resize: none;
  /* 38px keeps a comfortable single-line target while giving the footer
     back four pixels. Mirrored by resizeComposer()'s minHeight in app.js -
     the two must stay in step or the box jumps on first keystroke. */
  height: 38px;
  min-height: 38px;
  max-height: 132px;
  overflow-y: hidden;
  line-height: 1.35;
  box-sizing: border-box;
  /* a pill needs more inset than a rectangle before text stops looking like
     it is leaning on the curve. Left inset widened a touch further (18px ->
     20px) so typed text has clear room before the curve. */
  padding: 8px 20px;
  background: rgba(255, 253, 244, .96);
  /* Stronger resting border (.16 -> .32 alpha, final UI precision pass) -
     the pill was reading as barely-there against the cream footer. The gold
     focus state below still carries the extra emphasis on top of this. */
  border: 1px solid rgba(58, 84, 54, .32);
  border-radius: var(--r-pill);
  color: var(--ink);
  font-size: var(--fs-body);
  transition: border-color .15s ease, box-shadow .15s ease, border-radius .15s ease;
}
/* once the reply grows past one line the pill relaxes into a rounded box */
.compose textarea.is-multiline { border-radius: 14px; }
/* the focus ring was a full-strength gold border plus a 3px halo, which on a
   cream field was the loudest thing in the panel. A muted gold edge and a
   thinner, fainter halo still say "this is where you are typing" without
   competing with the conversation above it. */
.compose textarea:focus { outline: none; border-color: rgba(184, 134, 11, .58); box-shadow: 0 0 0 2px rgba(184, 134, 11, .1); }
/* Nudged darker again (.68 -> .75 alpha, final UI precision pass) - still
   short of the field's own ink colour, but no longer reading as pale/weak. */
.compose textarea::placeholder { color: rgba(27, 45, 32, .75); }
/* ONE owner for the resting look. The button used to be dimmed three times
   over - a translucent fill, :disabled opacity+saturate, and ambient.js's
   .pb-idle filter+opacity on top - which stacked into something that read
   as broken rather than inactive. The resting state is now a clean outlined
   control: plainly not armed, but plainly still a button. Text present
   (.is-ready, owned by syncComposer) fills it with the club green. */
.btn-send {
  position: relative;
  width: 36px;
  height: 36px;
  flex: 0 0 36px;
  align-self: center;
  display: grid;
  place-items: center;
  padding: 0;
  line-height: 0;
  background: rgba(255, 253, 244, .9);
  border: 1px solid rgba(58, 84, 54, .32);
  color: rgba(58, 84, 54, .6);
  border-radius: 50%;
  transition: background .15s ease, border-color .15s ease, color .15s ease, transform .09s ease;
}
/* armed: filled green, a gold keyline and a soft lift - the same "primary
   action" language Continue uses in the top bar, so "there is something to
   send" is unmistakable at a glance rather than a subtle fill change. */
.btn-send.is-ready {
  background: linear-gradient(180deg, var(--green-mid), var(--green-deep));
  border-color: rgba(217, 169, 60, .6);
  color: #FBF7EA;
  box-shadow: inset 0 1px 0 rgba(255, 251, 235, .22), 0 2px 6px rgba(40, 60, 35, .26);
}
.btn-send:disabled { cursor: default; opacity: 1; filter: none; transform: none; }
.btn-send:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.btn-send::before {
  content: "";
  position: absolute;
  inset: -2px;
  border-radius: 50%;
}
.btn-send:hover { filter: brightness(1.06); }
.btn-send:not(.is-ready):hover { background: #FFFDF4; border-color: rgba(58, 84, 54, .34); color: rgba(58, 84, 54, .72); }
.btn-send:active { transform: scale(.95); }
/* "message flew" gesture on send */
.btn-send.send-fly .ui-icon { animation: sendFly .28s ease; }
@keyframes sendFly {
  0% { transform: translateX(0); opacity: 1; }
  45% { transform: translateX(12px); opacity: 0; }
  55% { transform: translateX(-10px); opacity: 0; }
  100% { transform: translateX(0); opacity: 1; }
}
.btn-send .ui-icon {
  display: block;
  width: 14px;
  height: 14px;
  margin: 0;
  vertical-align: middle;
}
.compose-hint {
  flex: 0 0 auto;
  /* aligned to the same reading column as the input above it, and trimmed:
     this line is the last thing between the conversation and the panel edge */
  padding: 0 max(20px, calc((100% - var(--thread-measure)) / 2)) 6px;
  font-size: .74em;
  color: var(--ink-faint);
  transition: opacity .3s ease;
}
.compose-hint.hint-hide { opacity: 0; }

/* Active Meetup metadata stays compact and conversational. The thread,
   bubbles and composer remain the one normal Messenger conversation. */

/* ---------- next match ----------
   The fixture is presented as a match card: opponent front and centre at
   the display step with a clear Home/Away chip, competition and kickoff as
   one quiet sub-line beneath. */
.match-row > div { min-width: 0; }
.match-opp {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
  font-family: var(--serif);
  font-weight: 700;
  font-size: var(--fs-display);
  line-height: 1.15;
  color: var(--green-deep);
  text-wrap: balance;
}
.match-venue {
  flex: 0 0 auto;
  align-self: center;
  padding: 2px 8px;
  border-radius: var(--r-pill);
  border: 1px solid var(--line-soft);
  font-family: var(--sans);
  font-size: .56em;
  font-weight: 750;
  letter-spacing: .02em;
}
.match-venue.home { color: var(--green-deep); background: rgba(55, 102, 63, .1); }
.match-venue.away { color: var(--gold-deep); background: rgba(184, 134, 11, .1); }
.match-sub { margin-top: 3px; font-size: var(--fs-meta); color: var(--ink-dim); text-wrap: pretty; }
/* quiet countdown line under the fixture date - same family as .match-sub,
   one step smaller and gold-toned like the card's other accent text
   (.match-eyebrow, .panel-h2-go), so it reads as a supporting detail rather
   than a second headline. Hidden by default (see index.html); JS unhides it
   only when there is a real upcoming fixture. */
/* removed by request - "In N days" / "Tomorrow" no longer shown under the
   match date. app.js still sets its text/hidden state each render; this
   rule just keeps it permanently off rather than touching that logic. */
.match-countdown,
.match-countdown[hidden] { display: none; }
.pressure { margin-top: 10px; }
.pressure .lbl { display: flex; justify-content: space-between; font-size: .85em; color: var(--ink-dim); margin-bottom: 4px; }
.pressure .lbl span:last-child { font-weight: 700; color: var(--ink); }
.pressure .bar { height: 5px; background: rgba(62, 88, 58, .15); border-radius: 3px; overflow: hidden; }
.pressure .bar span { display: block; height: 100%; background: linear-gradient(90deg, var(--gold-bright), var(--bad)); border-radius: 3px; }
.col-right .pressure { margin-top: 6px; }

/* .match-facts/.match-fact rules removed: the markup they styled
   (#match-form / #match-difficulty rows) is not in index.html, so they had
   no host on any screen. renderMatchFacts() in app.js is already guarded on
   those elements existing and is left untouched. */
.form-pip {
  display: grid;
  place-items: center;
  width: 15px;
  height: 15px;
  border-radius: 4px;
  font-size: 9px;
  font-weight: 800;
  color: #FBF7EA;
}
.form-pip.w { background: var(--ok); }
.form-pip.l { background: var(--bad); }
.form-none { font-weight: 500; color: var(--ink-faint); font-size: .92em; }

/* ---------- tactics ---------- */
.tactics-cur { font-family: var(--serif); font-weight: 700; font-size: var(--fs-feature); color: var(--green-deep); }
/* derived DEF-MID-ATT shape, presented like a team-sheet notation */
.tactics-shape {
  flex: 0 0 auto;
  padding: 2px 9px;
  border: 1px solid var(--line-gold-soft);
  border-radius: var(--r-pill);
  background: var(--gold-tint);
  color: var(--gold-deep);
  font-size: .78em;
  font-weight: 800;
  letter-spacing: .08em;
  font-variant-numeric: tabular-nums;
}
.tactics-shape:empty { display: none; }
.tactics-form { color: var(--ink-dim); font-size: .9em; margin-top: 2px; }
.pitch {
  margin-top: 10px;
  aspect-ratio: 1665 / 897; /* matches the supplied pitch image exactly - never stretched or cropped */
  width: 100%;
  border-radius: 10px;
  background: url("../images/pitch/pitch-half.png") center center / cover no-repeat;
  position: relative;
  overflow: hidden;
  /* a soft inner vignette settles the bright grass into the card and lifts
     the markers off it - reads as a lit tactical board, not a flat photo */
  box-shadow: inset 0 0 0 1px rgba(253, 248, 235, .55), inset 0 -18px 30px -18px rgba(20, 40, 20, .35);
}
/* the grass sits inside the cream/gold palette instead of shouting over it:
   a warm haze plus desaturation on the background only - avatars stay crisp */
.pitch::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  /* a much lighter wash than before: the tactical board is allowed to be
     properly green, it just can't glare - so the markers pop against it */
  background: linear-gradient(180deg, rgba(253, 248, 235, .08), rgba(246, 239, 219, .14));
  backdrop-filter: saturate(.88);
  -webkit-backdrop-filter: saturate(.88);
}
.pitch-link { cursor: pointer; transition: filter .12s ease, box-shadow .12s ease; }
.pitch-link:hover, .pitch-link:focus-visible { filter: brightness(1.05); box-shadow: 0 0 0 2px var(--line-gold); }
.pitch-link:focus-visible { outline: none; }
/* Formation markers: no dog identity, name or number - but each marker DOES
   carry the position it represents in that formation slot, using the exact
   same GK gold / DEF blue / MID green / ATT red badge language as the Pack
   list and profiles (.pos-gk/.pos-def/.pos-mid/.pos-att, set in the "dog
   roster: position accents" rules above) instead of a generic blank circle. */
.pitch .tactic-marker {
  position: absolute;
  z-index: 1;
  width: 29px;
  height: 29px;
  display: flex;
  align-items: center;
  justify-content: center;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  /* Solid (not the alpha-based --dog-accent-tint used on opaque cards) -
     these markers sit directly over the pitch photo, so an alpha tint let
     the grass show through and looked washed out/transparent. */
  background: var(--dog-accent-tint-solid, #F1E3C4);
  color: var(--dog-accent, #3F6848);
  font-size: .66em;
  font-weight: 800;
  letter-spacing: .01em;
  /* accent ring + a thin cream halo lifts each disc off the grass like a
     magnet on a tactics board */
  box-shadow:
    0 0 0 2px var(--dog-accent, #3F6848),
    0 0 0 4px rgba(253, 248, 235, .8),
    0 3px 7px rgba(20, 30, 15, .4);
  pointer-events: none;
}
.dev-relationship-control {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 8px 12px;
  padding: 10px;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-card);
  background: rgba(255, 253, 244, .7);
}
.dev-relationship-control input,
.dev-relationship-control select,
.dev-relationship-control button { grid-column: 1 / -1; }
.dev-seasonal-preview { padding: 10px; }
.dev-seasonal-grid {
  display: grid;
  grid-template-columns: minmax(74px, auto) minmax(0, 1fr);
  align-items: center;
  gap: 7px 10px;
}
.dev-seasonal-grid select { min-width: 0; width: 100%; }
.dev-seasonal-current { display: block; margin-top: 8px; color: var(--cream-dim); font-size: 12px; }

/* ---------- development-only Meetup launcher ---------- */
.meetup-dev-modal { width: min(720px, calc(100vw - 64px)); }
.meetup-dev-modal .body { max-height: min(68vh, 720px); overflow-y: auto; }
.meetup-dev-intro { margin: 0 0 14px; color: var(--ink-dim); line-height: 1.45; }
.meetup-dev-form {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}
.meetup-dev-location-fields { display: contents; }
.meetup-dev-location-fields[hidden] { display: none; }
.meetup-dev-field {
  display: grid;
  gap: 5px;
  color: var(--green-deep);
  font-size: .82em;
  font-weight: 750;
}
.meetup-dev-field select {
  width: 100%;
  min-width: 0;
}
.meetup-dev-participants {
  grid-column: 1 / -1;
  margin: 0;
  padding: 11px;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-card);
}
.meetup-dev-participants legend {
  padding: 0 5px;
  color: var(--green-deep);
  font-size: .82em;
  font-weight: 800;
}
.meetup-dev-manager {
  display: block;
  margin-bottom: 8px;
  color: var(--ink-faint);
  font-size: .74em;
}
.meetup-dev-participants > div {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 7px;
}
.meetup-dev-participant {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  padding: 7px 8px;
  border: 1px solid rgba(58, 84, 54, .14);
  border-radius: 8px;
  background: rgba(255, 253, 244, .72);
  color: var(--ink);
  font-size: .78em;
}
.meetup-dev-participant span {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.meetup-dev-note {
  grid-column: 1 / -1;
  margin: 0;
  color: var(--ink-faint);
  font-size: .72em;
  line-height: 1.45;
}
.meetup-dev-note code { color: var(--green-deep); }
/* ---------- Pawball Radio ---------- */
.radio-wrap { position: relative; flex: 0 0 auto; }
.radio-toggle { overflow: hidden; }
.radio-symbol { display: flex; transform: translateY(-1px); }
.radio-eq {
  position: absolute;
  left: 50%;
  bottom: 3px;
  height: 7px;
  display: flex;
  align-items: flex-end;
  gap: 2px;
  opacity: 0;
  transform: translateX(-50%);
  transition: opacity .15s ease;
}
.radio-eq span {
  width: 2px;
  height: 3px;
  border-radius: 2px;
  background: var(--gold-deep);
}
.radio-toggle.is-playing .radio-eq { opacity: .9; }
.radio-toggle.is-playing .radio-eq span { animation: radioPulse .8s ease-in-out infinite alternate; }
.radio-toggle.is-playing .radio-eq span:nth-child(2) { animation-delay: -.35s; }
.radio-toggle.is-playing .radio-eq span:nth-child(3) { animation-delay: -.6s; }
@keyframes radioPulse { to { height: 7px; } }

.radio-popover {
  position: absolute;
  top: calc(100% + 10px);
  right: 0;
  z-index: 60;
  width: min(280px, calc(100vw - 24px));
  padding: 14px;
  color: var(--ink);
  background: var(--cream);
  border: 1px solid var(--line-gold-soft);
  border-radius: var(--r-panel);
  box-shadow: 0 12px 30px rgba(35, 55, 30, .28);
}
.radio-popover[hidden] { display: none; }
.radio-heading { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.radio-heading h2 {
  margin: 0;
  font-family: var(--serif);
  font-size: 1.12em;
  color: var(--green-deep);
}
.radio-power {
  flex: 0 0 auto;
  padding: 5px 9px;
  border: 1px solid var(--line-gold-soft);
  border-radius: var(--r-pill);
  color: var(--green-deep);
  background: var(--cream-2);
  font-size: .78em;
  font-weight: 700;
}
.radio-power[aria-pressed="true"] { color: #FBF7EA; background: var(--green-btn); }
.radio-now-label { margin-top: 12px; color: var(--ink-faint); font-size: .76em; text-transform: uppercase; letter-spacing: .1em; }
.radio-track { margin-top: 2px; font-family: var(--serif); font-weight: 700; color: var(--green-deep); }
.radio-status { min-height: 1.3em; margin-top: 2px; color: var(--ink-dim); font-size: .78em; }
.radio-status.error { color: var(--bad); }
.radio-controls { display: flex; justify-content: center; align-items: center; gap: 8px; margin: 12px 0; }
.radio-controls button {
  width: 32px;
  height: 32px;
  display: grid;
  place-items: center;
  color: #FBF7EA;
  background: var(--green-btn);
  border: 1px solid var(--line-gold-soft);
  border-radius: 50%;
  font-size: 11px;
}
.radio-controls .radio-play { width: 38px; height: 38px; font-size: 13px; }
.radio-controls button:hover, .radio-power:hover { filter: brightness(1.08); }
.radio-controls .ui-icon { width: 13px; height: 13px; }
.radio-controls .radio-play .ui-icon { width: 15px; height: 15px; }
.radio-volume { display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center; gap: 10px; font-size: .82em; color: var(--ink-dim); }
.radio-volume input { width: 100%; accent-color: var(--gold-deep); }

/* ---------- club log ---------- */
.log-row {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  padding: 6px 0;
  border-bottom: 1px solid var(--line-soft);
  font-size: .92em;
}
.log-row:last-child { border-bottom: 0; }
.log-row .d { color: var(--ink-faint); white-space: nowrap; font-size: .88em; }
.log-row-note { padding: 0 0 6px; font-size: .84em; color: var(--ink-dim); font-style: italic; }
.season-history-entry { padding: 8px 0 10px; border-bottom: 1px solid var(--line-soft); }
.season-history-entry:last-of-type { border-bottom: 0; }
.season-history-entry .log-row { padding: 0; border-bottom: 0; }
.season-history-entry .season-note-section { margin-top: 7px; padding-top: 7px; }
.season-note-list { margin: 0; padding-left: 19px; color: var(--ink-dim); font-size: .9em; line-height: 1.4; }
.season-note-list li + li { margin-top: 4px; }
/* fixed shell + scrollable inner list, same pattern as .fixtures-modal -
   the log can only ever grow over a long save */
.modal.log-modal {
  width: min(620px, 94vw);
  max-height: 88vh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
.log-modal .body { min-height: 0; display: flex; flex-direction: column; }
.log-scroll {
  min-height: 160px;
  max-height: min(64vh, 620px);
  overflow-y: auto;
  overscroll-behavior: contain;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-card);
  background: rgba(255, 253, 244, .68);
  padding: 2px 12px;
}

/* ---------- full season fixtures ---------- */
.modal.fixtures-modal {
  width: min(1040px, 96vw);
  max-height: 92vh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
.fixtures-modal .body { min-height: 0; display: flex; flex-direction: column; }
.fixtures-meta { display: flex; justify-content: space-between; gap: 12px; margin-bottom: 8px; color: var(--ink-dim); font-size: .84em; }
.fixtures-head,
.fixture-row {
  display: grid;
  grid-template-columns: 112px 62px minmax(190px, 1fr) 88px minmax(190px, 1fr) 92px;
  align-items: center;
  gap: 10px;
}
.fixtures-head { padding: 6px 12px; color: var(--ink-faint); font-size: .72em; font-weight: 750; letter-spacing: .06em; text-transform: uppercase; }
.fixtures-scroll { min-height: 220px; max-height: min(64vh, 650px); overflow-y: auto; overscroll-behavior: contain; border: 1px solid var(--line-soft); border-radius: var(--r-card); background: rgba(255, 253, 244, .68); scroll-behavior: smooth; }
.fixture-group-label { position: sticky; top: 0; z-index: 1; padding: 7px 12px; background: rgba(246, 239, 219, .97); border-bottom: 1px solid var(--line-gold-soft); color: var(--gold-deep); font-size: .74em; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; }
.fixture-row { min-height: 48px; padding: 7px 12px; border-bottom: 1px solid var(--line-soft); font-size: .9em; font-variant-numeric: tabular-nums; }
.fixture-row:last-child { border-bottom: 0; }
.fixture-row.next { background: linear-gradient(90deg, rgba(217, 169, 60, .2), rgba(253, 248, 235, .68)); box-shadow: inset 4px 0 var(--gold-bright); }
.fixture-row.future { color: var(--ink-dim); }
.fixture-date { color: var(--ink-dim); white-space: nowrap; }
.fixture-venue { width: fit-content; padding: 3px 7px; border-radius: 999px; border: 1px solid var(--line-soft); font-size: .76em; font-weight: 750; }
.fixture-venue.home { color: var(--green-deep); background: rgba(55, 102, 63, .08); }
.fixture-venue.away { color: var(--gold-deep); background: rgba(184, 134, 11, .08); }
.fixture-team { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fixture-team.player { color: var(--green-deep); font-weight: 750; }
.away-team { text-align: left; }
.fixture-score { text-align: center; font-family: var(--serif); font-size: 1.08em; color: var(--green-deep); }
.fixture-ko { text-align: center; color: var(--ink-dim); }
.fixture-outcome { justify-self: start; display: grid; place-items: center; width: 28px; height: 28px; border-radius: 7px; color: #fff; font-size: .78em; font-weight: 850; }
.fixture-outcome.win { background: var(--ok); }
.fixture-outcome.draw { background: var(--gold-deep); }
.fixture-outcome.loss { background: var(--bad); }
.fixture-next-label { width: fit-content; padding: 4px 7px; border-radius: 999px; background: var(--green-btn); color: var(--cream); font-size: .72em; font-weight: 750; white-space: nowrap; }
/* ---------- character cards ---------- */
.char-card {
  flex: 0 0 auto;
  width: var(--contact-card-width, clamp(90px, 7.2vw, 110px));
  padding: 9px 5px 5px;
  background: var(--panel-contact);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-card);
  box-shadow: var(--shadow-card);
  text-align: center;
  position: relative;
  scroll-snap-align: start;
  scroll-snap-stop: always;
  transition: background-color .16s ease, transform .16s ease, border-color .16s ease, box-shadow .16s ease;
}
.char-card:last-child { scroll-snap-align: end; }
/* a quiet lift, no scale: nine cards side by side should answer the cursor
   without the whole rail appearing to wobble */
.char-card:hover { transform: translateY(-3px); border-color: var(--line-gold); box-shadow: 0 8px 18px rgba(40, 60, 35, .26); }
.char-card:hover .portrait-frame { border-color: var(--gold-bright); box-shadow: 0 0 0 3px rgba(217, 169, 60, .25), 0 1px 4px rgba(40, 60, 35, .2); }
.char-card:active { transform: translateY(-1px) scale(.97); }
.char-card.selected {
  transform: translateY(-2px);
  border-color: var(--green-deep);
  box-shadow: 0 0 0 2px var(--gold-bright), 0 8px 18px rgba(40, 60, 35, .28);
}
.char-card.selected .portrait-frame {
  border-color: var(--green-deep);
  box-shadow: 0 0 0 3px rgba(217, 169, 60, .3), 0 1px 4px rgba(40, 60, 35, .2);
}
.char-card.scene-participant {
  opacity: 1;
  border-color: var(--line-gold);
}
.char-card.scene-locked {
  opacity: .52;
  cursor: not-allowed;
  filter: saturate(.72);
}
.char-card.scene-locked:hover,
.char-card.scene-locked:active {
  transform: none;
  box-shadow: var(--shadow-card);
}
.char-card .avwrap { position: relative; display: inline-block; margin: 1px auto 3px; }
.char-card .portrait-frame {
  box-shadow: 0 1px 4px rgba(40, 60, 35, .2);
  width: clamp(50px, 4.8vw, 64px);
  height: clamp(50px, 4.8vw, 64px);
  border-radius: 50%;
  display: block;
}
.char-card .online-dot {
  position: absolute;
  right: 2px;
  bottom: 3px;
  width: 11px;
  height: 11px;
  border: 2px solid var(--cream);
  border-radius: 50%;
  background: #35A853;
  box-shadow: 0 1px 3px rgba(40, 74, 42, .35);
}
.char-card .nm {
  font-weight: 700;
  font-size: .95em;
  line-height: 1.25;
  color: var(--green-deep);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.char-card .rl {
  font-size: .76em;
  line-height: 1.2;
  /* was --ink-faint (~4.1:1 on the card's cream fill, under the 4.5:1 text
     minimum, worse still against the wallpaper bleeding through it) -
     --ink-dim is the same ink at higher opacity, already used for role text
     elsewhere (.convo-head .role etc.), so this just brings the rail card in
     line rather than introducing a new colour. */
  color: var(--ink-dim);
  margin-top: 0;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  /* reserve space for 2 lines always, so a role that wraps never makes its
     card taller than a role that fits on one line */
  min-height: calc(1.2em * 2);
}
.char-card .md { display: flex; align-items: center; justify-content: center; gap: 4px; font-size: .8em; margin-top: 0; font-weight: 600; white-space: nowrap; min-height: 15px; }
.char-card .md .status-icon { width: 13px; height: 13px; }
/* Darker than the shared --ok/--warn/--bad tokens (which measure roughly
   3.6:1 / 2.5:1 / 3.7:1 on this card's cream fill - all under the 4.5:1 text
   minimum, --warn worst of all) - scoped to the card only, so the mood bars
   and every other use of those tokens elsewhere keeps its current colour;
   this just fixes the one spot that read too faint. Same hues, just darker. */
.char-card .md.good { color: #2F6E3F; }
.char-card .md.mid { color: #8C6318; }
.char-card .md.poor { color: #A34E3D; }
/* contact cards show unread status only - no online/available indicator here.
   Attached to the portrait's own top-right edge (not the wider card edge) with
   a small inset, via the shared .unread-badge component. */
.char-card .unread-badge.on-portrait {
  top: -3px;
  right: -3px;
  box-shadow: 0 0 6px rgba(198, 75, 60, .5);
  z-index: 2;
}
/* new-message glow: a warm gold halo around the portrait, not the whole card,
   so it never resizes the card or competes with the selected-state ring. It
   clears the instant S.unread[id] drops to 0 on read, same as the badge
   above (renderStrip only adds .has-unread when unread > 0).
   The portrait already wears a permanent 2px var(--line-gold) border at full
   opacity, so an unread ring in that same muted gold barely read against it
   - almost no visible contrast between "always-on border" and "new message".
   Using --gold-bright for the ring (a distinctly lighter/warmer gold) plus a
   wider, stronger outer bloom gives it real separation from the resting
   state, and the card itself picks up a faint matching bloom so the glow
   also reads against the woodland wallpaper behind the rail, not just the
   portrait's own small circle. */
.char-card.has-unread:not(.selected) {
  box-shadow: 0 0 16px 2px rgba(217, 169, 60, .45), var(--shadow-card);
  animation: pbUnreadCardGlow 2.4s ease-in-out infinite;
}
.char-card.has-unread .portrait-frame {
  border-color: var(--gold-bright);
  box-shadow: 0 0 0 3px var(--gold-bright), 0 0 18px 4px rgba(217, 169, 60, .85), 0 1px 4px rgba(40, 60, 35, .2);
  animation: pbUnreadGlow 2.4s ease-in-out infinite;
}
.char-card.has-unread.selected .portrait-frame {
  box-shadow: 0 0 0 3px var(--gold-bright), 0 0 18px 4px rgba(217, 169, 60, .85), 0 1px 4px rgba(40, 60, 35, .2);
}
@keyframes pbUnreadGlow {
  0%, 100% { box-shadow: 0 0 0 3px var(--gold-bright), 0 0 14px 3px rgba(217, 169, 60, .7), 0 1px 4px rgba(40, 60, 35, .2); }
  50% { box-shadow: 0 0 0 3px var(--gold-bright), 0 0 22px 5px rgba(217, 169, 60, 1), 0 1px 4px rgba(40, 60, 35, .2); }
}
@keyframes pbUnreadCardGlow {
  0%, 100% { box-shadow: 0 0 14px 1px rgba(217, 169, 60, .35), var(--shadow-card); }
  50% { box-shadow: 0 0 20px 3px rgba(217, 169, 60, .6), var(--shadow-card); }
}

/* ---------- people rail rows ----------
   The same .char-card component, re-laid out for the vertical rail. Nine
   squat cards in a horizontal strip were a scanning problem: faces were the
   only thing readable at that size, the row competed for width with the
   conversation, and there was no room to grow. As rows the portrait leads,
   the name and role sit beside it, and the rail simply gets longer as the
   club does - it scrolls locally rather than forcing another redesign.
   Every class, data attribute and animation hook is unchanged, so unread
   badges, presence dots, scene locking and selection all behave as before. */
.rail-list .char-card {
  /* The rail is now a capped 230-260px column, so the avatar no longer
     scales with viewport WIDTH - it would only ever be answering a
     measurement that no longer moves. What it must still answer is HEIGHT:
     the rail has no scroll of its own by request, so eleven rigid rows have
     to fit whatever vertical room the window leaves. The clamp floor keeps
     a face readable at 1024x768.
     Height and the row insets are declared on .rail-list (layout.css) so the
     group rows share exactly the same geometry, and so the selected row's
     outline clears the portrait instead of hugging it.
     Rows GROW: the rail is a fixed-height column and the cast is a fixed
     length, so rigid rows left a dead gap below Arthur that got taller the
     taller the window was. Every row now shares the leftover equally
     (flex-grow 1 from an identical basis), so the list always reaches the
     bottom of the rail and the rhythm stays even. --rail-avatar-size is
     sized to the same measurement in layoutContactStrip(), so the portraits
     grow with the rows rather than floating in air. */
  flex: 1 1 var(--rail-card-height);
  max-height: 64px;
  width: 100%;
  min-height: var(--rail-card-height);
  box-sizing: border-box;
  display: grid;
  /* Name, full role and mood form one compact three-line text block. Nothing
     competes horizontally, so names and canonical titles remain intact and
     mood reads as supporting information rather than a detached column. */
  grid-template-columns: var(--rail-avatar-size) minmax(0, 1fr);
  grid-template-rows: auto auto minmax(0, 1fr);
  column-gap: 6px;
  align-items: center;
  padding: var(--rail-row-inset-y) var(--rail-row-inset-x);
  text-align: left;
  scroll-snap-align: none;
  scroll-snap-stop: normal;
  /* No fill, no border, no shadow at rest. The rail's own surface already
     handles readability; a row only takes a fill when it means something. */
  background: transparent;
  border: 0;
  border-radius: 10px;
  box-shadow: none;
}
/* Owner card (last in the rail) matches every other row exactly - same
   spacing, no divider - by request. */
.rail-list .char-card .avwrap { grid-column: 1; grid-row: 1 / 4; margin: 0; align-self: center; }
.rail-list .char-card .rail-name-line {
  grid-column: 2;
  grid-row: 1;
  /* Stretch, then bottom-align the contents - NOT align-self:end on an
     auto-height box. An auto-height box is as tall as its tallest child, and
     the unread badge (17px) is fractionally taller than the name's line box
     (~16.7px), so a badge appearing or clearing moved the name by a fraction
     of a pixel - enough to make the whole column look like it twitched when
     selecting a contact cleared its unread. Stretching pins the box to the
     track, so nothing inside it can move the name again. */
  align-self: stretch;
  min-width: 0;
  display: flex;
  align-items: flex-end;
}
.rail-list .char-card .nm { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rail-list .char-card .rail-meta {
  grid-column: 2;
  grid-row: 2;
  align-self: stretch;
  display: flex;
  align-items: flex-end;
  min-width: 0;
  min-height: 0;
}
.rail-list .char-card .rl {
  min-width: 0;
  min-height: 0;
  width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  display: block;
}
.rail-list .char-card .md {
  grid-column: 2;
  grid-row: 3;
  justify-content: flex-start;
  align-self: start;
  min-height: 0;
  min-width: 0;
  padding-left: 0;
  flex: 0 0 auto;
  justify-self: start;
  margin-top: 1px;
  overflow: visible;
}
.rail-list .char-card .md span {
  overflow: visible;
  text-overflow: clip;
  white-space: nowrap;
}
.rail-list .char-card .portrait-frame {
  width: var(--rail-avatar-size);
  height: var(--rail-avatar-size);
  border-width: 1px;
  box-shadow: none;
}
/* the rail's own type scale: name strongest, role secondary, mood tertiary
   and clearly the lightest of the three. Name nudged up from .9em/700 (too
   small and weak against the row's own portrait) to 1.02em/800 - roughly
   1-2px larger and a visibly stronger weight, final UI precision pass. */
.rail-list .char-card .nm { font-size: 1.02em; font-weight: 800; line-height: 1.2; }
.rail-list .char-card .rl { font-size: .72em; line-height: 1.2; }
.rail-list .char-card .md { font-size: .68em; font-weight: 700; line-height: 1.2; }
.rail-list .char-card:hover .portrait-frame { border-color: var(--line-gold); box-shadow: none; }
.rail-list .char-card .online-dot { width: 9px; height: 9px; right: 0; bottom: 1px; box-shadow: none; }
/* Unread on an unbounded row: a warm fill plus the portrait ring, rather
   than the pulsing halo the old bordered tile needed to be seen against the
   wallpaper. The row is on a calm surface now, so the quieter state is the
   more legible one - and nine rows no longer breathe at once. */
.rail-list .char-card.has-unread:not(.selected) {
  background: rgba(247, 231, 190, .38);
  box-shadow: none;
  animation: none;
}
/* a rail row answers the cursor with a fill, not a lift: a vertical list
   should never look like its rows are coming loose from the column */
.rail-list .char-card:hover,
.rail-list .rail-group-card:hover {
  transform: none;
  background: rgba(255, 253, 244, .62);
  border-color: transparent;
}
.rail-list .char-card:active,
.rail-list .rail-group-card:active { transform: none; background: rgba(255, 253, 244, .8); }
/* ---------- the selected conversation ---------- */
.rail-list .char-card.selected,
.rail-list .rail-group-card.selected {
  transform: none;
  background: rgba(241, 227, 196, .72);
  border: 0;
  border-radius: 8px;
  outline: 1px solid var(--line-gold-soft);
  outline-offset: -1px;
  box-shadow: none;
}
.rail-list .char-card.selected .nm,
.rail-list .rail-group-card.selected .nm { color: var(--green-deep); font-weight: 850; }
.rail-list .char-card.scene-locked:hover,
.rail-list .char-card.scene-locked:active { transform: none; background: transparent; }

/* ---------- group rooms at the top of the rail ----------
   A group is a room, not a person, so it is deliberately not a .char-card:
   a face cluster instead of one portrait, a member count instead of a role,
   no mood and no presence dot. Everything that answers "where am I / what is
   waiting" - selection, unread, meetup locking - is shared with the cast
   cards below, so the column reads as one list. */
.rail-head {
  flex: 0 0 auto;
  min-height: 20px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 0 2px 4px;
}
.rail-head h2 {
  margin: 0;
  color: var(--green-deep);
}
.rail-unread-summary {
  flex: 0 0 auto;
  font-size: .72em;
  font-weight: 700;
  color: var(--ink-dim);
  white-space: nowrap;
}
.rail-section {
  flex: 0 0 auto;
  min-height: 20px;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  padding: 2px;
}
.rail-section-label {
  /* same small-label recipe as .panel h2 / .match-eyebrow */
  font-size: var(--fs-label);
  font-weight: 800;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: rgba(27, 45, 32, .58);
}
.rail-section-staff {
  min-height: 24px;
  margin-top: 2px;
  padding-top: 6px;
  border-top: 1px solid var(--rail-line, rgba(84, 105, 88, .24));
}
.rail-list .rail-group-card {
  flex: 1 1 var(--rail-card-height);
  max-height: 64px;
  width: 100%;
  min-height: var(--rail-card-height);
  box-sizing: border-box;
  display: grid;
  /* An explicit avatar-width first column, not `auto`: with `auto` the group
     mark sized its own column, so the group title started 4px left of every
     contact name below it and the rail had two text columns instead of one. */
  grid-template-columns: var(--rail-avatar-size) minmax(0, 1fr) auto;
  align-items: center;
  column-gap: 6px;
  padding: var(--rail-row-inset-y) var(--rail-row-inset-x);
  text-align: left;
  /* same plain row the cast cards are - the rail's own surface is the only
     readability treatment, and fill means hover or selection, nothing else */
  background: transparent;
  border: 0;
  border-radius: 8px;
  box-shadow: none;
  transition: background-color .16s ease;
}
.rail-list .rail-group-card.scene-locked {
  opacity: .52;
  cursor: not-allowed;
  filter: saturate(.72);
}
.rail-list .rail-group-card.scene-locked:hover,
.rail-list .rail-group-card.scene-locked:active { transform: none; background: transparent; }
.rail-group-text { display: flex; flex-direction: column; min-width: 0; }
/* Title matches the cast rows' own strengthened scale (1.02em/800), so
   Club Chat reads with the same prominence as a colleague's name rather
   than a quieter room label. */
.rail-group-card .nm {
  font-weight: 800;
  font-size: 1.02em;
  line-height: 1.2;
  color: var(--green-deep);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rail-group-card .rl { font-size: .74em; line-height: 1.2; color: var(--ink-dim); }
/* One quiet group mark, sized to the cast rows' own avatar so the whole
   column shares a leading edge. The member faces belong to the conversation
   header, which shows the fuller cluster; a second stack of the same four
   portraits here was duplication rather than identity. */
.rail-group-mark {
  display: grid;
  place-items: center;
  /* exactly the cast portrait's diameter, so the whole rail - group rows,
     the add-group placeholder and every contact - shares one leading edge */
  width: var(--rail-avatar-size, 40px);
  height: var(--rail-avatar-size, 40px);
  overflow: hidden;
  border-radius: 50%;
  border: 0;
  background: rgba(246, 239, 219, .85);
  color: var(--green-deep);
}
.rail-group-mark img {
  width: 68%;
  height: 68%;
  object-fit: contain;
}
.rail-group-mark-icon { width: 55%; height: 55%; }
/* unread on an unbounded row is a warm text/badge state, not a glow around
   a card edge that no longer exists */
.rail-list .rail-group-card.has-unread:not(.selected) { background: rgba(247, 231, 190, .38); }
.rail-list .rail-group-card .unread-badge.on-chip-inline { justify-self: end; }
/* Group creation is a secondary action, not another conversation. It keeps
   the same text edge as the rows above without borrowing their full height or
   competing with the selected room. */
.rail-add-group-row {
  flex: 0 0 34px;
  width: 100%;
  min-height: 34px;
  box-sizing: border-box;
  display: grid;
  grid-template-columns: 28px minmax(0, 1fr);
  align-items: center;
  column-gap: 6px;
  padding: 3px var(--rail-row-inset-x);
  border: 1px solid transparent;
  border-radius: 8px;
  background: transparent;
  color: var(--ink-dim);
  font-size: .82em;
  font-weight: 650;
  text-align: left;
  transition: background-color .16s ease, color .16s ease;
}
.rail-add-group-row .rail-add-text {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* a dashed disc where the group's crest will be - it reads as an empty slot
   rather than as another room that already exists */
.rail-add-mark {
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: 1px dashed rgba(70, 101, 76, .38);
  color: rgba(70, 101, 76, .62);
  transition: border-color .16s ease, color .16s ease;
}
/* explicit px, not a percentage: a percentage HEIGHT on an SVG inside a grid
   cell resolves against the replaced element's 150px default, not the disc,
   so the glyph rendered 63px tall and spilled out of the bottom of the row */
.rail-add-mark-icon {
  width: 12px;
  height: 12px;
  vertical-align: 0;
}
.rail-add-group-row:hover { background: rgba(253, 248, 235, .44); border-color: var(--rail-line, rgba(84, 105, 88, .24)); color: var(--green-deep); }
.rail-add-group-row:hover .rail-add-mark { border-color: var(--line-gold); color: var(--green-deep); }
.rail-add-group-row:focus-visible { outline: 2px solid var(--gold); outline-offset: -2px; }
/* Keyboard focus was reachable on the rail but invisible on the rows
   themselves - only the add-group row had a ring. */
.rail-list .char-card:focus-visible,
.rail-list .rail-group-card:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: -2px;
}
.rail-add-group-row .rail-section-add-icon { width: 12px; height: 12px; }

/* ---------- activity-card base (Meetup prompt row only) ----------
   The homepage activity bar has been retired - the Messenger's position and
   size must never change with what's happening at the club. This shape now
   serves only the Meetup prompt row inside the Messenger shell (see
   #meetup-prompt-row / .is-meetup-prompt below). */
.activity-card {
  min-width: 0;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  column-gap: 10px;
  align-items: center;
  padding: 9px 12px;
  background: var(--panel-contact);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-card);
  box-shadow: var(--shadow-card);
  text-align: left;
  transition: transform .16s ease, border-color .16s ease, box-shadow .16s ease;
}
.activity-card:hover { transform: translateY(-3px); border-color: var(--line-gold); box-shadow: 0 8px 18px rgba(40, 60, 35, .26); }
.activity-card:active { transform: translateY(-1px) scale(.985); }
.activity-eyebrow {
  grid-column: 2;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: .68em;
  font-weight: 800;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--label-gold);
}
.activity-text { grid-column: 2; min-width: 0; }
.activity-title {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 700;
  font-size: .95em;
  line-height: 1.25;
  color: var(--green-deep);
}
.activity-detail {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  margin-top: 1px;
  font-size: .78em;
  line-height: 1.25;
  color: var(--ink-faint);
}
/* Meetup quick-reply prompts now live inside the Messenger shell itself,
   above the thread (see #meetup-prompt-row below) rather than in the
   homepage activity bar - entering a Meetup no longer changes the activity
   bar's height. The cards keep the familiar activity-card proportions but
   wear a quieter green Meetup tint instead of the homepage's gold/cream
   treatment, so the strip reads as part of the live scene, not a fourth
   navigation row. */
.activity-card.is-meetup-prompt {
  color: var(--green-deep);
  background: rgba(240, 246, 236, .55);
  border: 1px solid rgba(58, 84, 54, .14);
  box-shadow: none;
  grid-template-columns: minmax(0, 1fr);
  /* quieter footprint than the homepage activity-card it's based on - these
     are contextual chips feeding the composer, not button-height UI */
  padding: 11px 13px;
}
.activity-card.is-meetup-prompt:hover {
  /* a small lift + soft shadow - much quieter than the homepage activity-card's
     version above, but enough that these read as pressable cards rather than
     flat navigation tabs */
  transform: translateY(-1px);
  background: rgba(240, 246, 236, .88);
  border-color: var(--green-mid);
  box-shadow: 0 4px 10px rgba(40, 60, 35, .14);
}
.activity-card.is-meetup-prompt:active { transform: none; }
.activity-card.is-meetup-prompt .activity-title {
  color: var(--green-deep);
  font-size: .89em;
  font-weight: 750;
  line-height: 1.2;
}
/* subtitle recedes further than before (was --ink-dim, same family as the
   title) so the two lines separate by weight/tone, not just size */
.activity-card.is-meetup-prompt .activity-detail {
  color: var(--ink-faint);
  font-size: .72em;
  font-weight: 500;
  line-height: 1.2;
}
.activity-card.is-meetup-prompt .activity-icon,
.activity-card.is-meetup-prompt .activity-eyebrow { display: none; }
.activity-card.is-meetup-prompt .activity-text { grid-column: 1 / -1; }

/* ---------- Meetup quick actions (inside the composer footer) ---------- */
.meetup-prompt-row {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  align-items: stretch;
  gap: 6px;
  padding: 8px max(22px, calc((100% - var(--thread-measure)) / 2 - 12px)) 0;
  border-top: 1px solid rgba(58, 84, 54, .15);
}
.meetup-prompt-row[hidden] { display: none; }
.meetup-quick-action {
  min-width: 0;
  min-height: 32px;
  padding: 6px 9px;
  border: 1px solid rgba(58, 84, 54, .17);
  border-radius: var(--r-pill);
  background: rgba(255, 253, 244, .82);
  color: var(--green-deep);
  font-size: .72em;
  font-weight: 750;
  line-height: 1.15;
}
.meetup-quick-action:hover {
  border-color: var(--line-gold);
  background: #FFFDF4;
}
.meetup-quick-action.leave {
  border-color: transparent;
  background: transparent;
  color: var(--ink-faint);
  font-weight: 650;
}
.meetup-quick-action.leave:hover { color: var(--gold-deep); }

/* ---------- views / modal ---------- */
.overlay {
  position: fixed;
  inset: 0;
  display: grid;
  place-items: center;
  padding: 10px;
  /* deeper dim plus a light blur: the dashboard recedes and the open
     screen owns the moment */
  background: rgba(24, 42, 28, .42);
  backdrop-filter: blur(5px);
  -webkit-backdrop-filter: blur(5px);
  z-index: 40;
}
.modal {
  position: relative;
  width: min(580px, 92vw);
  max-height: 82vh;
  overflow-y: auto;
  background: rgba(253, 248, 235, .97);
  border: 1px solid var(--line-gold);
  border-radius: var(--r-panel);
  box-shadow: 0 0 0 1px rgba(253, 248, 235, .4), 0 18px 50px rgba(25, 42, 24, .4);
  padding: clamp(18px, 2.2vw, 28px);
}
.modal-close {
  position: absolute;
  top: 12px;
  right: 14px;
  z-index: 2;
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  border: 1px solid var(--line-soft);
  border-radius: 50%;
  background: rgba(253, 248, 235, .92);
  color: var(--green-deep);
}
/* drawn as an SVG rather than a "x" text glyph: every font renders that
   glyph with its own (inconsistent, often off-centre) internal metrics, so
   place-items:center was centring an invisible line-box, not the visible
   ink. An SVG has no such metrics - centring the box now reliably centres
   what you see. */
.modal-close svg { display: block; pointer-events: none; }
.modal-close:hover { background: var(--cream-2); border-color: var(--gold); }
.modal-close:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--gold); }
.modal h3 {
  margin: 0 0 2px;
  padding-right: 42px;
  font-family: var(--serif);
  font-size: 1.55em;
  color: var(--green-deep);
}
/* every screen opens with the same header band: serif title, quiet
   descriptor, gold hairline underneath */
.modal .sub {
  color: var(--ink-dim);
  margin-bottom: 14px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--line-gold-soft);
}
.modal .body { font-size: 1em; }
.modal .body p { margin-bottom: 10px; line-height: 1.5; }
/* ---------- match preview: scout notes ---------- */
/* the descriptor line is replaced by the opponent header block below */
.modal .sub:empty { display: none; }
.preview-modal .sub { display: none; }
.preview-modal .preview-opp {
  margin: 2px 0 16px;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--line-gold-soft);
}
.preview-modal .preview-opp-name {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  font-family: var(--serif);
  font-weight: 700;
  font-size: 1.35em;
  line-height: 1.15;
  color: var(--green-deep);
}
.preview-modal .preview-opp-name .fixture-venue { font-family: var(--sans); font-size: .5em; }
.preview-modal .preview-ko { margin-top: 4px; color: var(--ink-dim); font-size: .92em; }
/* each derived line reads like a note pinned to the briefing */
.preview-modal .scout-note,
.sidecar-preview .scout-note {
  margin: 0 0 8px;
  padding: 10px 14px;
  background: #FFFDF4;
  border: 1px solid var(--line-soft);
  border-left: 3px solid var(--line-gold);
  border-radius: 10px;
  line-height: 1.5;
  box-shadow: 0 1px 3px rgba(40, 60, 35, .07);
}
.preview-modal .scout-record,
.sidecar-preview .scout-record {
  margin: 12px 2px 0;
  color: var(--ink-dim);
  font-size: .92em;
  font-style: italic;
}
/* ---------- match preview: compact workspace briefing ---------- */
.sidecar-preview { display: grid; gap: 14px; }
.sidecar-preview .preview-section {
  padding-bottom: 13px;
  border-bottom: 1px solid rgba(58, 84, 54, .14);
}
.sidecar-preview .preview-section:last-child { padding-bottom: 0; border-bottom: 0; }
.sidecar-preview h4 {
  margin: 0 0 6px;
  color: var(--label-gold);
  font-size: .66em;
  font-weight: 800;
  letter-spacing: .1em;
  text-transform: uppercase;
}
.sidecar-preview .preview-flavour {
  margin: 0 0 8px;
  color: var(--green-deep);
  font-weight: 650;
  line-height: 1.35;
}
.sidecar-preview .preview-detail {
  margin: 4px 0 0;
  color: var(--ink-dim);
  font-size: .82em;
  line-height: 1.4;
}
.sidecar-preview .preview-detail strong {
  display: inline-block;
  min-width: 96px;
  margin-right: 8px;
  color: var(--green-deep);
  font-size: .9em;
  letter-spacing: .025em;
  text-transform: uppercase;
}
.sidecar-preview .preview-tactic {
  margin: 0 0 5px;
  color: var(--green-deep);
  font-weight: 800;
}
.sidecar-preview .preview-tactic span { color: var(--gold-deep); }
.sidecar-preview .preview-readiness { color: var(--ink-faint); }
.sidecar-preview .preview-unavailable { color: var(--bad); font-weight: 700; }
.preview-workspace-actions { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
.sidecar-footer .sidecar-workspace-action {
  justify-content: center;
  min-height: 36px;
  padding-inline: 10px;
  border-color: rgba(217, 169, 60, .55);
  background: linear-gradient(180deg, var(--green-mid), var(--green-deep));
  color: #FBF7EA;
}

.settings-top-actions { display: flex; justify-content: flex-start; margin-bottom: 12px; }
.settings-top-actions .btn-quiet { padding: 6px 14px; font-size: .85em; }
.modal-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 18px; padding-top: 12px; border-top: 1px solid rgba(58, 84, 54, .12); }

.modal.club-life-modal {
  width: min(580px, 92vw);
  text-align: center;
  box-shadow: inset 0 3px 0 rgba(201, 151, 58, .5), 0 18px 50px rgba(25, 42, 24, .4);
}
.club-life-modal h3 { padding-left: 42px; }
.club-life-modal .sub { color: var(--ink); font-weight: 750; }
.club-life-people {
  color: var(--gold-deep);
  font-size: .76em;
  font-weight: 800;
  letter-spacing: .08em;
  text-transform: uppercase;
}
.club-life-text { margin: 10px auto 0; max-width: 52ch; color: var(--ink-dim); line-height: 1.5; }
.club-life-modal .modal-actions { justify-content: center; }

/* ---------- character profile pages ---------- */
.modal.profile-modal { width: min(860px, 94vw); background: rgba(253, 248, 235, .98); }
.p-layout { display: grid; grid-template-columns: minmax(0, 236px) minmax(0, 1fr); gap: 24px; align-items: start; }
.p-details, .p-section { min-width: 0; }
.p-hero { text-align: center; }
.p-hero img {
  width: 100%;
  max-width: 236px;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: 18px;
  border: 2px solid var(--line-gold);
  box-shadow: var(--shadow-panel);
  background: var(--panel-light);
}
/* Human profile portraits use their full-size source and never inherit the
   circular avatar frame's zoom, translation or fixed thumbnail dimensions. */
.p-hero .human-profile-image {
  width: 100%;
  max-width: 236px;
  display: block;
  margin-inline: auto;
  overflow: hidden;
  border-radius: 18px;
  border: 2px solid var(--line-gold);
  box-shadow: var(--shadow-panel);
  background: var(--panel-light);
}
.p-hero .human-profile-image img {
  display: block;
  width: 100%;
  height: auto;
  max-width: none;
  aspect-ratio: auto;
  object-fit: contain;
  object-position: 50% 50%;
  transform: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}
.human-profile-fallback { display: none; color: var(--ink-faint); font-size: .85em; }
.human-profile-image.is-missing {
  aspect-ratio: 1 / 1;
  display: grid;
  place-items: center;
  padding: 16px;
}
.human-profile-image.is-missing .human-profile-fallback { display: block; }
.p-hero .nm { font-family: var(--serif); font-weight: 700; font-size: 1.45em; color: var(--green-deep); margin-top: 10px; }
.p-hero .rl { color: var(--ink-dim); margin-top: 1px; }
.p-hero .md { display: flex; align-items: center; justify-content: center; gap: 4px; margin-top: 8px; font-weight: 600; }
.p-hero .md .status-icon { width: 13px; height: 13px; }
/* Dog profiles keep their own squad-role chip ("Starting XI" / "Rotation") -
   the human category badge (Staff/Board/Welfare) has been removed, but this
   is a different chip with its own meaning and stays as it was. */
.p-hero-dog .group-chip {
  display: inline-block;
  margin-top: 8px;
  padding: 3px 12px;
  border: 1px solid var(--line-gold-soft);
  border-radius: 999px;
  background: var(--panel-light);
  font-size: .8em;
  letter-spacing: .04em;
  color: var(--gold-deep);
  font-weight: 700;
}
.p-section { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--line-soft); }
.p-section:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
/* Finding 9: one quiet derived sentence under the relationship bar */
.p-status-line { margin: 8px 0 0; font-style: italic; font-size: .92rem; color: var(--ink-dim); }
.p-status-reason { margin: 4px 0 0; font-size: .86rem; line-height: 1.4; color: var(--ink-dim); }
.p-section .group-label { margin-bottom: 6px; }
.p-section p { margin: 0 0 6px; overflow-wrap: anywhere; }
.p-hero-facts {
  display: grid;
  grid-template-columns: 54px minmax(0, 1fr);
  width: 100%;
  max-width: 254px;
  margin: 12px auto 0;
  overflow: hidden;
  border: 1px solid var(--line-gold-soft);
  border-radius: 14px;
  background: linear-gradient(145deg, rgba(255, 253, 244, .98), rgba(240, 225, 188, .64));
  box-shadow: 0 5px 16px rgba(64, 70, 44, .1), inset 0 1px rgba(255, 255, 255, .72);
  text-align: left;
}
.p-hero-fact {
  min-width: 0;
  padding: 10px 12px;
}
.p-hero-fact + .p-hero-fact { border-left: 1px solid var(--line-gold-soft); }
.p-hero-fact-label {
  display: block;
  margin-bottom: 2px;
  color: var(--gold-deep);
  font-size: .62em;
  font-weight: 800;
  letter-spacing: .15em;
  line-height: 1.2;
  text-transform: uppercase;
}
.p-hero-fact strong {
  display: block;
  color: var(--green-deep);
  font-family: var(--serif);
  font-size: 1.2em;
  line-height: 1.22;
}
.p-hero-fact-age strong {
  font-size: 1.55em;
  line-height: 1;
}
/* Born now reads at the same size as any other hero-fact value (Age is the
   one exception, sized up as the headline number) - the card widened a
   touch and the Age column narrowed slightly so the longest authored date
   ("17 December 1982") still fits on one line at this larger size. */
.p-hero-fact-born strong {
  font-size: 1.2em;
  white-space: nowrap;
}
.chip {
  display: inline-block;
  padding: 3px 11px;
  margin: 2px 5px 3px 0;
  border: 1px solid var(--line-gold-soft);
  border-radius: 999px;
  background: var(--panel-light);
  font-size: .84em;
  color: var(--green-deep);
}
/* Squad Evolution: a plain, non-numeric status chip in the Pack list.
   Settled dogs show no chip at all - see dogSquadStatusLabel() in app.js. */
.dog-status-chip { font-size: .76em; padding: 2px 9px; vertical-align: middle; }
.dog-status-chip.status-restless { border-color: var(--gold-deep); color: var(--gold-deep); }
.dog-status-chip.status-considering,
.dog-status-chip.status-leaving { border-color: var(--bad); color: var(--bad); }
.dog-status-chip.status-final { border-color: var(--green-mid); color: var(--green-deep); }
.p-status-line.status-restless { color: var(--gold-deep); }
.p-status-line.status-considering,
.p-status-line.status-leaving { color: var(--bad); }
.p-status-line.status-final { color: var(--green-deep); }

/* Scouting and staged recruitment. */
.scout-invite-btn {
  flex: 0 0 auto;
  align-self: center;
  margin-right: 8px;
  padding: 6px 14px;
  font-size: .78em;
  font-weight: 800;
  border-radius: var(--r-card);
  background: linear-gradient(180deg, var(--green-mid), var(--green-deep));
  color: #FBF7EA;
  cursor: pointer;
}
.scout-invite-btn:disabled { opacity: .52; cursor: default; filter: none; }
.scout-invite-btn:hover { filter: brightness(1.08); }
.scout-invite-btn:active { transform: scale(.97); }
.scout-withdraw-btn {
  padding: 6px 11px;
  border: 1px solid var(--line-gold);
  border-radius: var(--r-card);
  background: var(--panel-light);
  color: var(--ink-dim);
  font-size: .76em;
  font-weight: 750;
  cursor: pointer;
}
.scout-row { align-items: center; }
.scout-row .dog-row-info { align-items: center; }
.scout-row-summary { flex: 1 1 300px; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.scout-row-summary .dog-row-main { flex: 0 1 auto; }
.scout-incentive-line { display: flex; flex-wrap: wrap; align-items: center; gap: 7px; color: var(--ink-dim); font-size: .78em; }
.scout-incentive-line strong { color: var(--green-deep); }
.scout-requirement { font-size: .92em; }
.is-met { color: var(--green-mid); }
.is-unmet { color: var(--bad); }
.scout-row-actions { flex: 0 0 118px; display: flex; justify-content: flex-end; padding-right: 8px; }
.scout-action-wrap { display: flex; flex-direction: column; align-items: stretch; gap: 4px; }
.scout-action-note { width: 112px; color: var(--ink-faint); font-size: .64em; line-height: 1.2; text-align: center; }
.scout-refresh-text { color: var(--ink-dim); font-size: .86em; font-weight: 700; }
.scout-incentive-detail { display: grid; gap: 4px; margin-top: 8px; color: var(--ink-dim); font-size: .9em; }
.scout-incentive-detail strong { color: var(--green-deep); }
.confirm-summary { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
.confirm-summary > div { display: flex; flex-direction: column; gap: 3px; padding: 10px 12px; border: 1px solid var(--line-soft); border-radius: var(--r-card); background: var(--panel-light); }
.confirm-summary span { color: var(--ink-dim); font-size: .78em; }
.confirm-summary strong { color: var(--green-deep); }
.confirm-summary small { color: var(--ink-faint); line-height: 1.4; }
.confirm-summary .confirm-wide { grid-column: 1 / -1; }
.scout-interest { font-size: .76em; padding: 2px 9px; vertical-align: middle; }
.scout-interest-strong { border-color: var(--green-mid); color: var(--green-deep); }
.scout-interest-good { border-color: var(--green-mid); color: var(--green-deep); }
.scout-interest-mid { border-color: var(--gold-deep); color: var(--gold-deep); }
.scout-interest-low,
.scout-interest-none { border-color: var(--bad); color: var(--bad); }
/* Club Care & Facilities (Phase 3): four plain cards reusing the existing
   .p-section/.group-label/.chip/.btn-gold/.btn-quiet shell - deliberately
   no price, cost or shop-style presentation anywhere. */
.care-card { border: 1px solid var(--line-gold-soft); border-radius: var(--r-card); padding: 12px 14px; margin-bottom: 12px; background: var(--panel-light); }
.care-card:last-child { margin-bottom: 0; }
.care-level-row { display: flex; align-items: center; gap: 8px; margin: 2px 0 8px; }
.care-owner { font-size: .82em; color: var(--ink-dim); }
.care-level-chip { font-weight: 700; }
.care-level-chip.care-level-poor { border-color: var(--bad); color: var(--bad); }
.care-level-chip.care-level-basic { border-color: var(--gold-deep); color: var(--gold-deep); }
.care-level-chip.care-level-good { border-color: var(--green-mid); color: var(--green-deep); }
.care-level-chip.care-level-excellent { border-color: var(--green-deep); color: var(--green-deep); background: rgba(55,102,63,.10); }
.care-explain { margin: 0 0 6px; color: var(--ink-dim); font-size: .92em; }
.care-effects { margin: 0 0 6px; font-size: .88em; color: var(--ink-dim); font-style: italic; }
.care-recruitment { margin: 8px 0; padding: 8px 10px; border-radius: 8px; background: var(--gold-tint); color: var(--ink-dim); font-size: .86em; }
.care-recruitment strong { color: var(--green-deep); }
.care-recruitment ul { margin: 5px 0 0 18px; padding: 0; }
.care-recruitment p { margin: 4px 0 0; }
.care-progress { margin: 8px 0; }
.care-progress-label { font-size: .8em; color: var(--gold-deep); font-weight: 700; margin-bottom: 4px; }
.care-progress-bar { height: 8px; border-radius: 999px; background: var(--line-gold-soft); overflow: hidden; }
.care-progress-bar > span { display: block; height: 100%; background: linear-gradient(90deg, var(--gold-bright), var(--green-mid)); }
.care-actions { margin-top: 8px; }
.care-actions .btn-gold,
.care-actions .btn-quiet { padding: 8px 16px; font-size: .86em; }
.care-actions .btn-quiet:disabled { opacity: .6; cursor: default; }
.care-allowance { margin-bottom: 10px; padding: 10px 12px; border: 1px solid var(--line-gold); border-radius: var(--r-card); background: var(--gold-tint); color: var(--green-deep); }
.care-active-summary { display: flex; flex-direction: column; gap: 3px; margin-bottom: 12px; padding: 10px 12px; border-radius: var(--r-card); background: rgba(55,102,63,.1); color: var(--green-deep); }
.care-active-summary span { color: var(--ink-dim); font-size: .84em; }
.care-disabled-reason { margin-top: 5px; color: var(--bad); font-size: .76em; line-height: 1.3; }
.care-highest { color: var(--green-deep); font-size: .86em; font-weight: 750; }
.modal.care-modal { width: min(900px, 94vw); max-height: 90vh; }
.care-modal .body { overflow-y: auto; }

.care-modal .body { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
.care-modal .care-allowance,
.care-modal .care-active-summary { grid-column: 1 / -1; margin-bottom: 0; }
.care-modal .care-card { margin-bottom: 0; }

/* ---------- dog roster: position accents (no portraits, ever) ----------
   One accent colour per position - GK gold, DEF blue, MID green, ATT red -
   each paired with a light tint of the same colour for the badge fill, so
   the badge itself is never plain cream. Blue is the one net-new colour
   (see --blue-deep/--blue-tint in theme.css); the rest are already part of
   the palette. Never an image, initial or icon. */
.pos-gk  { --dog-accent: var(--gold-deep); --dog-accent-tint: var(--gold-tint); --dog-accent-tint-solid: var(--gold-tint-solid); }
.pos-def { --dog-accent: var(--blue-deep); --dog-accent-tint: var(--blue-tint); --dog-accent-tint-solid: var(--blue-tint-solid); }
.pos-mid { --dog-accent: var(--green-deep); --dog-accent-tint: var(--green-tint); --dog-accent-tint-solid: var(--green-tint-solid); }
.pos-att { --dog-accent: var(--red); --dog-accent-tint: var(--red-tint); --dog-accent-tint-solid: var(--red-tint-solid); }

/* Small position chip used anywhere a dog would otherwise need an avatar
   (Messenger list, conversation header). */
.dog-role-chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--dog-accent-tint, var(--cream));
  border: 2px solid var(--dog-accent, var(--green-deep));
  color: var(--dog-accent, var(--green-deep));
  font-size: .68em;
  font-weight: 800;
  letter-spacing: .02em;
  flex: 0 0 auto;
}

/* Profile hero: a dossier header instead of a portrait. */
.p-hero-dog { padding-top: 4px; }
.dog-position-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 64px;
  height: 64px;
  margin: 0 auto;
  border-radius: 50%;
  background: var(--dog-accent-tint, var(--cream));
  border: 3px solid var(--dog-accent, var(--green-deep));
  color: var(--dog-accent, var(--green-deep));
  font-family: var(--serif);
  font-size: 1.05em;
  font-weight: 800;
}
.dog-hero-stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  width: 100%;
  max-width: 236px;
  margin: 12px auto 0;
  overflow: hidden;
  border: 1px solid var(--line-gold-soft);
  border-radius: 14px;
  background: linear-gradient(145deg, rgba(255, 253, 244, .98), rgba(240, 225, 188, .64));
  box-shadow: 0 5px 16px rgba(64, 70, 44, .1), inset 0 1px rgba(255, 255, 255, .72);
}
.dog-hero-stat { min-width: 0; padding: 8px 6px; text-align: center; }
.dog-hero-stat + .dog-hero-stat { border-left: 1px solid var(--line-gold-soft); }
.dog-hero-stat span {
  display: block;
  margin-bottom: 2px;
  color: var(--gold-deep);
  font-size: .6em;
  font-weight: 800;
  letter-spacing: .12em;
  text-transform: uppercase;
}
.dog-hero-stat strong { display: block; color: var(--green-deep); font-family: var(--serif); font-size: .92em; }
.p-hero-dog .stage-line { margin-top: 12px; }

/* Compact attribute indicators (pace / strength / intelligence / discipline
   / teamwork) - relabel the existing five-a-side matchAttributes, nothing new. */
.dog-attrs { display: flex; flex-direction: column; gap: 7px; }
.dog-attr { display: grid; grid-template-columns: 84px minmax(0, 1fr); align-items: center; gap: 10px; }
.dog-attr-label { font-size: .84em; color: var(--ink-dim); }
.dog-attr-bar {
  position: relative;
  height: 7px;
  border-radius: 999px;
  background: var(--gold-tint);
  overflow: hidden;
}
.dog-attr-bar span { position: absolute; inset: 0 auto 0 0; display: block; height: 100%; border-radius: 999px; background: var(--green-mid); }

/* ---------- Pack squad list: a thin single-line row per dog ----------
   Always one column, always one line per dog - a genuine list, not a grid
   of large cards. Everything sits on the same flex row; only the breed/
   role meta text is allowed to give up space first (min-width: 0 + ellipsis)
   so eleven dogs fit with minimal wasted space and no left/right blocks. */
.dog-list { display: flex; flex-direction: column; gap: 4px; }
/* fixed shell + scrollable inner list, same pattern as Fixtures/Schedule/
   Club Log; group headers stick to the top as the list scrolls */
.modal.pack-modal {
  width: min(760px, 96vw);
  max-height: 90vh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
.pack-modal .body { min-height: 0; display: flex; flex-direction: column; }
.pack-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 10px; flex: 0 0 auto; }
.pack-count { font-size: .86em; color: var(--ink-dim); font-weight: 600; }
.pack-autopick { flex: 0 0 auto; padding: 8px 16px; font-size: .88em; }
.dog-list-scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; }
.dog-list-scroll .group-label { position: sticky; top: 0; z-index: 1; padding: 6px 4px; background: rgba(253, 248, 235, .97); }
.sidecar-pack { display: flex; flex-direction: column; min-height: 0; }
.sidecar-pack .pack-toolbar {
  position: sticky;
  top: -14px;
  z-index: 2;
  margin-inline: -2px;
  padding: 4px 2px 10px;
  background: var(--panel-dash);
}
.sidecar-pack .dog-list-scroll { overflow: visible; }

/* ---------- Pack workspace: compact character cards ----------
   The sidecar keeps its existing width. Two deliberately dense columns make
   the nine-character roster scannable without turning it back into a table. */
.sidecar-pack .pack-toolbar {
  margin-bottom: 2px;
  padding-bottom: 9px;
}
.sidecar-pack .pack-count {
  color: var(--ink-dim);
  font-size: .76em;
  font-weight: 600;
}
.sidecar-pack .pack-count strong {
  color: var(--green-deep);
  font-weight: 850;
}
.sidecar-pack .pack-autopick {
  min-height: 26px;
  padding: 4px 8px;
  border: 1px solid var(--line-gold-soft);
  border-radius: 7px;
  background: rgba(255, 253, 244, .72);
  color: var(--gold-deep);
  font-size: .68em;
  font-weight: 800;
  letter-spacing: .02em;
}
.sidecar-pack .pack-autopick:hover {
  border-color: var(--gold);
  background: #FFFDF4;
}
.pack-card-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
}
.pack-dog-card {
  position: relative;
  min-width: 0;
  min-height: 126px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 10px;
  overflow: hidden;
  border: 1px solid rgba(58, 84, 54, .18);
  border-radius: 11px;
  background: rgba(255, 253, 244, .82);
  color: var(--ink);
  text-align: left;
  box-shadow: 0 2px 7px rgba(35, 52, 36, .05);
  transition: border-color .14s ease, background .14s ease, box-shadow .14s ease, transform .14s ease;
}
.pack-dog-card:hover {
  border-color: var(--dog-accent, var(--green-mid));
  background: #FFFDF4;
  transform: translateY(-1px);
}
.pack-dog-card:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
}
.pack-dog-card.is-starting {
  border-color: rgba(184, 134, 11, .48);
  background: linear-gradient(145deg, #FFF4D7, #F3E1B3);
  box-shadow: inset 0 0 0 1px rgba(184, 134, 11, .12), 0 3px 9px rgba(92, 69, 21, .09);
}
.pack-card-top {
  min-width: 0;
  display: grid;
  grid-template-columns: 38px minmax(0, 1fr);
  align-items: center;
  gap: 8px;
}
.pack-card-portrait {
  grid-row: 1 / span 2;
  width: 38px;
  height: 38px;
  display: grid;
  place-items: center;
  border: 1px dashed color-mix(in srgb, var(--dog-accent, var(--green-mid)) 55%, transparent);
  border-radius: 10px;
  background: var(--dog-accent-tint, var(--cream));
  color: var(--dog-accent, var(--green-deep));
  font-family: var(--serif);
  font-size: 1.02em;
  font-weight: 800;
}
.pack-card-identity {
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 3px;
}
.pack-card-name {
  max-width: 100%;
  overflow: hidden;
  color: var(--green-deep);
  font-size: .86em;
  font-weight: 800;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.pack-card-position {
  padding: 2px 6px;
  border-radius: 999px;
  background: var(--dog-accent-tint, var(--cream));
  color: var(--dog-accent, var(--green-deep));
  font-size: .55em;
  font-weight: 850;
  letter-spacing: .055em;
}
.pack-card-starting {
  position: absolute;
  top: 7px;
  right: 7px;
  padding: 2px 5px;
  border-radius: 5px;
  background: var(--green-deep);
  color: #FFF8E8;
  font-size: .48em;
  font-weight: 850;
  letter-spacing: .07em;
  text-transform: uppercase;
}
.pack-dog-card.is-starting .pack-card-name { padding-right: 35px; }
.pack-card-metrics {
  min-width: 0;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 6px;
}
.pack-card-rating,
.pack-card-condition {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.pack-card-rating span,
.pack-card-condition span {
  color: var(--ink-faint);
  font-size: .48em;
  font-weight: 800;
  letter-spacing: .06em;
  text-transform: uppercase;
}
.pack-card-rating strong {
  color: var(--green-deep);
  font-size: 1.02em;
  line-height: 1;
}
.pack-card-condition {
  align-items: flex-end;
  text-align: right;
}
.pack-card-condition strong {
  overflow: hidden;
  color: var(--ink-dim);
  font-size: .62em;
  font-weight: 750;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.pack-card-trait {
  margin-top: auto;
  overflow: hidden;
  color: var(--ink-dim);
  font-size: .63em;
  line-height: 1.28;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sidecar-pack + * { min-width: 0; }
.sidecar-footer .pack-confirm {
  width: 100%;
  min-height: 40px;
  text-transform: uppercase;
  letter-spacing: .055em;
}
/* the dog's own row also picks up a quiet gold wash while they hold a
   starting spot, echoing the starter-toggle disc's fill colour */
.dog-row.is-starting { background: var(--gold-tint); box-shadow: inset 0 0 0 1px rgba(184, 134, 11, .28); }
.dog-row-rating { font-weight: 800; color: var(--green-deep); }
.dog-row {
  display: flex;
  align-items: stretch;
  gap: 8px;
  width: 100%;
  background: #FFFDF4;
  border: 1px solid var(--line-soft);
  border-left: 3px solid var(--dog-accent, var(--green-deep));
  border-radius: 8px;
  transition: border-color .12s ease, background .12s ease;
}
.dog-row:hover, .dog-row:focus-within { border-color: var(--line-gold); background: rgba(255, 253, 244, .92); }
/* The row's own left/right edges are the div above; the profile button
   inside is a plain, unstyled hit-target so it can sit next to the
   independent starter-toggle button (buttons can't nest). */
.dog-row-info {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 4px 6px 10px;
  background: none;
  border: 0;
  text-align: left;
  cursor: pointer;
}
.dog-row-position {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 27px;
  height: 27px;
  border-radius: 50%;
  background: var(--dog-accent-tint, var(--cream));
  border: 2px solid var(--dog-accent, var(--green-deep));
  color: var(--dog-accent, var(--green-deep));
  font-size: .62em;
  font-weight: 800;
}
.dog-row-main {
  flex: 1 1 260px;
  min-width: 0;
  display: flex;
  align-items: baseline;
  gap: 7px;
}
.dog-row-name { font-weight: 700; color: var(--green-deep); white-space: nowrap; flex: 0 1 auto; overflow: hidden; text-overflow: ellipsis; }
.dog-row-age { font-weight: 500; color: var(--ink-dim); font-size: .78em; flex: 0 0 auto; }
.dog-row-meta { font-size: .8em; color: var(--ink-dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; flex: 1 1 auto; }
.dog-row-stats { flex: 0 0 auto; display: flex; gap: 12px; }
.dog-row-stat { display: flex; align-items: center; gap: 4px; font-size: .78em; color: var(--ink-dim); white-space: nowrap; }
.dog-row-stat-lbl { font-size: .82em; letter-spacing: .03em; text-transform: uppercase; color: var(--gold-deep); font-weight: 700; }
/* Form bar: fill colour is set inline per-dog (red -> gold -> green, see
   dogFormBarColor in app.js) so the bar itself carries the "improving vs
   dropping" read at a glance, not just a word. */
.form-bar {
  position: relative;
  width: 46px;
  height: 6px;
  border-radius: 999px;
  background: rgba(58, 84, 54, .14);
  overflow: hidden;
}
.form-bar span { position: absolute; inset: 0 auto 0 0; display: block; height: 100%; border-radius: 999px; transition: width .3s ease; }
/* Starting-five toggle: an empty ring for a rotation dog (click to bring
   them into the XI, swapping the current same-position starter), a plain
   solid-filled disc for a dog already starting - colour only, no icon
   (their place is only given up by picking someone else in the same
   position, not by un-clicking them). */
.dog-row-starter {
  flex: 0 0 auto;
  align-self: center;
  margin-right: 8px;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--cream);
  border: 2px solid var(--line-soft);
  cursor: pointer;
  transition: border-color .12s ease, background .12s ease, transform .12s ease;
}
.dog-row-starter:hover:not(:disabled) { border-color: var(--dog-accent, var(--green-deep)); transform: scale(1.08); }
.dog-row-starter:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.dog-row-starter.is-starting {
  background: var(--dog-accent, var(--green-deep));
  border-color: var(--dog-accent, var(--green-deep));
  cursor: default;
}
.p-msg { display: flex; align-items: flex-start; min-width: 0; gap: 8px; padding: 5px 0; font-size: .92em; }
.p-msg .who { flex: 0 0 auto; font-weight: 700; color: var(--gold-deep); }
.p-msg .tx { flex: 1 1 auto; min-width: 0; color: var(--ink-dim); line-height: 1.4; overflow-wrap: anywhere; white-space: pre-line; }
.btn-gold {
  padding: 9px 22px;
  background: linear-gradient(180deg, var(--green-mid), var(--green-deep));
  color: #FBF7EA;
  font-weight: 800;
  border-radius: var(--r-card);
}
.btn-gold:hover { filter: brightness(1.08); }
.btn-gold:active { transform: scale(.97); }
.btn-quiet {
  padding: 9px 18px;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-card);
  color: var(--ink-dim);
  background: #FFFDF4;
}
.btn-quiet:hover { color: var(--ink); border-color: var(--gold); }
.btn-quiet:active { transform: scale(.97); }

.day-change {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  padding: 7px 0;
  border-bottom: 1px solid var(--line-soft);
}
.day-change:last-child { border-bottom: 0; }
.day-change .chg { font-weight: 600; white-space: nowrap; }
.day-change .chg.up { color: var(--ok); }
.day-change .chg.down { color: var(--bad); }

/* ---------- profile modal ---------- */
.profile-head { display: flex; align-items: center; gap: 14px; margin-bottom: 14px; }
.profile-head img { width: 78px; height: 78px; border-radius: 50%; object-fit: cover; border: 2px solid var(--line-gold); }
.profile-head .nm { font-family: var(--serif); font-weight: 700; font-size: 1.3em; color: var(--green-deep); }
.profile-head .rl { color: var(--ink-dim); }
.profile-head .md { margin-top: 3px; font-weight: 600; }
.stage-line { display: flex; justify-content: space-between; font-size: .88em; margin-bottom: 4px; }
.stage-line .stage { display: inline-flex; align-items: center; gap: 3px; font-weight: 700; color: var(--gold-deep); }
.profile-notes { margin-top: 12px; }
.profile-notes .group-label { margin-top: 12px; }

/* Permanent header calendar rail. The current day is a solid forest-green tile
   centred in a fixed viewport; a wider set of dates lives in the track and the
   track slides horizontally (transform) when the day changes, so time reads as
   a continuous glide through a stable centre. No overlay, no marker, no flash. */
.hud-cal { display: flex; align-items: center; justify-content: center; flex: 1 1 auto; min-width: 0; overflow: hidden; }
.hud-cal-viewport {
  width: min(410px, 42vw);
  overflow: hidden;
}
/* The visible dates end cleanly at the viewport edges; there is no implied
   overflow treatment because the five-card strip is complete in the top bar. */
.hud-cal-track { display: flex; gap: 14px; justify-content: center; will-change: transform; }
.hud-cal-day {
  position: relative;
  flex: 0 0 66px;
  width: 66px;
  /* a little more height and internal padding: the tiles previously packed
     two lines of type plus the daypart segments into 32px, which is what
     made the strip feel compressed rather than the bar being too short */
  min-height: 36px;
  padding: 4px 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  border-radius: 10px;
  border: 1px solid rgba(84, 105, 88, .18);
  background: rgba(255, 253, 244, .3);
  /* an inactive tile is a quiet chip, not a raised card - the shadow it
     carried was multiplied by every tile in the strip */
  box-shadow: none;
  transition: background .3s ease, border-color .3s ease, box-shadow .3s ease;
}
.hud-cal-wd {
  font-size: 8.5px;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: rgba(27, 45, 32, .52);
  font-weight: 700;
  line-height: 1;
  opacity: .75;
}
/* Day + abbreviated month, one line, on every tile - no year in the compact
   rail; the full weekday/day/month/year is still available wherever a
   longer-form date is genuinely useful (see currentDateLabel() etc.). */
.hud-cal-dm {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .01em;
  text-transform: uppercase;
  color: rgba(27, 45, 32, .68);
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.hud-cal-daynum { font-weight: 800; }
.hud-cal-month { font-size: .84em; font-weight: 700; }
/* the selected day stays the one emphasised tile - a touch of lift plus the
   forest fill separates it from the now-flatter, quieter inactive dates */
.hud-cal-day.is-active {
  background: var(--green-deep);
  border-color: var(--green-deep);
  /* the active tile keeps a shadow - it is now the ONLY one in the strip,
     so a softer one separates it just as clearly as the old heavier cast */
  box-shadow: 0 1px 5px rgba(40, 60, 35, .18);
}
.hud-cal-day.is-active .hud-cal-wd { color: rgba(250, 245, 228, .8); opacity: 1; }
.hud-cal-day.is-active .hud-cal-dm { color: #FAF5E4; }
.hud-cal-day.is-active .hud-cal-daynum { font-weight: 850; }
/* segmented daypart progress - only shown on the active tile. Past segments
   brighten, the current one takes the daypart colour, future ones stay quiet.
   This is the homepage's only remaining time-of-day indicator (the old
   header daypart badge was removed separately, well before this pass) -
   restored after briefly being hidden here, since removing it left nowhere
   on screen showing morning/afternoon/evening. */
.hud-cal-seg { display: none; }
.hud-cal-day.is-active .hud-cal-seg { display: flex; gap: 2px; margin-top: 1px; width: 36px; }
.hud-cal-seg i {
  flex: 1;
  height: 3px;
  border-radius: 1.5px;
  background: rgba(159, 183, 156, .45);
}
.hud-cal-seg i.done { background: rgba(223, 235, 215, .68); }
.hud-cal-seg i.future { background: rgba(144, 171, 142, .48); }
.hud-cal-seg i.cur { background: #FFFDF4; }
.hud-cal-day.has-match::after {
  content: "";
  position: absolute;
  top: 5px;
  right: 6px;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--calendar-accent);
}
/* a future/past matchday reads at a glance, not only once the sliding track
   brings it to the active centre - a muted warm-gold/champagne fill, held
   well short of the bright accent gold so it stays premium rather than a
   neon flag. The corner dot is redundant once the tile itself carries the
   fill, so it steps aside here and returns only on the active tile below. */
.hud-cal-day.has-match:not(.is-active) {
  background: rgba(224, 189, 122, .4);
  border-color: rgba(150, 118, 54, .6);
}
.hud-cal-day.has-match:not(.is-active) .hud-cal-wd,
.hud-cal-day.has-match:not(.is-active) .hud-cal-dm {
  color: var(--green-deep);
  opacity: 1;
}
.hud-cal-day.has-match:not(.is-active)::after { display: none; }
/* today's own fixture keeps the same dark-green "current day" fill as any
   other current day, but earns a stronger, restrained gold marker in place
   of the quiet default dot. */
.hud-cal-day.is-active.has-match::after {
  width: 6px;
  height: 6px;
  top: 4px;
  right: 5px;
  background: var(--gold-bright);
  box-shadow: 0 0 0 2px rgba(217, 169, 60, .3);
}

#day-banner {
  position: fixed !important;
  top: 84px;
  left: 50%;
  z-index: 61;
  transform: translate(-50%, -22px) scale(.94);
  pointer-events: none;
  min-width: 260px;
  max-width: 420px;
  padding: 14px 26px;
  text-align: center;
  background: rgba(253, 248, 235, .97);
  border: 1px solid var(--line-gold);
  border-radius: var(--r-panel);
  /* thin gold cap along the top edge, echoing the modal language */
  box-shadow: inset 0 3px 0 rgba(201, 151, 58, .5), var(--shadow-panel);
  opacity: 0;
  transition: opacity .55s cubic-bezier(.22, .61, .36, 1), transform .55s cubic-bezier(.22, .61, .36, 1);
}
#day-banner.on { opacity: 1; transform: translate(-50%, 0) scale(1); }
#day-banner .date {
  font-family: var(--serif);
  font-weight: 700;
  font-size: 1.3em;
  color: var(--green-deep);
}
#day-banner .sub { margin-top: 3px; font-size: .88em; color: var(--ink-dim); }
#day-banner.life-at-club { max-width: 520px; padding: 15px 28px 17px; }
#day-banner .life-title { margin-top: 4px; color: var(--ink); font-weight: 750; }
#day-banner .life-people {
  margin-top: 2px;
  color: var(--gold-deep);
  font-size: .72em;
  font-weight: 800;
  letter-spacing: .08em;
  text-transform: uppercase;
}
#day-banner.life-at-club .sub { margin-top: 7px; line-height: 1.42; }

/* The old HUD date-shift animation (ghost of the previous date sliding up as
   the new one rose into place) was removed with the header date block: the
   permanent calendar rail owns the date display and animates its own slide. */

/* ---------- shared phase-transition modal (breaks / season review / new season) ---------- */
.modal.phase-modal { width: min(640px, 94vw); max-height: 90vh; }
.phase-modal .phase-dates {
  color: var(--ink-dim);
  font-size: .88em;
  margin: -6px 0 12px;
  font-style: italic;
}
.phase-modal .t-fx { border-bottom: 1px solid var(--line-soft); }
.phase-modal .t-fx:last-child { border-bottom: 0; }
.phase-modal .day-change .chg { font-size: .95em; }
.phase-modal .season-note-section { margin-top: 12px; padding-top: 10px; }

/* ---------- tactics modal ---------- */
/* pitch.mini now inherits sizing from .pitch's own aspect-ratio */
.tactic-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 8px; margin: 10px 0 8px; }
.tactic-card {
  padding: 12px 4px;
  text-align: center;
  font-weight: 700;
  font-size: .94em;
  color: var(--green-deep);
  background: #FFFDF4;
  border: 1px solid var(--line-gold-soft);
  border-radius: 12px;
  cursor: pointer;
  box-shadow: 0 1px 3px rgba(40, 60, 35, .08);
  transition: border-color .15s ease, background .15s ease, box-shadow .15s ease, transform .12s ease;
}
.tactic-card:hover { border-color: var(--line-gold); transform: translateY(-1px); box-shadow: 0 3px 9px rgba(40, 60, 35, .14); }
.tactic-card:active { transform: scale(.98); }
/* each card carries its DEF-MID-ATT notation, so switching tactics reads
   in numbers as well as on the pitch */
.tactic-card-shape {
  display: block;
  margin-top: 3px;
  color: var(--gold-deep);
  font-size: .76em;
  font-weight: 800;
  letter-spacing: .08em;
  font-variant-numeric: tabular-nums;
}
.tactic-card.selected .tactic-card-shape { color: var(--gold-deep); }
.tactic-card.selected {
  background: var(--gold-tint);
  border-color: var(--gold);
  box-shadow: inset 0 0 0 1px var(--gold);
  color: var(--green-deep);
}
.t-fx { display: flex; justify-content: space-between; gap: 12px; padding: 5px 0; font-size: .92em; border-bottom: 1px solid rgba(58, 84, 54, .12); }
.t-fx:last-child { border-bottom: 0; }
.t-fx span:first-child { color: var(--ink-dim); flex: 0 0 auto; }
.t-fx span:last-child { text-align: right; font-weight: 650; color: var(--ink-dim); }
.t-fx .effect-positive { color: var(--ok); }
.t-fx .effect-neutral { color: #4E514E; }
.t-fx .effect-caution,
.t-fx .effect-mixed { color: #9A6A18; }
.t-fx .effect-negative { color: #A4473F; }

/* Tactics is compacted into the Messenger sidecar without changing its
   selection or save logic. */
.messenger-sidecar .pitch.mini {
  width: 100%;
  max-height: 210px;
  margin: 10px 0 12px;
}
.messenger-sidecar .tactic-grid {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 6px;
  margin: 8px 0 10px;
}
.messenger-sidecar .tactic-card {
  min-height: 54px;
  padding: 8px 5px;
  font-size: .78em;
}
.messenger-sidecar .t-fx { font-size: .78em; }
.messenger-sidecar .p-section { padding: 10px; }
.sidecar-save-btn {
  width: 100%;
  min-height: 40px;
}
/* consequence toast */
#app > .toast-wrap {
  position: absolute !important; /* never in the flex flow - see layout.css note */
  /* bottom-right: the consequence layer never collides with the day banner */
  bottom: 24px;
  right: 22px;
  z-index: 50;
  display: flex;
  flex-direction: column;
  gap: 8px;
  pointer-events: none;
}
.toast {
  animation: toastIn .35s cubic-bezier(.22, .61, .36, 1);
  min-width: 220px;
  max-width: 320px;
  padding: 10px 14px;
  background: var(--cream);
  border: 1px solid var(--line-gold);
  border-left: 3px solid var(--gold);
  border-radius: var(--r-card);
  box-shadow: var(--shadow-panel);
  font-size: .92em;
}
.toast .t { font-family: var(--serif); font-weight: 700; color: var(--green-deep); margin-bottom: 2px; }
@keyframes toastIn { from { opacity: 0; transform: translateX(40px) scale(.95); } to { opacity: 1; transform: none; } }
.toast.out { animation: toastOut .25s ease forwards; }
.toast.toast-refocus { animation: pbToastRefocus .35s ease-out; }
@keyframes pbToastRefocus { 45% { transform: translateX(-5px); } }
@keyframes toastOut { to { opacity: 0; transform: translateX(8px); } }

/* simple grid views (Dogs / Contacts / Reports / Schedule) */
.view-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 12px;
}
.view-panel { max-height: 100%; overflow-y: auto; }
.group-label {
  font-size: .78em;
  font-weight: 700;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--gold-deep);
  margin: 16px 0 8px;
}
.group-label:first-child { margin-top: 0; }
.big-card {
  padding: 14px 10px;
  background: #FFFDF4;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-card);
  box-shadow: var(--shadow-card);
  text-align: center;
  transition: border-color .12s ease;
}
.big-card:hover { border-color: var(--line-gold); }
.big-card > img,
.big-card > .portrait-frame { width: 74px; height: 74px; border-radius: 50%; object-fit: cover; margin: 0 auto 8px; border: 2px solid var(--line-gold); }
.big-card .card-age { color: var(--ink-faint); font-size: .78em; font-weight: 500; margin-top: 2px; }
.big-card .rel-bar { width: 80%; margin: 8px auto 0; }
.sched-row {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 42px;
  padding: 7px 4px;
  border-bottom: 1px solid var(--line-soft);
}
.sched-row .sched-team { font-weight: 650; color: var(--green-deep); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sched-row .d { margin-left: auto; color: var(--ink-dim); white-space: nowrap; font-variant-numeric: tabular-nums; }
/* countdown pill on the next fixture */
.sched-count {
  flex: 0 0 auto;
  padding: 3px 9px;
  border-radius: var(--r-pill);
  background: var(--green-btn);
  color: var(--cream);
  font-size: .74em;
  font-weight: 750;
  white-space: nowrap;
}

.sched-phase {
  width: fit-content;
  margin-bottom: 10px;
  padding: 3px 10px;
  border: 1px solid var(--line-gold-soft);
  border-radius: var(--r-pill);
  background: var(--gold-tint);
  color: var(--gold-deep);
  font-size: .8em;
  font-weight: 700;
}
.sched-empty { color: var(--ink-dim); padding: 12px 4px; }
.sched-sub {
  margin: 20px 0 6px;
  font-size: var(--fs-label);
  font-weight: 800;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--label-gold);
}

.table-row {
  display: grid;
  grid-template-columns: 24px 1fr 30px 64px 42px 36px;
  align-items: center;
  gap: 8px;
  min-height: 34px;
  padding: 5px 6px;
  border-bottom: 1px solid var(--line-soft);
  font-size: .92em;
  font-variant-numeric: tabular-nums;
}
.table-row.table-head {
  min-height: 26px;
  border-bottom: 1px solid var(--line-gold-soft);
  color: var(--ink-faint);
  font-size: .72em;
  font-weight: 750;
  letter-spacing: .05em;
  text-transform: uppercase;
}
.table-row.table-head .tn, .table-row.table-head .st { color: var(--ink-faint); font-weight: 750; }
.table-row.me {
  background: var(--gold-tint);
  border-radius: 8px;
  box-shadow: inset 3px 0 0 var(--gold);
}
.table-row.me .tn { font-weight: 750; color: var(--green-deep); }
.table-row.me .pos { font-weight: 800; color: var(--green-deep); }
.table-row .pos { color: var(--ink-dim); text-align: right; }
.table-row .tn { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.table-row .st { color: var(--ink-dim); text-align: right; }
.table-row .st.pts { color: inherit; font-weight: 800; }

/* ---------- two-division league tables ---------- */
.modal.league-modal {
  width: min(1180px, 96vw);
  max-height: 92vh;
  padding: clamp(16px, 1.5vw, 24px);
}
.league-screen { min-width: 0; }
.league-tabs {
  display: inline-flex;
  gap: 5px;
  padding: 4px;
  margin-bottom: 8px;
  background: rgba(55, 102, 63, .08);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-card);
}
.league-tab {
  padding: 8px 14px;
  border-radius: 8px;
  color: var(--ink-dim);
  font-weight: 650;
  transition: background .12s ease, color .12s ease, box-shadow .12s ease;
}
.league-tab:hover { color: var(--green-deep); background: rgba(253, 248, 235, .65); }
.league-tab.active {
  color: var(--cream);
  background: var(--green-btn);
  box-shadow: var(--shadow-card);
}
.league-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  margin-bottom: 6px;
  color: var(--ink-dim);
  font-size: .83em;
}
/* North/South are parallel divisions with no promotion or relegation
   between them (Batch 4), so the table carries no legend, no promotion/
   relegation row markers - only its own "me" row highlight below. */
.league-table-scroll {
  border: 1px solid var(--line-soft);
  border-radius: var(--r-card);
  background: rgba(255, 253, 244, .68);
}
.league-table { min-width: 850px; font-variant-numeric: tabular-nums; }
.league-table-row {
  display: grid;
  grid-template-columns: 42px 40px minmax(190px, 1fr) repeat(8, 44px) minmax(150px, 170px);
  align-items: center;
  min-height: 32px;
  padding: 3px 10px 3px 6px;
  border-bottom: 1px solid var(--line-soft);
}
.league-table-row:last-child { border-bottom: 0; }
/* zebra striping keeps eyes on the right row across ten dense columns */
.league-table-row:not(.league-table-head):not(.me):nth-child(even) { background: rgba(255, 253, 244, .55); }
.league-table-row > span:not(.league-club):not(.league-badge-cell):not(.league-last-five) { text-align: right; }
.league-table-head {
  position: sticky;
  top: 0;
  z-index: 2;
  min-height: 28px;
  background: rgba(246, 239, 219, .98);
  color: var(--ink-dim);
  font-size: .76em;
  font-weight: 750;
  letter-spacing: .04em;
  text-transform: uppercase;
  box-shadow: 0 1px 0 var(--line-gold-soft);
}
/* the player's row is highlighted through its own fill/weight alone - no
   legend swatch or row-edge marker is needed to explain it */
.league-table-row.me {
  background: rgba(217, 169, 60, .17);
  color: var(--green-deep);
  font-weight: 700;
}

/* Sidecar standings show every club while keeping only the columns that can
   still be read at the compressed width. The full competition data remains
   authoritative; this is a deliberate presentation subset, not another
   table model. */
.sidecar-league .league-tabs {
  width: 100%;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
  padding: 0;
  margin-bottom: 12px;
  border: 0;
  background: transparent;
  box-shadow: none;
}
.sidecar-league .league-tab {
  min-width: 0;
  min-height: 34px;
  padding: 7px 10px;
  border: 1px solid rgba(217, 169, 60, .42);
  border-radius: var(--r-card);
  color: var(--green-deep);
  background: rgba(255, 253, 244, .5);
  font-size: var(--fs-label);
  font-weight: 800;
  letter-spacing: .07em;
  text-align: center;
  text-transform: uppercase;
  box-shadow: var(--shadow-card);
}
.sidecar-league .league-tab.active {
  color: #FBF7EA;
  border-color: rgba(217, 169, 60, .55);
  background: linear-gradient(180deg, var(--green-mid), var(--green-deep));
  box-shadow: inset 0 1px 0 rgba(255, 251, 235, .28), var(--shadow-card);
}
.sidecar-league .league-meta {
  justify-content: flex-start;
  gap: 0;
  margin: 0 4px 10px;
  color: var(--ink-faint);
  font-size: var(--fs-meta);
  font-weight: 500;
}
.sidecar-league .league-meta span + span::before {
  content: "·";
  margin: 0 7px;
  color: var(--gold-deep);
}
.sidecar-league-table {
  overflow: visible;
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  font-variant-numeric: tabular-nums;
}
.sidecar-league-row {
  display: grid;
  grid-template-columns: 20px 22px minmax(0, 1fr) 26px 28px 30px;
  align-items: center;
  gap: 4px;
  min-height: 30px;
  padding: 4px;
  border-bottom: 0;
  color: var(--ink-dim);
  font-size: var(--fs-meta);
  line-height: 1.2;
}
.sidecar-league-row:last-child { border-bottom: 0; }
.sidecar-league-row > span:not(.sidecar-league-club):not(.league-badge-cell) { text-align: right; }
.sidecar-league-row.head {
  position: sticky;
  top: 0;
  z-index: 2;
  min-height: 24px;
  color: var(--ink-faint);
  background: transparent;
  font-size: var(--fs-label);
  font-weight: 800;
  letter-spacing: .06em;
  text-transform: uppercase;
  border-bottom: 1px solid rgba(84, 105, 88, .24);
}
.sidecar-league-row.me {
  color: var(--green-deep);
  background: #F3E6C6;
  border-radius: 6px;
  font-weight: 800;
  box-shadow: none;
}
.sidecar-league-club {
  min-width: 0;
  padding-left: 3px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sidecar-league-row .league-club-badge {
  width: 18px;
  height: 18px;
}
@media (max-width: 1100px) {
  /* At the supported 1024px floor, protect the two things that identify a
     standing at a glance: full club names and points. The badge and matches
     played return automatically as soon as the stage has room. */
  .sidecar-league-row {
    grid-template-columns: 18px minmax(0, 1fr) 32px;
    gap: 6px;
  }
  .sidecar-league-row .league-badge-cell,
  .sidecar-league-row .sidecar-stat-mp,
  .sidecar-league-row .sidecar-stat-gd {
    display: none;
  }
}
.league-pos { color: var(--ink-dim); }
.league-badge-cell { display: flex; justify-content: center; }
.league-club { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding-left: 4px; }
.league-points { font-weight: 800; color: var(--ink); }
.league-last-five { padding-left: 9px; }
.league-club-badge {
  position: relative;
  width: 25px;
  height: 29px;
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  overflow: hidden;
  clip-path: polygon(50% 0, 94% 15%, 88% 70%, 50% 100%, 12% 70%, 6% 15%);
  background: var(--badge-primary);
  color: var(--badge-secondary);
  box-shadow: inset 0 0 0 2px var(--badge-accent);
}
.league-club-badge::after {
  content: "";
  position: absolute;
  inset: 3px;
  clip-path: inherit;
  border: 1px solid var(--badge-accent);
}
.league-club-badge > span {
  position: relative;
  z-index: 1;
  font-family: var(--serif);
  font-size: 7px;
  font-weight: 850;
  letter-spacing: -.02em;
  text-shadow: 0 1px 1px rgba(0, 0, 0, .45);
}
.league-club-badge.badge-sash { background: linear-gradient(135deg, var(--badge-primary) 0 38%, var(--badge-accent) 39% 57%, var(--badge-primary) 58%); }
.league-club-badge.badge-halves { background: linear-gradient(90deg, var(--badge-primary) 0 50%, var(--badge-secondary) 50%); color: var(--badge-accent); }
.league-club-badge.badge-stripe { background: repeating-linear-gradient(90deg, var(--badge-primary) 0 6px, var(--badge-secondary) 6px 10px); color: var(--badge-accent); }
.league-club-badge.badge-quarters { background: conic-gradient(var(--badge-primary) 0 25%, var(--badge-secondary) 0 50%, var(--badge-primary) 0 75%, var(--badge-secondary) 0); color: var(--badge-accent); }
.league-club-badge.badge-chevron { background: linear-gradient(145deg, transparent 0 42%, var(--badge-accent) 43% 56%, transparent 57%), var(--badge-primary); }
.league-club-badge.player-badge {
  clip-path: none;
  object-fit: contain;
  background: transparent;
  box-shadow: none;
}
.league-form { display: flex; justify-content: flex-end; gap: 4px; }
.form-dot {
  width: 19px;
  height: 19px;
  display: inline-grid;
  place-items: center;
  border-radius: 50%;
  color: #fff;
  font-size: 8.5px;
  font-weight: 800;
  line-height: 1;
}
.form-w { background: var(--ok); }
.form-d { background: rgba(80, 84, 93, .55); }
.form-l { background: var(--red); }
.form-empty { color: var(--ink-faint); background: rgba(58, 84, 54, .08); }
.sched-table-link { margin-top: 10px; }

.typing.bubble {
  width: 54px;
  min-height: 34px;
  padding: 10px 13px;
}
/* A DM's typing bubble carries data-speaker immediately (the contact is
   known before they reply), so it already inherits that colleague's own
   colour via the [data-speaker] rule above - no separate rule needed. In a
   group the actual speaker isn't decided until the reply resolves, so
   guessing a colour would be wrong; it reads as a neutral translucent
   system tone instead (same treatment as .bubble.sys), never the old flat
   green .bubble.them fallback. */
.bubble.them.typing:not([data-speaker]) {
  background: var(--cream);
  color: var(--ink-dim);
}
.typing-dots {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  height: 13px;
}
.typing-dots span {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  opacity: .62;
  animation: messengerTypingDot 1.5s ease-in-out infinite;
}
.typing-dots span:nth-child(2) { animation-delay: .16s; }
.typing-dots span:nth-child(3) { animation-delay: .32s; }
@keyframes messengerTypingDot {
  0%, 60%, 100% { transform: translateY(0); opacity: .48; }
  30% { transform: translateY(-4px); opacity: .95; }
}
@media (prefers-reduced-motion: reduce) {
  .typing-dots span {
    animation: none !important;
    transform: none !important;
    opacity: .75;
  }
  .convo,
  .scene-panel,
  .messenger-body.scene-active .scene-panel,
  .messenger-body.sidecar-active .scene-panel {
    transition-duration: .08s;
    transition-property: opacity, visibility;
  }
}

.reply-failed {
  align-self: flex-start;
  display: flex;
  align-items: center;
  gap: 9px;
  margin: 2px 8px;
  padding: 9px 12px;
  color: var(--bad);
  background: #FFFDF4;
  border: 1px solid var(--line-soft);
  border-left: 3px solid var(--bad);
  border-radius: 10px;
  box-shadow: 0 1px 3px rgba(40, 60, 35, .08);
  font-size: .83em;
}
/* ---------- Stage 2: dashboard life ---------- */

/* dashboard entrance: hud fades first, panels rise in sequence. Guarded so
   reduced-motion users never see stuck opacity:0 panels. */
@media (prefers-reduced-motion: no-preference) {
  /* the club wakes up in reading order: header, then the people rail, then
     the stage, then the dashboard */
  #app.boot .hud { opacity: 0; animation: pbBootFade .25s ease-out forwards; }
  #app.boot .people-rail,
  #app.boot .messenger,
  #app.boot .col-right > .panel { opacity: 0; animation: pbPanelIn .55s cubic-bezier(.22, .61, .36, 1) forwards; }
  #app.boot .people-rail { animation-delay: .22s; }
  #app.boot .messenger { animation-delay: .38s; }
  #app.boot .col-right > .panel:nth-child(1) { animation-delay: .5s; }
  #app.boot .col-right > .panel:nth-child(2) { animation-delay: .62s; }
}
@keyframes pbBootFade { from { opacity: 0; } to { opacity: 1; } }
@keyframes pbPanelIn { from { opacity: 0; transform: translateY(26px) scale(.985); } to { opacity: 1; transform: none; } }

/* value change: new value rises in, then a brief glow in its own tone colour */
.value-changed { display: inline-block; animation: pbValueChanged 1.5s ease-out; }
@keyframes pbValueChanged {
  0% { opacity: 0; transform: translateY(9px); }
  20% { opacity: 1; transform: translateY(0) scale(1.12); }
  30%, 70% { text-shadow: 0 0 4px currentColor, 0 0 18px currentColor; transform: scale(1.12); }
  100% { text-shadow: none; transform: scale(1); }
}

/* time of day carries a quiet colour identity */
.status-time { transition: color .3s ease, background-color .3s ease; }
.status-time.tod-0 { color: var(--gold-deep); background: var(--gold-tint); }
.status-time.tod-1 { color: var(--gold-deep); background: var(--gold-tint); }
.status-time.tod-2 { color: #6B4E75; background: rgba(117, 86, 122, .16); }
.status-time.swap { animation: pbValueChanged 1.5s ease-out; }

/* modal entrance / exit */
.overlay { animation: pbOverlayIn .2s ease-out; }
.overlay .modal { animation: pbModalIn .28s cubic-bezier(.22, .61, .36, 1); }
.overlay.closing { animation: pbOverlayOut .15s ease-in forwards; }
.overlay.closing .modal { animation: pbModalOut .15s ease-in forwards; }
@keyframes pbOverlayIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes pbModalIn { from { opacity: 0; transform: scale(.88) translateY(14px); } to { opacity: 1; transform: none; } }
@keyframes pbOverlayOut { to { opacity: 0; } }
@keyframes pbModalOut { to { opacity: 0; transform: scale(.97); } }

/* live bubble entrances - effects glow on their own overlay layer, so the
   entrance always plays, including on stat-changing replies. Effect classes
   coming and going never change the bubble's computed animation, so the
   entrance can never re-run. */
.bubble.bubble-in-them { animation: pbBubbleIn .34s cubic-bezier(.22, .61, .36, 1) backwards; }
.bubble.bubble-in-me { animation: pbBubbleIn .26s cubic-bezier(.22, .61, .36, 1) backwards; }
/* Reply morph: when a reply replaces the typing indicator it grows out of
   the pill's footprint (bottom-left anchored, like the pill itself) rather
   than arriving as a separate bubble - one object becoming the message */
.bubble.bubble-morph-in {
  transform-origin: 0 100%;
  animation: pbBubbleMorph .3s cubic-bezier(.3, 1.18, .56, 1) backwards;
}
@keyframes pbBubbleMorph {
  from { opacity: .45; transform: scale(var(--morph-sx, .4), var(--morph-sy, .55)); }
  to { opacity: 1; transform: none; }
}
@keyframes pbBubbleIn { from { opacity: 0; transform: translateY(16px) scale(.9); } to { opacity: 1; transform: none; } }

/* typing bubble breathes in, then keeps a very slow warm breath while the
   reply is being written. The breath is a static glow on ::after whose
   opacity pulses - composited, no per-frame repaint. Removal is instant and
   handled in JS: the arriving reply's entrance covers the hand-over. */
.typing.bubble { transform-origin: 0 100%; animation: pbTypingIn .3s cubic-bezier(.34, 1.4, .64, 1); }
.typing.bubble::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  box-shadow: 0 0 0 1px rgba(214, 186, 120, .3), 0 0 12px rgba(214, 186, 120, .22);
  opacity: 0;
  animation: pbTypingBreath 3.2s ease-in-out .5s infinite;
}
@keyframes pbTypingIn { from { opacity: 0; transform: scale(.55); } to { opacity: 1; transform: scale(1); } }
@keyframes pbTypingBreath {
  0%, 100% { opacity: 0; }
  50% { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .typing.bubble { animation: pbTypingIn .3s ease-out; }
}

/* thread header while a reply is being written */
.presence-label.is-typing { color: var(--gold-deep); font-style: italic; }

/* ---------- Stage 3: state theatre ---------- */

/* Next Match urgency ramp. The card also carries a permanent whisper of
   gold along its top edge - the one panel besides Continue allowed a
   standing accent, because "what's next" is the game's second question. */
.match-panel {
  position: relative;
  overflow: hidden;
  transition: border-color .4s ease, box-shadow .4s ease;
  box-shadow: inset 0 2px 0 rgba(201, 151, 58, .38), var(--shadow-panel);
  /* the match card sits a step more solid than its neighbours */
  background: rgba(253, 248, 235, .88);
}
/* border-color removed by request - with the dashboard reset's 1px
   transparent border-inline (see .dashboard-scroll .match-panel), turning
   it gold here drew two thin vertical lines down the sides of the Next
   Match card. The h2 gold tint alone still signals "match soon". */
.match-panel.match-soon h2 { color: var(--gold-bright); }
.match-panel.match-day {
  border-color: var(--gold);
  box-shadow: 0 0 0 2px rgba(217, 169, 60, .45), 0 0 22px rgba(217, 169, 60, .3), var(--shadow-panel);
  /* match day earns a slightly more solid card than the everyday .88 */
  background: rgba(253, 248, 235, .95);
}
.match-panel.match-day h2 { color: var(--gold-bright); }
.match-panel.match-day .match-opp { font-size: calc(var(--fs-display) * 1.09); }
.match-panel.match-day .match-sub { color: var(--gold-deep); font-weight: 700; }
.match-panel.match-day::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 3px;
  pointer-events: none;
  background: linear-gradient(90deg, transparent, rgba(233, 190, 90, 1), transparent);
  transform: translateX(-100%);
  animation: pbMatchShimmer 3s ease-in-out 3;
}
@keyframes pbMatchShimmer { 0% { transform: translateX(-100%); } 55%, 100% { transform: translateX(100%); } }

/* post-match result flash on the panel: one glow in the result's colour */
.match-panel.result-win { animation: pbResultWin 1.4s ease-out; }
.match-panel.result-draw { animation: pbResultDraw 1.4s ease-out; }
.match-panel.result-loss { animation: pbResultLoss 1.4s ease-out; }
@keyframes pbResultWin {
  12% { transform: scale(1.02); }
  12%, 65% { box-shadow: 0 0 0 4px rgba(62, 142, 79, .75), 0 0 44px rgba(62, 142, 79, .65), var(--shadow-panel); }
  30% { transform: scale(1); }
}
@keyframes pbResultDraw {
  12%, 65% { box-shadow: 0 0 0 4px rgba(197, 152, 58, .7), 0 0 40px rgba(197, 152, 58, .55), var(--shadow-panel); }
}
@keyframes pbResultLoss {
  8% { transform: translateX(-5px); }
  16% { transform: translateX(4px); }
  24% { transform: translateX(0); }
  12%, 65% { box-shadow: 0 0 0 4px rgba(192, 97, 78, .75), 0 0 44px rgba(192, 97, 78, .6), var(--shadow-panel); }
}

/* tactic change: formation markers glide to their new positions instead of
   teleporting - quick and subtle, since this fires on every tactic preview click */
.pitch .tactic-marker { transition: left .45s cubic-bezier(.45, 0, .25, 1), top .45s cubic-bezier(.45, 0, .25, 1); }

/* pressure bar moves visibly when it changes */
.pressure .bar span { transition: width .5s ease-out; }

/* relationship milestone: one gold shimmer across the contact's row and card */
.rel-shimmer::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  border-radius: inherit;
  background: linear-gradient(105deg, transparent 25%, rgba(244, 210, 122, .65) 50%, transparent 75%);
  transform: translateX(-110%);
  animation: pbRelShimmer 1.1s ease-out forwards;
}
@keyframes pbRelShimmer { to { transform: translateX(110%); } }
.convo-rel.stage-changed .rel-bar { animation: pbStageGlow .6s ease-out; }
.convo-rel.stage-changed .rel-bar.romance,
.convo-rel.stage-changed .rel-bar.warm { animation-name: pbStageGlowRose; }
@keyframes pbStageGlow { 25%, 70% { box-shadow: 0 0 0 2px rgba(218, 175, 74, .9), 0 0 20px rgba(218, 175, 74, .9); } }
@keyframes pbStageGlowRose { 25%, 70% { box-shadow: 0 0 0 2px rgba(217, 138, 150, .9), 0 0 20px rgba(217, 138, 150, .9); } }

/* unread arrival: one stronger amber flash on the row, badge springs in */
.char-card.unread-arrive { animation: pbUnreadFlash .9s ease-out; }
@keyframes pbUnreadFlash {
  0%, 35% { box-shadow: inset 0 0 0 999px rgba(232, 196, 106, .55), 0 0 14px rgba(217, 169, 60, .5); }
  100% { box-shadow: inset 0 0 0 999px transparent; }
}
.unread-badge.badge-spring { animation: pbBadgeSpring .5s cubic-bezier(.34, 1.56, .64, 1); }
@keyframes pbBadgeSpring { 0% { transform: scale(0); } 60% { transform: scale(1.45); } 100% { transform: scale(1); } }

/* contact coming online: a single pulse, never ambient */
.online-dot.pulse { animation: pbDotPulse .6s ease-out; }
@keyframes pbDotPulse { 40% { transform: scale(1.9); box-shadow: 0 0 10px rgba(53, 168, 83, .8); } 100% { transform: scale(1); } }

/* ---------- Stage 4: refinement ---------- */

/* Sound and settings keep the Continue button's 36px visual rhythm. Their
   invisible hit target extends to 44px, so touch and pointer access improve
   without moving the right-hand controls or making the glyphs oversized. */
/* Widened slightly (8px -> 12px, final UI precision pass) so the icon
   button and Continue read as two separate controls, not a fused block. */
.hud-continue { gap: 12px; }
.hud-icon-btn {
  width: 36px;
  height: 36px;
  flex: 0 0 36px;
  display: grid;
  place-items: center;
  position: relative;
  border-radius: 10px;
  background: transparent;
  color: rgba(27, 45, 32, .82);
  transition: background .14s ease, color .14s ease, transform .09s ease;
}
.hud-icon-btn::before { content: ""; position: absolute; inset: -4px; }
.hud-icon-btn .ui-icon { width: 20px; height: 20px; }
.hud-icon-btn:hover { color: var(--green-deep); background: rgba(58, 84, 54, .12); }
.hud-icon-btn:active { transform: scale(.94); }
.hud-icon-btn:focus-visible { outline: 2px solid var(--gold-bright); outline-offset: 2px; }
.hud-icon-btn.active { color: var(--green-deep); background: var(--gold-tint); }

/* the thread warms almost imperceptibly for romantic bonds - a whisper,
   never a statement */
/* Romance no longer tints the thread background - that was a translucent
   wash over the wallpaper. The .romance-tint class is still toggled by
   app.js (see renderConvo) for any future non-visual use, but it now
   carries no styling of its own. */

/* ---------- v5.1.31 micro-interaction pass ---------- */

/* chevrons acknowledge intent: every "View X ›" button nudges its arrow
   toward the destination on hover */
.panel-btn > span:last-child { transition: transform .18s cubic-bezier(.4, 0, .2, 1); }
.panel-btn:hover > span:last-child { transform: translateX(3px); }
.panel-btn:active > span:last-child { transform: translateX(1px); }

/* ---------- clickable panel heading ----------
   A panel title that is also a destination. It keeps the exact typography of
   every other h2 and adds only a chevron and a hover underline, so nothing
   about the panel's weight or rhythm changes. */
.panel-h2-link {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  position: relative;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  color: inherit;
  cursor: pointer;
  transition: color .14s ease;
}
.panel-h2-link:hover { color: var(--gold-deep); }
.panel-h2-link::after {
  content: "";
  position: absolute;
  left: 0;
  right: 14px;
  bottom: -3px;
  height: 1.5px;
  border-radius: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform .22s cubic-bezier(.4, 0, .2, 1);
}
.panel-h2-link:hover::after { transform: scaleX(1); }
.panel-h2-go {
  font-size: 1.1em;
  line-height: 1;
  transition: transform .18s cubic-bezier(.4, 0, .2, 1);
}
.panel-h2-link:hover .panel-h2-go { transform: translateX(2px); }

/* ---------- Game Management: save/load slots (Task 2) ---------- */
.save-actions { display: flex; flex-direction: column; gap: 8px; }
.save-slot-list { display: flex; flex-direction: column; gap: 10px; }
.save-slot {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 13px 14px; border: 1px solid var(--line-gold); border-radius: var(--r-card, 11px);
  background: var(--panel-light);
}
.save-slot.is-empty { border-style: dashed; background: var(--cream-2); opacity: .9; }
.save-slot-main { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.save-slot-main strong { color: var(--green-deep); font-size: 1.02em; }
.save-slot-main .d { font-size: .72em; letter-spacing: .05em; text-transform: uppercase; color: var(--ink-faint); }
.save-slot-meta { display: flex; flex-wrap: wrap; gap: 4px 12px; margin-top: 3px; font-size: .82em; color: var(--ink-dim); }
.save-slot-time { font-size: .76em; color: var(--ink-faint); margin-top: 2px; }
.save-slot-action { flex: 0 0 auto; }
.save-slot-action .slot-btn { white-space: nowrap; }
.slot-empty-tag { font-size: .82em; color: var(--ink-faint); }
.save-empty-state {
  padding: 20px; border: 1px dashed var(--line-gold); border-radius: var(--r-card, 11px);
  color: var(--ink-dim); font-size: .92em; text-align: center;
}
.save-success { padding: 6px 2px; color: var(--green-deep); font-size: .95em; }
.save-error { padding: 6px 2px; color: var(--red); font-size: .95em; }

/* Evening intentionally uses the exact same panel/glass treatment as morning
   and afternoon - only the wallpaper changes. Panels must never look
   different across times of day. */

/* ---------- unified right-side Dashboard ----------
   Structural layout pass: the old left column and the two separate right
   cards are consolidated into one compact Dashboard panel. Inner blocks reuse
   the existing panel/mood/rep/league/tactics markup, so their styling and JS
   hooks are untouched; here we only strip the nested card chrome so they read
   as sections rather than cards, and keep everything compact. */
.dashboard-panel {
  --dash-space-1: 4px;
  --dash-space-2: 8px;
  --dash-space-3: 12px;
  --dash-space-4: 16px;
  --dash-line: rgba(84, 105, 88, .24);
  --dash-button-border: rgba(217, 169, 60, .42);
  --dash-button-border-strong: rgba(217, 169, 60, .62);
  --dash-button-fill: var(--green-btn);
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
}
.dashboard-panel > .dashboard-head {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--dash-space-3);
  padding-bottom: var(--dash-space-3);
}
.dashboard-panel > .dashboard-head h2 {
  margin: 0;
  color: var(--green-deep);
}
/* Weather sits beside the "Dashboard" title, top of the panel rather than a
   scroll row lower down - pure flavour (see WEATHER_LABELS/WEATHER_TEMP_RANGE
   in app.js), same deterministic seasonal roll the wallpaper uses so the
   number and icon can never disagree with what's on screen. */
.weather-strip {
  display: flex;
  align-items: center;
  gap: var(--dash-space-1);
  margin: 0;
}
.weather-strip[hidden] { display: none; }
/* icon + degrees only - the condition word (still set in JS for anyone
   inspecting weather-label) is visually dropped so this reads as one quick
   glance, not a second line of copy competing with "Dashboard" */
.weather-strip .weather-icon { width: 17px; height: 17px; color: var(--green-deep); flex: 0 0 auto; }
/* a temperature is dashboard data, so it sits in the sans stack like every
   other number in this column - the serif step belongs to the club title,
   section headings and major match information */
.weather-temp { font-weight: 700; font-size: var(--fs-body); color: var(--ink-dim); font-variant-numeric: tabular-nums; }
.weather-label { display: none; }
.dashboard-scroll {
  flex: 1 1 auto;
  min-height: 0;
  /* homepage scrollbars removed by request - see .col in layout.css for the
     same change on the outer column. Content here is a fixed, bounded set
     of rows/buttons. */
  overflow-y: hidden;
  overflow-x: hidden;
  overscroll-behavior: contain;
  display: flex;
  flex-direction: column;
  gap: 0;
  padding: 0 2px 0 0;
}

/* nested blocks lose their card chrome and become flat sections. The
   box-shadow reset is skipped for .match-panel - it carries its own
   permanent gold top-edge and match-soon/day accents (see .match-panel
   rules above), which this reset was otherwise silently cancelling because
   it comes later in the cascade at equal selector specificity. The border
   stays transparent on every nested panel, .match-panel included, so no
   rectangular outline shows in its default state. */
.dashboard-scroll .panel {
  background: transparent;
  border: 1px solid transparent; /* transparent so match-soon/day glow can tint it */
  border-radius: 0;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  padding: 0;
}
.dashboard-scroll .panel:not(.match-panel) { box-shadow: none; }
.dashboard-scroll .match-panel {
  flex: 0 0 auto;
  overflow: visible;
  border-block: 0;
  border-inline: 1px solid transparent;
  box-shadow: none;
  padding-bottom: 0;
}
.dashboard-scroll .dash-section + .dash-section {
  border-top: 0;
  padding-top: 0;
}

/* ---------- dashboard navigation ---------- */
.dash-nav { display: flex; flex-direction: column; }
.dash-nav-row {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 0;
  background: none;
  border: 0;
  border-radius: 8px;
  color: var(--ink);
  text-align: left;
  cursor: pointer;
  transition: background .14s ease, color .14s ease;
}
.dash-nav-row:hover { background: rgba(255, 253, 244, .55); color: var(--green-deep); }
.dash-nav-row:active { background: rgba(255, 253, 244, .75); }
.dash-nav-row:focus-visible { outline: 2px solid var(--gold); outline-offset: -2px; }
.dash-nav-label {
  flex: 0 1 auto;
  min-width: 0;
  font-size: .92em;
  font-weight: 700;
  color: var(--green-deep);
  white-space: nowrap;
}
.dash-nav-value {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-align: right;
  font-size: var(--fs-meta);
  color: var(--ink-dim);
}
.dash-nav-value:empty { flex: 1 1 auto; }
.dash-nav-row .panel-h2-go { flex: 0 0 auto; color: var(--gold-deep); transition: transform .18s cubic-bezier(.4, 0, .2, 1); }
.dash-nav-row:hover .panel-h2-go { transform: translateX(3px); }
/* One structural divider separates the standings/match group from club
   status. Spacing carries the remaining hierarchy. */
.dash-divider {
  flex: 0 0 auto;
  height: 1px;
  margin: var(--dash-space-1) 0 var(--dash-space-2);
  background: var(--dash-line);
}
.dashboard-scroll .panel h2 { margin: 0 0 6px; }

/* Next Match: demoted eyebrow instead of a second major heading */
.match-eyebrow {
  display: flex;
  align-items: center;
  gap: var(--dash-space-1);
  margin: 0 0 var(--dash-space-1);
  padding: 0;
  background: none;
  border: 0;
  /* same small-label recipe as .panel h2; only the colour is quieter,
     because this is an eyebrow rather than the card's own title */
  font-size: var(--fs-label);
  font-weight: 800;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-faint);
  cursor: pointer;
}
.match-eyebrow:hover { color: var(--green-deep); }

/* Keep the fixture identity compact without flattening its hierarchy. */
.dashboard-scroll .match-eyebrow { margin-bottom: 4px; }
.dashboard-scroll .match-opp {
  flex-wrap: nowrap;
  align-items: baseline;
  gap: var(--dash-space-2);
  font-size: calc(var(--fs-display) * 1.05);
  line-height: 1.12;
}
.dashboard-scroll .match-opp-name {
  flex: 0 1 auto;
  min-width: 0;
  font-family: var(--sans);
  font-weight: 750;
  line-height: 1.08;
  overflow-wrap: anywhere;
  text-wrap: balance;
}
.dashboard-scroll .match-side {
  flex: 0 0 auto;
  font-family: var(--sans);
  font-size: .52em;
  font-weight: 650;
  letter-spacing: .02em;
  color: var(--ink-dim);
}
.match-action-row {
  display: flex;
  flex-direction: column;
  gap: var(--dash-space-2);
  margin-top: var(--dash-space-2);
}

/* Today is a compact state readout, not another navigation cluster. It is
   rendered only when app.js has a useful existing value to show. */
.today-section {
  flex: 0 0 auto;
  margin-top: var(--dash-space-3);
  padding-top: var(--dash-space-3);
  border-top: 1px solid var(--dash-button-border);
}
.today-section[hidden] { display: none; }
.today-heading {
  margin: 0 var(--dash-space-1) var(--dash-space-2);
  color: var(--ink-faint);
  font-family: var(--sans);
  font-size: .8em;
  font-weight: 800;
  letter-spacing: .1em;
}
.today-rows { display: flex; flex-direction: column; gap: var(--dash-space-1); }
.today-row {
  display: flex;
  align-items: baseline;
  gap: var(--dash-space-2);
  min-width: 0;
  padding: 0 var(--dash-space-1);
  font-size: .94em;
}
.today-row-label { flex: 0 0 auto; color: var(--ink-faint); }
.today-row-value {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-align: right;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 700;
  color: var(--green-deep);
}

/* Nearby standings use the full available width; the complete table remains
   one click away in the match actions above. */
.league-section { margin-top: var(--dash-space-3); }
.league-mini {
  display: flex;
  flex-direction: column;
  margin-top: 0;
  max-height: none;
  overflow: visible;
  padding: 0;
}
/* Pos / Club / GD / Pts. The full stat set belongs on the
   full League Table screen, one click away: squeezed into this column it
   left the club NAME - the only thing the preview exists to identify - with
   too little space. Two numeric columns leave the compact view readable. */
.league-mini-row {
  display: grid;
  grid-template-columns: 16px minmax(0, 1fr) repeat(2, 28px);
  align-items: center;
  gap: 6px;
  min-height: 28px;
  padding: var(--dash-space-1);
  line-height: 1.2;
  font-size: var(--fs-meta);
  color: var(--ink-dim);
}
.league-mini-row > span:not(.league-mini-name) { text-align: right; font-variant-numeric: tabular-nums; }
.league-mini-pos { color: var(--ink-faint); font-weight: 700; text-align: left !important; }
.league-mini-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: .94em; }
.league-mini-pts { font-weight: 800; color: var(--ink); }
.league-mini-head {
  min-height: 22px;
  padding-bottom: var(--dash-space-1);
  font-size: .72em;
  font-weight: 800;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-faint);
  border-bottom: 1px solid var(--dash-line);
}
.league-mini-row.me {
  color: var(--green-deep);
  font-weight: 800;
  background: #F3E6C6;
  border-radius: 6px;
}
.league-mini-row.me .league-mini-pos { color: var(--green-deep); }
.league-mini-row.me .league-mini-pts { color: var(--green-deep); }

/* Club Mood + Trust: matched compact rows, aligned to the same 4px inset the
   navigation rows above and below them use. The label column used to reserve
   a fixed 96px minimum and the row carried a 12px inset on both sides, which
   between them spent roughly half the available width on nothing - and the
   bar then had to say everything on its own. Label, bar and a BROAD state
   word now share the line: the word carries the meaning, the bar carries the
   sense of movement, and neither exposes a raw score (the precise value
   stays on the bar's own tooltip). */
.club-status-section { display: flex; flex-direction: column; gap: var(--dash-space-1); margin-top: 0; }
.status-row {
  display: grid;
  grid-template-columns: 78px minmax(0, 1fr) 64px;
  align-items: center;
  gap: var(--dash-space-2);
  width: 100%;
  min-height: 32px;
  padding: var(--dash-space-1);
  background: none;
  border: 0;
  border-radius: 8px;
  text-align: left;
  cursor: pointer;
  transition: background .14s ease;
}
.status-row:hover { background: rgba(255, 253, 244, .55); }
.status-row:focus-visible { outline: 2px solid var(--dash-button-border-strong); outline-offset: -2px; }
.status-label {
  font-size: .95em;
  font-weight: 700;
  color: var(--green-deep);
  white-space: nowrap;
}
.status-value {
  font-size: .9em;
  font-weight: 700;
  color: var(--ink-dim);
  white-space: nowrap;
  min-width: 0;
  justify-self: stretch;
  text-align: right;
}
.status-value.status-tone-positive { color: #2F6E3F; }
.status-value.status-tone-caution { color: #8C6318; }
.status-value.status-tone-negative { color: #A34E3D; }
.status-value.status-tone-gold { color: var(--gold-deep); }
.status-scale {
  display: flex;
  align-items: center;
  gap: 7px;
  min-width: 0;
}
.status-scale .status-bar { flex: 1 1 auto; }
.status-bar {
  height: 7px;
  border: 1px solid var(--dash-line);
  background: rgba(84, 105, 88, .13);
  border-radius: 3px;
  overflow: hidden;
}
.status-fill {
  display: block;
  height: 100%;
  width: 0;
  border-radius: 3px;
  background: var(--green-btn);
  transition: width .5s ease-out;
}
.status-fill.status-tone-positive { background: var(--ok); }
.status-fill.status-tone-caution { background: var(--warn); }
.status-fill.status-tone-negative { background: var(--bad); }
.status-fill.status-tone-gold { background: var(--gold-deep); }
.status-row:hover .status-fill { filter: brightness(1.06); }
.status-detail { margin: 2px 0 4px; }
.status-detail[hidden] { display: none; }

.dash-actions {
  flex: 0 0 auto;
  margin-top: 0;
  padding-top: var(--dash-space-2);
  gap: var(--dash-space-2);
}
/* Layout and colour only: geometry, type, depth and motion are shared directly
   with .btn-continue in layout.css so the two button families cannot drift. */
.dashboard-scroll .dashboard-action {
  box-sizing: border-box;
  width: 100%;
  height: 36px;
  margin: 0;
  justify-content: center;
  border-color: var(--dash-button-border);
  background: var(--dash-button-fill);
  color: var(--cream);
}
.dashboard-action:hover { border-color: var(--dash-button-border-strong); }
.dashboard-scroll .dashboard-action.is-sidecar-active {
  background: #F3E6C6;
  border-color: rgba(46, 86, 56, .35);
  color: var(--green-deep);
  box-shadow: inset 0 0 0 1px rgba(46, 86, 56, .08);
}
.dashboard-scroll .dashboard-action.is-sidecar-active:hover {
  border-color: rgba(46, 86, 56, .52);
  background: #F6EBCF;
}
.dashboard-action:focus-visible { border-color: var(--dash-button-border-strong); outline: 2px solid rgba(79, 104, 83, .34); outline-offset: 2px; }
.dashboard-action .dash-nav-label,
.dashboard-action .dash-nav-value { color: inherit; }
.dashboard-action .dash-nav-label { font-size: inherit; font-weight: inherit; letter-spacing: inherit; }
.dashboard-action .dash-nav-value { margin-left: auto; font-size: .92em; letter-spacing: 0; opacity: .78; }

/* ============================================================
   Stage 1 studio presentation layer
   Presentation only: existing layout, widths and DOM contracts remain owned
   by the rules above and by layout.css.
   ============================================================ */

/* ---------- shared surfaces and interface language ---------- */
.panel {
  background: var(--panel-light);
  border-color: var(--line-subtle);
  box-shadow: 0 18px 44px rgba(0, 0, 0, .22);
}
.col .panel {
  background: var(--panel-dash);
  border-color: var(--line-subtle);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
}
.panel h2,
.panel h3,
.panel-title-row h2,
.rail-head h2,
.rail-section-label,
.scene-panel-eyebrow,
.sidecar-label,
.match-eyebrow,
.today-heading,
.league-mini-head,
.table-row.head,
.fixture-section-label {
  color: var(--cream-muted);
  font-family: var(--font-mono);
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
}
.panel h2 .section-icon,
.panel-h2-go,
.dash-nav-row .panel-h2-go {
  color: var(--purple-bright);
}
.panel-btn,
.btn-gold {
  background: var(--purple);
  border: 1px solid var(--line-strong);
  color: var(--cream);
  box-shadow: 0 8px 22px rgba(53, 18, 110, .28);
}
.panel-btn:hover,
.btn-gold:hover {
  background: var(--purple-bright);
  filter: none;
}
.panel-btn.quiet,
.btn-quiet {
  background: rgba(245, 243, 239, .035);
  border-color: var(--line);
  color: var(--cream-muted);
}
.panel-btn.quiet:hover,
.btn-quiet:hover {
  background: rgba(122, 76, 242, .13);
  border-color: var(--line-strong);
  color: var(--cream);
}
.inline-name-link {
  color: var(--cream);
  text-decoration-color: var(--purple-bright);
}
.inline-name-link:hover { color: var(--cream-muted); }

/* ---------- permanent header components ---------- */
.hud-divider {
  background: linear-gradient(180deg, transparent, var(--line) 20%, var(--line) 80%, transparent);
}
.hud-date-main {
  color: var(--cream);
  font-family: var(--font-sans);
}
.hud-date #status-time {
  background: rgba(122, 76, 242, .2);
  color: var(--cream);
  font-family: var(--font-mono);
}
.hud-date #status-season,
.hud-date-sep { color: var(--cream-muted); }
.hud-cal-track { gap: 14px; }
.hud-cal-day {
  color: var(--cream-muted);
  border-color: transparent;
  background: transparent;
}
.hud-cal-day:hover {
  background: rgba(245, 243, 239, .045);
  color: var(--cream);
}
.hud-cal-wd {
  color: var(--cream-muted);
  font-family: var(--font-mono);
  letter-spacing: .1em;
}
.hud-cal-dm,
.hud-cal-daynum,
.hud-cal-month { color: inherit; }
.hud-cal-day.is-active {
  background: var(--purple-deep);
  border-color: var(--line-strong);
  box-shadow: 0 0 26px rgba(122, 76, 242, .22);
}
.hud-cal-day.is-active .hud-cal-wd { color: var(--cream-muted); }
.hud-cal-day.is-active .hud-cal-dm,
.hud-cal-day.is-active .hud-cal-daynum { color: var(--cream); }
.hud-cal-seg i.done { background: rgba(245, 243, 239, .42); }
.hud-cal-seg i.future { background: rgba(245, 243, 239, .14); }
.hud-cal-seg i.cur { background: var(--purple-bright); }
.hud-cal-day.has-match:not(.is-active) {
  background: rgba(122, 76, 242, .1);
  border-color: var(--line);
}
.hud-icon-btn {
  color: var(--cream-muted);
  border: 1px solid transparent;
}
.hud-icon-btn:hover,
.hud-icon-btn.active {
  background: rgba(122, 76, 242, .16);
  border-color: var(--line);
  color: var(--cream);
}
.hud-icon-btn:focus-visible { outline-color: var(--purple-bright); }
.btn-continue,
.dashboard-scroll .dashboard-action {
  background: var(--purple);
  border-color: var(--line-strong);
  color: var(--cream);
  box-shadow: 0 8px 24px rgba(53, 18, 110, .28);
}
.btn-continue:hover,
.dashboard-scroll .dashboard-action:hover {
  background: var(--purple-bright);
  border-color: rgba(245, 243, 239, .28);
  filter: none;
}

/* ---------- left conversation rail ---------- */
.people-rail {
  background:
    linear-gradient(180deg, rgba(122, 76, 242, .055), transparent 32%),
    var(--panel-contact);
  border-color: var(--line-subtle);
  box-shadow: 0 18px 44px rgba(0, 0, 0, .2);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
}
.rail-head {
  border-bottom-color: var(--line-subtle);
  background: rgba(5, 5, 6, .32);
}
.rail-head h2,
.rail-section-label { color: var(--cream-muted); }
.rail-unread-summary {
  background: var(--purple-deep);
  border-color: var(--line);
  color: var(--cream);
}
.rail-list .char-card,
.rail-list .rail-group-card {
  color: var(--cream-muted);
  border-color: transparent;
  background: transparent;
  box-shadow: none;
}
.rail-list .char-card + .char-card,
.rail-list .rail-group-card + .rail-group-card,
.rail-section + .rail-section {
  border-top-color: var(--line-subtle);
}
.rail-list .char-card .nm,
.rail-group-card .nm { color: var(--cream); }
.rail-list .char-card .rl,
.rail-group-card .rl,
.rail-list .char-card .md { color: var(--cream-muted); }
.rail-list .char-card:hover,
.rail-list .rail-group-card:hover,
.rail-list .char-card:active,
.rail-list .rail-group-card:active {
  background: rgba(245, 243, 239, .04);
  color: var(--cream);
}
.rail-list .char-card.selected,
.rail-list .rail-group-card.selected {
  background:
    linear-gradient(90deg, rgba(122, 76, 242, .25), rgba(122, 76, 242, .075));
  border-color: var(--line);
  box-shadow: inset 2px 0 0 var(--purple-bright);
}
.rail-list .char-card.selected .nm,
.rail-list .rail-group-card.selected .nm { color: var(--cream); }
.rail-list .char-card.has-unread:not(.selected),
.rail-list .rail-group-card.has-unread:not(.selected) {
  background: rgba(122, 76, 242, .09);
}
.portrait-frame {
  background: var(--black-raised);
  border-color: rgba(245, 243, 239, .2);
  box-shadow: 0 0 0 1px rgba(5, 5, 6, .7);
}
.portrait-frame.is-initial {
  background: var(--purple-deep);
  color: var(--cream);
  font-family: var(--font-sans);
}
.rail-list .char-card:hover .portrait-frame,
.rail-list .char-card.selected .portrait-frame {
  border-color: var(--purple-bright);
  box-shadow: 0 0 18px rgba(122, 76, 242, .2);
}
.online-dot,
.presence-dot.online { background: var(--ok); }
.rail-group-mark {
  background: var(--purple-deep);
  border-color: var(--line);
  color: var(--cream-muted);
}
.rail-add-group-row {
  color: var(--cream-muted);
  border-color: var(--line-subtle);
}
.rail-add-mark {
  border-color: var(--line);
  color: var(--purple-bright);
}
.rail-add-group-row:hover {
  background: rgba(122, 76, 242, .1);
  border-color: var(--line);
  color: var(--cream);
}
.rail-add-group-row:hover .rail-add-mark {
  border-color: var(--purple-bright);
  color: var(--cream);
}

/* ---------- Messenger shell, header and integrated views ---------- */
.messenger {
  background:
    linear-gradient(145deg, rgba(122, 76, 242, .055), transparent 34%),
    rgba(5, 5, 6, .72);
  border-color: var(--line-subtle);
  box-shadow: 0 22px 54px rgba(0, 0, 0, .28);
}
.convo {
  color: var(--cream);
  background: rgba(5, 5, 6, .28);
}
.convo-head {
  background:
    linear-gradient(90deg, rgba(122, 76, 242, .105), transparent 42%),
    rgba(16, 14, 20, .94);
  border-bottom-color: var(--line-subtle);
}
.convo-head .name {
  color: var(--cream);
  font-family: var(--font-sans);
}
.convo-head .role,
.convo-role,
.convo-role::after,
.convo-head .presence-label { color: var(--cream-muted); }
.group-faces-more {
  background: var(--purple-deep);
  border-color: var(--line);
  color: var(--cream-muted);
}
.convo-chip-btn,
.convo-mute-btn {
  background: rgba(245, 243, 239, .035);
  border-color: var(--line);
  color: var(--cream-muted);
}
.convo-chip-btn:hover,
.convo-mute-btn:hover,
.convo-mute-btn.is-muted {
  background: rgba(122, 76, 242, .15);
  border-color: var(--line-strong);
  color: var(--cream);
}
.convo-rel .lbl,
.convo-rel .lbl strong { color: var(--cream-muted); }
.convo-rel .rel-bar {
  background: rgba(245, 243, 239, .09);
  border-color: var(--line-subtle);
}
.convo-rel .rel-bar span { background: var(--purple-bright); }
.convo-thread {
  color: var(--cream);
  background:
    linear-gradient(rgba(245, 243, 239, .018) 1px, transparent 1px),
    linear-gradient(90deg, rgba(245, 243, 239, .018) 1px, transparent 1px),
    rgba(10, 9, 13, .52);
  background-size: 32px 32px;
  scrollbar-color: rgba(245, 243, 239, .22) transparent;
  transition: opacity .28s ease, filter .28s ease;
}
.messenger-body.sidecar-active .convo-thread {
  opacity: .88;
}
.bubble {
  border: 1px solid rgba(245, 243, 239, .1);
  box-shadow: 0 10px 26px rgba(0, 0, 0, .18);
}
.bubble.them[data-speaker] {
  --speaker-bg: var(--purple-deep);
  --speaker-label: rgba(245, 243, 239, .68);
  background:
    linear-gradient(145deg, rgba(122, 76, 242, .2), transparent 54%),
    var(--speaker-bg);
  color: var(--cream);
}
.bubble.them.typing:not([data-speaker]) {
  background: var(--purple-deep);
  color: var(--cream);
}
.bubble.me {
  background:
    linear-gradient(145deg, rgba(245, 243, 239, .08), transparent 52%),
    var(--purple);
  border-color: rgba(245, 243, 239, .15);
  color: var(--cream);
}
.bubble-speaker {
  color: var(--speaker-label, var(--cream-muted));
  font-family: var(--font-mono);
  letter-spacing: .08em;
  text-transform: uppercase;
}
.bubble .meta { color: rgba(245, 243, 239, .54); }
.bubble.sys {
  background: rgba(245, 243, 239, .045);
  border-color: var(--line-subtle);
  color: var(--cream-muted);
  box-shadow: none;
}
.bubble.sys .bubble-text { color: inherit; }
.thread-divider { color: rgba(245, 243, 239, .46); }
.thread-divider::before,
.thread-divider::after { background: var(--line-subtle); }
.thread-divider span {
  background: var(--black);
  color: var(--cream-muted);
  font-family: var(--font-mono);
}
.convo-empty { color: var(--cream-muted); }
.convo-empty-icon { color: var(--purple-bright); }
.msg-item,
.group-list-row,
.new-chat-row,
.new-group-member {
  color: var(--cream-muted);
  border-color: var(--line-subtle);
}
.msg-item:hover,
.group-list-row:hover,
.new-chat-row:hover,
.new-group-member:hover {
  background: rgba(122, 76, 242, .1);
}
.msg-item .name,
.group-list-row .name,
.new-chat-row .name,
.new-group-member .name { color: var(--cream); }

/* ---------- composer and contextual actions ---------- */
#compose-wrap {
  background: rgba(16, 14, 20, .96);
  border-top-color: var(--line-subtle);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
}
#compose-wrap:focus-within,
.messenger-body.sidecar-active #compose-wrap,
.messenger-body.sidecar-active #compose-wrap:focus-within {
  background:
    linear-gradient(90deg, rgba(122, 76, 242, .08), transparent 54%),
    rgba(16, 14, 20, .98);
}
.compose { border-top-color: var(--line-subtle); }
.compose textarea {
  background: var(--black-raised);
  border-color: var(--line);
  color: var(--cream);
  caret-color: var(--cream);
}
.compose textarea::placeholder { color: rgba(245, 243, 239, .46); }
.compose textarea:focus {
  border-color: var(--purple-bright);
  box-shadow: 0 0 0 2px rgba(122, 76, 242, .16), 0 0 22px rgba(122, 76, 242, .09);
}
.btn-send {
  background: rgba(245, 243, 239, .035);
  border-color: var(--line);
  color: var(--cream-muted);
}
.btn-send:not(.is-ready):hover {
  background: rgba(122, 76, 242, .11);
  border-color: var(--line-strong);
  color: var(--cream);
}
.btn-send.is-ready {
  background: var(--purple);
  border-color: var(--line-strong);
  color: var(--cream);
  box-shadow: 0 0 22px rgba(122, 76, 242, .2);
}
.compose-hint { color: var(--cream-muted); }
.meetup-prompt-row { border-top-color: var(--line-subtle); }
.meetup-quick-action,
.activity-card.is-meetup-prompt {
  background: rgba(122, 76, 242, .1);
  border-color: var(--line);
  color: var(--cream);
  box-shadow: none;
}
.meetup-quick-action:hover,
.activity-card.is-meetup-prompt:hover {
  background: rgba(122, 76, 242, .18);
  border-color: var(--purple-bright);
  color: var(--cream);
  box-shadow: 0 8px 22px rgba(0, 0, 0, .2);
}
.activity-card.is-meetup-prompt .activity-title { color: var(--cream); }
.activity-card.is-meetup-prompt .activity-detail,
.meetup-quick-action.leave { color: var(--cream-muted); }
.meetup-quick-action.leave:hover { color: var(--cream); }
.scene-notice,
.scene-schedule {
  background: rgba(122, 76, 242, .1);
  border-color: var(--line-subtle);
  color: var(--cream-muted);
}
.scene-notice strong,
.scene-schedule strong { color: var(--cream); }

/* ---------- fixed right dashboard ---------- */
.dashboard-panel {
  --dash-line: var(--line-subtle);
  --dash-button-border: var(--line);
  --dash-button-border-strong: var(--line-strong);
  --dash-button-fill: var(--purple);
  background:
    linear-gradient(180deg, rgba(122, 76, 242, .065), transparent 36%),
    var(--panel-dash);
}
.dashboard-panel > .dashboard-head {
  border-bottom: 1px solid var(--line-subtle);
}
.dashboard-panel > .dashboard-head h2 {
  color: var(--cream);
  font-family: var(--font-sans);
  font-size: 1.12em;
  letter-spacing: -.01em;
  text-transform: none;
}
.weather-strip .weather-icon,
.weather-temp { color: var(--cream-muted); }
.dashboard-scroll { color: var(--cream-muted); }
.dashboard-scroll .panel {
  background: transparent;
  border-color: transparent;
}
.match-eyebrow { color: var(--cream-muted); }
.match-eyebrow:hover { color: var(--cream); }
.dashboard-scroll .match-opp,
.dashboard-scroll .match-opp-name,
.dash-nav-label,
.status-label,
.today-row-value { color: var(--cream); }
.dashboard-scroll .match-side,
.dash-nav-value,
.today-row-label,
.status-value { color: var(--cream-muted); }
.league-mini-row {
  color: var(--cream-muted);
  border-bottom-color: var(--line-subtle);
}
.league-mini-head { color: rgba(245, 243, 239, .48); }
.league-mini-pts { color: var(--cream); }
.league-mini-row.me {
  background: rgba(122, 76, 242, .17);
  color: var(--cream);
}
.league-mini-row.me .league-mini-pos,
.league-mini-row.me .league-mini-pts { color: var(--cream); }
.status-row,
.dash-nav-row {
  color: var(--cream-muted);
}
.status-row:hover,
.dash-nav-row:hover,
.dash-nav-row:active {
  background: rgba(245, 243, 239, .04);
  color: var(--cream);
}
.status-bar {
  background: rgba(245, 243, 239, .075);
  border-color: var(--line);
}
.status-fill { background: var(--purple-bright); }
.status-fill.status-tone-positive { background: var(--ok); }
.status-fill.status-tone-caution { background: var(--warn); }
.status-fill.status-tone-negative { background: var(--bad); }
.status-value.status-tone-positive { color: var(--ok); }
.status-value.status-tone-caution { color: var(--warn); }
.status-value.status-tone-negative { color: var(--bad); }
.dashboard-scroll .dashboard-action.is-sidecar-active,
.dashboard-scroll .dashboard-action.is-sidecar-active:hover {
  background: var(--cream);
  border-color: var(--cream);
  color: var(--purple-deep);
  box-shadow: 0 0 24px rgba(122, 76, 242, .18);
}
.today-section { border-top-color: var(--line); }

/* ---------- Messenger context sidecar ----------
   Width, flex-basis and transition mechanics intentionally stay above. */
.scene-panel {
  color: var(--cream);
  background:
    linear-gradient(145deg, rgba(122, 76, 242, .12), transparent 46%),
    var(--panel-dash);
  border-left-color: transparent;
  box-shadow: none;
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
}
.messenger-body.sidecar-active .scene-panel {
  border-left-color: var(--line);
  box-shadow: -18px 0 42px rgba(0, 0, 0, .28);
}
.messenger-body.sidecar-active .convo-head {
  background:
    linear-gradient(90deg, rgba(122, 76, 242, .1), transparent 54%),
    rgba(16, 14, 20, .96);
}
.messenger-body.sidecar-active #compose-wrap,
.messenger-body.sidecar-active #compose-wrap:focus-within {
  background:
    linear-gradient(90deg, rgba(122, 76, 242, .075), transparent),
    rgba(16, 14, 20, .98);
}
.sidecar-shell {
  background: transparent;
  color: var(--cream);
}
.scene-panel-head {
  background: rgba(5, 5, 6, .28);
  border-bottom-color: var(--line-subtle);
}
.scene-panel-head h3 {
  color: var(--cream);
  font-family: var(--font-sans);
}
.scene-panel-head p,
.sidecar-intro { color: var(--cream-muted); }
.sidecar-body { scrollbar-color: rgba(245, 243, 239, .2) transparent; }
.sidecar-section {
  border-color: var(--line-subtle);
  background: rgba(245, 243, 239, .022);
}
.sidecar-footer {
  background: rgba(5, 5, 6, .36);
  border-top-color: var(--line-subtle);
}
.sidecar-close {
  background: rgba(245, 243, 239, .035);
  border-color: var(--line);
  color: var(--cream-muted);
}
.sidecar-close:hover {
  background: rgba(122, 76, 242, .16);
  border-color: var(--line-strong);
  color: var(--cream);
}
.sidecar-close:focus-visible { outline-color: var(--purple-bright); }

/* ---------- shared modals and management surfaces ---------- */
.overlay {
  background: rgba(5, 5, 6, .78);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}
.modal,
.modal.profile-modal {
  color: var(--cream);
  background:
    linear-gradient(145deg, rgba(122, 76, 242, .09), transparent 34%),
    var(--black-raised);
  border-color: var(--line);
  box-shadow: 0 0 0 1px rgba(245, 243, 239, .025), 0 28px 74px rgba(0, 0, 0, .62);
}
.modal h3 {
  color: var(--cream);
  font-family: var(--font-sans);
  letter-spacing: -.02em;
}
.modal .sub {
  color: var(--cream-muted);
  border-bottom-color: var(--line);
}
.modal-close {
  background: rgba(245, 243, 239, .035);
  border-color: var(--line);
  color: var(--cream-muted);
}
.modal-close:hover {
  background: rgba(122, 76, 242, .16);
  border-color: var(--line-strong);
  color: var(--cream);
}
.modal-close:focus-visible { box-shadow: 0 0 0 2px var(--purple-bright); }
.modal-actions { border-top-color: var(--line-subtle); }
.modal input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
.modal textarea,
.modal select {
  background: var(--black);
  border-color: var(--line);
  color: var(--cream);
}
.modal input::placeholder,
.modal textarea::placeholder { color: rgba(245, 243, 239, .4); }
.modal input:focus,
.modal textarea:focus,
.modal select:focus {
  outline-color: var(--purple-bright);
  border-color: var(--purple-bright);
}
.save-slot,
.save-slot.is-empty,
.view-card,
.fixture-row,
.schedule-row,
.log-row,
.t-card,
.p-section,
.profile-hero,
.club-life-card,
.care-card,
.scout-card {
  background: rgba(245, 243, 239, .025);
  border-color: var(--line-subtle);
  color: var(--cream-muted);
  box-shadow: none;
}
.save-slot-main strong,
.view-card strong,
.fixture-row strong,
.schedule-row strong,
.profile-hero h4,
.p-section h4 { color: var(--cream); }
.save-slot-main .d,
.save-slot-meta,
.save-slot-time,
.slot-empty-tag,
.save-empty-state { color: var(--cream-muted); }
.save-empty-state { border-color: var(--line); }
.save-success { color: var(--ok); }

/* ---------- opening menu and manager-avatar flow ----------
   These selectors outrank the runtime-injected legacy style blocks without
   changing their markup or JavaScript. */
body .pb-opening {
  color: var(--cream);
  background:
    radial-gradient(circle at 50% 24%, rgba(53, 18, 110, .34), transparent 46%),
    linear-gradient(rgba(5, 5, 6, .94), rgba(5, 5, 6, .98));
  font-family: var(--font-sans);
}
.pb-opening .pb-opening-panel {
  color: var(--cream);
  background:
    linear-gradient(145deg, rgba(122, 76, 242, .1), transparent 42%),
    var(--black-raised);
  border-color: var(--line);
  box-shadow: 0 30px 84px rgba(0, 0, 0, .68);
}
.pb-opening .pb-opening-crest {
  filter: drop-shadow(0 0 24px rgba(122, 76, 242, .28));
}
.pb-opening .pb-opening-title {
  color: var(--cream);
  font-family: var(--font-sans);
  letter-spacing: .08em;
}
.pb-opening .pb-opening-sub { color: var(--cream-muted); }
.pb-opening .pb-btn {
  font-family: var(--font-mono);
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
}
.pb-opening .pb-btn-gold {
  background: var(--purple);
  border-color: var(--line-strong);
  color: var(--cream);
}
.pb-opening .pb-btn-gold:hover { background: var(--purple-bright); }
.pb-opening .pb-btn-quiet {
  background: transparent;
  border-color: var(--line);
  color: var(--cream-muted);
}
.pb-opening .pb-btn-quiet:hover {
  background: rgba(122, 76, 242, .13);
  color: var(--cream);
}
.pb-opening .pb-field > label,
.pb-opening .pb-slot-main strong {
  color: var(--cream);
  font-family: var(--font-mono);
  letter-spacing: .06em;
  text-transform: uppercase;
}
.pb-opening .pb-field input,
.pb-opening .pb-choice-btn,
.pb-opening .pb-slot,
.pb-opening .pb-slot.is-empty {
  background: var(--black);
  border-color: var(--line);
  color: var(--cream);
}
.pb-opening .pb-field input:focus,
.pb-opening .pb-choice-btn:focus-visible {
  outline-color: var(--purple-bright);
}
.pb-opening .pb-choice-btn:hover { background: rgba(122, 76, 242, .12); }
.pb-opening .pb-choice-btn.is-active {
  background: var(--purple);
  border-color: var(--line-strong);
  color: var(--cream);
}
.pb-opening .pb-load-empty {
  border-color: var(--line);
  color: var(--cream-muted);
}
.pb-opening .pb-slot-tag,
.pb-opening .pb-slot-meta,
.pb-opening .pb-slot-time { color: var(--cream-muted); }
.pb-opening .pb-avatar-preview,
.pb-opening .pb-avatar-chip {
  background-color: var(--purple-deep);
  border-color: var(--line);
  color: var(--cream);
}
.pb-opening .pb-avatar-chip.is-active {
  border-color: var(--purple-bright);
  box-shadow: 0 0 0 2px rgba(122, 76, 242, .22);
}

body .pb-cropper-overlay {
  color: var(--cream);
  background: rgba(5, 5, 6, .82);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}
.pb-cropper-overlay .pb-cropper-panel {
  color: var(--cream);
  background:
    linear-gradient(145deg, rgba(122, 76, 242, .1), transparent 44%),
    var(--black-raised);
  border-color: var(--line);
  box-shadow: 0 30px 84px rgba(0, 0, 0, .68);
}
.pb-cropper-overlay .pb-cropper-title {
  color: var(--cream);
  font-family: var(--font-sans);
}
.pb-cropper-overlay .pb-cropper-stage {
  background:
    linear-gradient(45deg, rgba(245, 243, 239, .035) 25%, transparent 25% 75%, rgba(245, 243, 239, .035) 75%),
    linear-gradient(45deg, rgba(245, 243, 239, .035) 25%, var(--black) 25% 75%, rgba(245, 243, 239, .035) 75%);
  background-position: 0 0, 10px 10px;
  background-size: 20px 20px;
}
.pb-cropper-overlay .pb-cropper-mask {
  border-color: rgba(245, 243, 239, .72);
  box-shadow: 0 0 0 2000px rgba(5, 5, 6, .66);
}
.pb-cropper-overlay .pb-cropper-controls label,
.pb-cropper-overlay .pb-cropper-loading,
.pb-cropper-overlay .pb-cropper-hint { color: var(--cream-muted); }
.pb-cropper-overlay .pb-cropper-zoom { background: var(--line); }
.pb-cropper-overlay .pb-cropper-zoom::-webkit-slider-thumb {
  background: var(--purple-bright);
  border-color: var(--cream);
  box-shadow: 0 0 14px rgba(122, 76, 242, .26);
}
.pb-cropper-overlay .pb-cropper-zoom::-moz-range-thumb {
  background: var(--purple-bright);
  border-color: var(--cream);
  box-shadow: 0 0 14px rgba(122, 76, 242, .26);
}
.pb-cropper-overlay .pb-cropper-preview {
  background-color: var(--purple-deep);
  border-color: var(--line);
  box-shadow: 0 0 18px rgba(122, 76, 242, .18);
}

/* ---------- development-only surfaces ---------- */
.meetup-dev-intro,
.dev-seasonal-current,
.dev-romance-unavailable { color: var(--cream-muted); }
.meetup-dev-form,
.dev-relationship-control,
.dev-seasonal-preview {
  color: var(--cream-muted);
  background: rgba(245, 243, 239, .02);
  border-color: var(--line-subtle);
}
.meetup-dev-field,
.meetup-dev-participants,
.meetup-dev-manager,
.meetup-dev-note {
  color: var(--cream-muted);
  border-color: var(--line-subtle);
}
.meetup-dev-field > span,
.meetup-dev-participants legend,
.dev-relationship-control label,
.dev-seasonal-grid label {
  color: var(--cream);
  font-family: var(--font-mono);
  letter-spacing: .06em;
  text-transform: uppercase;
}
.meetup-dev-participant {
  background: rgba(245, 243, 239, .025);
  border-color: var(--line-subtle);
  color: var(--cream-muted);
}
.meetup-dev-participant:hover {
  background: rgba(122, 76, 242, .1);
  color: var(--cream);
}
.meetup-dev-note code { color: var(--purple-bright); }
