/*
 * Plain CSS component styles. Tailwind utilities come from app/assets/builds/tailwind.css.
 * Custom color values reference CSS variables defined by the Tailwind theme.
 */

/* ── Self-hosted fonts (ad-hoc 2026-04-11) ──
 *
 * Replaces the previous <link> imports from fonts.googleapis.com. Fonts
 * live in app/assets/fonts/ and are served through Propshaft; the
 * url("…") paths get digest-rewritten at build time. Material Symbols
 * is fetched from Google Fonts via the `icon_names=` parameter as a
 * pre-subsetted variable font (65KB vs. 3.8MB for the full axis).
 *
 * When adding a new Material Symbols glyph, re-fetch the subsetted
 * font with:
 *
 *   bin/icons --fetch
 *
 * The script scans ERB templates, JS controllers (innerHTML, textContent,
 * glyph variables), the OWM weather icon map, and StarPaw ACTION_DEFS.
 * Run `bin/icons` (without --fetch) to just list the detected icons.
 */
@font-face {
  font-family: 'Outfit';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/outfit-latin-400-9c6ecee0.woff2") format('woff2');
}
@font-face {
  font-family: 'Outfit';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/assets/outfit-latin-500-9c6ecee0.woff2") format('woff2');
}
@font-face {
  font-family: 'Outfit';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/assets/outfit-latin-600-9c6ecee0.woff2") format('woff2');
}
@font-face {
  font-family: 'Outfit';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/assets/outfit-latin-700-9c6ecee0.woff2") format('woff2');
}
@font-face {
  font-family: 'Outfit';
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url("/assets/outfit-latin-800-9c6ecee0.woff2") format('woff2');
}
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/dm-sans-latin-400-c3a50c50.woff2") format('woff2');
}
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/assets/dm-sans-latin-500-c3a50c50.woff2") format('woff2');
}
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/assets/dm-sans-latin-600-c3a50c50.woff2") format('woff2');
}
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/assets/dm-sans-latin-700-c3a50c50.woff2") format('woff2');
}
@font-face {
  font-family: 'DM Sans';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/dm-sans-latin-400-italic-dbade5b5.woff2") format('woff2');
}
@font-face {
  font-family: 'Material Symbols Outlined';
  font-style: normal;
  font-weight: 400;
  font-display: block;
  src: url("/assets/material-symbols-outlined-subset-4d3a0cdb.woff2") format('woff2');
}

/* ── Base ── */
html {
  height: 100%;
  overflow: hidden;
}
body {
  background: var(--color-canvas);
  font-family: var(--font-body);
  -webkit-font-smoothing: antialiased;
  height: 100%;
  color: var(--color-ink);
  overflow-x: hidden;
  overflow-y: auto;
  overscroll-behavior: none;
}

/* Disable native image/link drag and iOS touch-callout previews */
img, a {
  -webkit-user-drag: none;
  user-drag: none;
  -webkit-touch-callout: none;
}

/* ── Material Symbols ──
 *
 * Plan 196: the whole rule lives in @layer base so Tailwind utilities can
 * override it. Unlayered author CSS beats ALL layered CSS regardless of
 * specificity — with this rule unlayered, its font-size: 24px silently
 * defeated every text-[..px] utility on an icon app-wide (utilities live in
 * the built tailwind.css's @layer utilities). Layer names are document-wide,
 * so this `base` merges before the build's `utilities` and text-[..px] /
 * align-[..] / block etc. now genuinely apply to icons. Unlayered rules
 * elsewhere in this file (.starpaw-* icon sizes, .filled-icon, …) still win
 * over both — unchanged behavior. */
@layer base {
  .material-symbols-outlined {
    font-family: 'Material Symbols Outlined';
    font-weight: normal;
    font-style: normal;
    font-size: 24px;
    line-height: 1;
    letter-spacing: normal;
    text-transform: none;
    white-space: nowrap;
    word-wrap: normal;
    direction: ltr;
    -webkit-font-feature-settings: 'liga';
    font-feature-settings: 'liga';
    -webkit-font-smoothing: antialiased;
    font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
    display: inline-block;
    vertical-align: middle;
  }
}
.filled-icon {
  font-variation-settings: 'FILL' 1, 'wght' 400, 'GRAD' 0, 'opsz' 24;
}

/* ── Loading spinner ── */
.loading-spinner {
  width: 40px;
  height: 40px;
  border: 3px solid var(--color-brand-light, rgba(0,0,0,0.1));
  border-top-color: var(--color-brand);
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
}
@keyframes spin {
  to { transform: rotate(360deg); }
}

/* ── Scrollbar ── */
.hide-scrollbar::-webkit-scrollbar { display: none; }
.hide-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }

/* ── Glass surfaces ── */
.glass {
  background: var(--color-glass);
  backdrop-filter: blur(20px) saturate(1.4);
  -webkit-backdrop-filter: blur(20px) saturate(1.4);
  border: 1px solid var(--color-glass-subtle);
}
.glass-strong {
  background: var(--color-glass-strong);
  backdrop-filter: blur(24px) saturate(1.5);
  -webkit-backdrop-filter: blur(24px) saturate(1.5);
  border: 1px solid var(--color-glass-subtle);
}

/* ── Depth ── */
.depth-sm { box-shadow: 0 2px 8px rgba(26,26,46,0.04), 0 0 0 1px rgba(26,26,46,0.03); }
.depth-md { box-shadow: 0 4px 20px rgba(26,26,46,0.06), 0 0 0 1px rgba(26,26,46,0.03); }
.depth-lg { box-shadow: 0 8px 40px rgba(26,26,46,0.08), 0 0 0 1px rgba(26,26,46,0.02); }

/* ── Tactile button ── */
.tactile {
  transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
  cursor: pointer;
}
.tactile:active { transform: scale(0.94); }

/* ── Action button press darken ── */
.btn-press:active { filter: brightness(0.85); }

/* ── Icon pill ── */
.icon-pill {
  width: 2.25rem; height: 2.25rem;
  display: flex; align-items: center; justify-content: center;
  border-radius: 9999px;
  background: rgba(255, 107, 74, 0.08);
  color: var(--color-brand);
  border: none; cursor: pointer;
  transition: all 0.2s;
  flex-shrink: 0;
}
.icon-pill:hover { background: rgba(255, 107, 74, 0.15); transform: scale(1.06); }

/* ── Label ── */
.label {
  font-family: var(--font-display);
  font-size: 0.6875rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-ink-faint);
}

/* ── Note block ── */
.note-block {
  padding: 0.875rem 1rem;
  background: rgba(255, 107, 74, 0.04);
  border-radius: 0.875rem;
  font-size: 0.8125rem;
  line-height: 1.65;
  color: var(--color-ink-soft);
  font-style: italic;
  border-left: 3px solid var(--color-brand-soft);
}

/* ── Entrance animation ── */
@keyframes rise {
  from { opacity: 0; transform: translateY(24px); }
  to   { opacity: 1; transform: translateY(0); }
}
.animate-rise { animation: rise 0.6s cubic-bezier(0.16, 1, 0.3, 1) both; }

/* Woof pop: playful tap feedback on empty dog-card icons */
@keyframes woof-pop {
  0%   { opacity: 0; transform: translate(-50%, 0) scale(0.5); }
  20%  { opacity: 1; transform: translate(-50%, -6px) scale(1.1); }
  50%  { opacity: 1; transform: translate(-50%, -10px) scale(1); }
  100% { opacity: 0; transform: translate(-50%, -18px) scale(0.8); }
}
.woof-pop {
  position: absolute;
  bottom: 100%;
  left: 50%;
  pointer-events: none;
  font-family: 'Comic Sans MS', 'Chalkboard SE', cursive;
  font-size: 11px;
  font-weight: 700;
  color: var(--color-ink-faint);
  white-space: nowrap;
  animation: woof-pop 0.6s ease-out forwards;
}

/* Logistics: hide checkboxes on non-today dates */
.logistics-past .log-check { display: none; }

/* Upcoming badge: alarm icon on today's dog card icons */
.upcoming-badge {
  position: absolute;
  bottom: 1px;
  right: 1px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--color-canvas-warm);
  display: flex;
  align-items: center;
  justify-content: center;
}
.upcoming-badge .material-symbols-outlined {
  font-size: 12px !important;
  width: 12px;
  height: 12px;
  line-height: 1;
  color: var(--color-ink-faint);
  font-variation-settings: "FILL" 0, "wght" 500, "opsz" 20;
}
@keyframes frame-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}
.frame-fade-in { animation: frame-fade 100ms ease-out both; }

/* ── Month picker overlay ── */
.month-picker-backdrop { position: fixed; inset: 0; z-index: 50; background: transparent; opacity: 0; transition: opacity 0.15s ease; pointer-events: none; }
.month-picker-backdrop.open { opacity: 1; pointer-events: auto; }
.month-picker { position: fixed; z-index: 51; background: var(--color-glass-strong); border-radius: 16px; padding: 14px 16px 18px; box-shadow: 0 8px 24px rgba(0,0,0,0.18); transform: translateY(-8px); opacity: 0; transition: transform 0.2s ease, opacity 0.15s ease; pointer-events: none; overflow: hidden; }
.month-picker.open { transform: translateY(0); opacity: 1; pointer-events: auto; }
.month-picker-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.month-picker-title { font-family: var(--font-display); font-weight: 700; font-size: 18px; color: var(--color-ink); }
.month-picker-nav { background: none; border: none; padding: 6px; color: var(--color-ink-faint); cursor: pointer; -webkit-tap-highlight-color: transparent; touch-action: manipulation; }
.month-grid { display: grid; grid-template-columns: repeat(7, 1fr); grid-template-rows: auto repeat(6, 72px); gap: 2px 0; }
.month-cell:not(:nth-child(7n)) { border-right: 0.5px solid rgba(0,0,0,0.06); }
.month-day-label { font-family: var(--font-display); font-weight: 600; font-size: 12px; color: var(--color-ink-faint); text-align: center; padding: 2px 0 6px; }
.month-cell { display: flex; flex-direction: column; align-items: center; justify-content: flex-start; gap: 2px; padding: 5px 2px; border-radius: 10px; cursor: pointer; -webkit-tap-highlight-color: transparent; touch-action: manipulation; box-shadow: inset 0 0 0 0.5px rgba(0,0,0,0.06); overflow: hidden; }
.month-cell-other { box-shadow: none; }
.month-cell:active { background: rgba(0,0,0,0.06); }
.month-cell-today { box-shadow: inset 0 0 0 1.5px var(--color-brand); }
.month-cell-selected { background: var(--color-brand); color: #fff; }
.month-cell-selected.month-cell-today { box-shadow: none; }
.month-cell-other { opacity: 0.25; }
.month-cell-num { font-family: var(--font-display); font-size: 16px; font-weight: 600; line-height: 1.2; }
.month-dots { display: flex; flex-direction: row; justify-content: center; gap: 3px; }
.month-dots-col { display: flex; flex-direction: column; align-items: center; gap: 0; }
.month-dot-icon { font-size: 12px; line-height: 1; }
.month-dot-count { font-family: var(--font-display); font-size: 9px; font-weight: 700; line-height: 1; text-align: right; font-variant-numeric: tabular-nums; justify-self: end; }
.month-dot-counts { display: flex; flex-direction: column; align-items: center; gap: 0; }
.month-dot-comma { font-size: 8px; color: var(--color-ink-faint); margin: 0 0.5px; }
.month-cell-selected .month-dot-count { color: rgba(255,255,255,0.8) !important; }
.month-cell-selected .month-dot-comma { color: rgba(255,255,255,0.5) !important; }
.month-dot-daycare { color: #f59e0b; }
.month-dot-boarding { color: #8b5cf6; }
.month-dot-charge { color: var(--color-mint); } /* Account month-jump picker (Plan 194): the money colour */
.month-cell-selected .month-dot { color: rgba(255,255,255,0.8) !important; }
.month-picker-spinner { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; pointer-events: none; }
.month-picker-spin-icon { font-size: 28px; color: var(--color-ink-faint); animation: spin 1s linear infinite; }

/* ── Date Range Picker ── */
.drp-backdrop { position: fixed; inset: 0; z-index: 50; background: transparent; opacity: 0; transition: opacity 0.15s ease; pointer-events: none; }
.drp-backdrop.open { opacity: 1; pointer-events: auto; }
.drp-card { position: fixed; z-index: 51; background: var(--color-glass-strong); -webkit-backdrop-filter: blur(16px) saturate(1.8); backdrop-filter: blur(16px) saturate(1.8); border-radius: 16px; padding: 12px 12px 14px; box-shadow: 0 8px 24px rgba(0,0,0,0.18); transform: translateY(-8px); opacity: 0; transition: transform 0.2s ease, opacity 0.15s ease; pointer-events: none; overflow: hidden; }
.drp-card.open { transform: translateY(0); opacity: 1; pointer-events: auto; }
.drp-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.drp-title { font-family: var(--font-display); font-weight: 700; font-size: 15px; color: var(--color-ink); }
.drp-nav { background: none; border: none; padding: 6px; color: var(--color-ink-faint); cursor: pointer; -webkit-tap-highlight-color: transparent; touch-action: manipulation; }
.drp-months { display: flex; gap: 8px; }
.drp-month { flex: 1; min-width: 0; }
.drp-month-label { font-family: var(--font-display); font-weight: 600; font-size: 12px; color: var(--color-ink-soft); text-align: center; margin-bottom: 4px; }
.drp-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 1px; }
.drp-day-label { font-family: var(--font-display); font-weight: 600; font-size: 10px; color: var(--color-ink-faint); text-align: center; padding: 2px 0 4px; }
.drp-cell { display: flex; align-items: center; justify-content: center; aspect-ratio: 1; border-radius: 8px; cursor: pointer; -webkit-tap-highlight-color: transparent; touch-action: manipulation; border: none; background: none; padding: 0; }
.drp-cell:active { background: rgba(0,0,0,0.06); }
.drp-cell-num { font-family: var(--font-display); font-size: 13px; font-weight: 600; line-height: 1; color: var(--color-ink); }
.drp-cell-other { opacity: 0.2; }
.drp-cell-past { opacity: 0.25; pointer-events: none; }
.drp-cell-today { box-shadow: inset 0 0 0 1.5px var(--color-brand); }
.drp-cell-endpoint { background: var(--color-brand); border-radius: 8px; }
.drp-cell-endpoint .drp-cell-num { color: #fff; }
.drp-cell-between { background: rgba(255, 107, 74, 0.12); border-radius: 0; }
.drp-cell-single { border-radius: 8px; }
.drp-cell-pending { animation: drp-pulse 1.2s ease-in-out infinite; }
@keyframes drp-pulse { 0%, 100% { box-shadow: 0 0 0 0 rgba(255, 107, 74, 0.3); } 50% { box-shadow: 0 0 0 4px rgba(255, 107, 74, 0.15); } }
.drp-footer { display: flex; justify-content: center; margin-top: 10px; }
.drp-today-btn { font-family: var(--font-display); font-weight: 700; font-size: 13px; color: var(--color-brand); background: none; border: none; padding: 6px 16px; cursor: pointer; -webkit-tap-highlight-color: transparent; touch-action: manipulation; }
.drp-today-btn:active { opacity: 0.6; }

turbo-frame#main-content { display: block; }
turbo-frame#dogs-list { display: block; }

/* ── Walk animation ── */
.walk-scene {
  position: relative;
  overflow: visible;
}

/* ── Tabs ── */
.tab-active {
  color: var(--color-brand);
  font-weight: 700;
  position: relative;
}
.tab-active::after {
  content: '';
  position: absolute;
  bottom: 0; left: 50%; transform: translateX(-50%);
  width: 20px; height: 3px;
  background: var(--color-brand);
  border-radius: 3px;
}
.tab-inactive {
  color: var(--color-ink-faint);
  font-weight: 600;
  transition: color 0.2s;
}
.tab-inactive:hover { color: var(--color-ink-soft); }

/* ── Day column tap highlight (corner brackets) ── */
.day-tap-highlight {
  position: relative;
  transition: opacity 0.35s ease-out;
}
.day-tap-highlight::before {
  --arm: 6px;
  --r: 2px;
  --c: rgba(255,107,74,0.35);
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    /* top-left horizontal */ linear-gradient(var(--c), var(--c)) 0 0 / var(--arm) 1.5px no-repeat,
    /* top-left vertical */   linear-gradient(var(--c), var(--c)) 0 0 / 1.5px var(--arm) no-repeat,
    /* top-right horizontal */ linear-gradient(var(--c), var(--c)) 100% 0 / var(--arm) 1.5px no-repeat,
    /* top-right vertical */   linear-gradient(var(--c), var(--c)) 100% 0 / 1.5px var(--arm) no-repeat,
    /* bottom-left horizontal */ linear-gradient(var(--c), var(--c)) 0 100% / var(--arm) 1.5px no-repeat,
    /* bottom-left vertical */   linear-gradient(var(--c), var(--c)) 0 100% / 1.5px var(--arm) no-repeat,
    /* bottom-right horizontal */ linear-gradient(var(--c), var(--c)) 100% 100% / var(--arm) 1.5px no-repeat,
    /* bottom-right vertical */   linear-gradient(var(--c), var(--c)) 100% 100% / 1.5px var(--arm) no-repeat;
}
.day-tap-highlight {
  box-shadow: none !important;
}
.day-tap-highlight.day-tap-fade {
  opacity: 0;
}

/* ── Dog card body transition ── */
.dog-card-body {
  transition: all 0.3s ease;
}

/* Schedule tape nav buttons */
.tape-nav-btn {
  flex-shrink: 0;
  width: 24px;
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--color-ink-soft);
  opacity: 0.5;
  transition: opacity 0.15s;
}
.tape-nav-btn:active:not(.tape-nav-disabled) {
  opacity: 1;
}
.tape-nav-btn.tape-nav-disabled {
  opacity: 0.15;
  pointer-events: none;
}

.tape-month-sep::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: -4.5px;
  width: 1px;
  background: rgba(160,160,178,0.3);
  pointer-events: none;
}

/* ── Modal ── */
/* ── New Dog Dialog ── */
.new-dog-dialog {
  margin: auto;
  padding: 1.25rem;
  border: none;
  border-radius: 1rem;
  background: var(--color-canvas);
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
  max-width: 20rem;
  width: calc(100% - 2rem);
  max-height: calc(100dvh - 4rem);
  overflow: hidden;
}
.new-dog-dialog::backdrop {
  background: rgba(26, 26, 46, 0.5);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
}


.modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 300;
  background: rgba(0, 0, 0, 0.3);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
}


.modal-panel {
  position: fixed;
  inset: 0.75rem;
  display: flex;
  flex-direction: column;
  background: var(--color-glass-strong);
  backdrop-filter: blur(24px) saturate(1.5);
  -webkit-backdrop-filter: blur(24px) saturate(1.5);
  border: 1px solid var(--color-glass-subtle);
  z-index: 301;
  max-width: 32rem;
  margin-inline: auto;
  border-radius: 1.5rem;
  overflow: hidden;
  box-shadow: 0 8px 24px rgba(0,0,0,0.18);
}

/* Ad-hoc 2026-04-11: fling-to-dismiss hero photo. Disable native
   touch-scroll and text selection on the photo area so the fling
   drag doesn't fight vertical scroll or select nearby text. */
.dog-modal-hero {
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
}

/* Hero photo overlay buttons — press feedback + pin rotation */
.hero-overlay-btn:active {
  background: rgba(255,255,255,0.3);
}
.hero-pin-btn .material-symbols-outlined {
  transition: transform 0.2s ease;
}
.hero-pin-btn:not(.hero-pin-active) .material-symbols-outlined {
  transform: rotate(45deg);
}

.modal-panel > form {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}

.modal-header {
  flex-shrink: 0;
}

.modal-scroll {
  flex: 1;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  scrollbar-width: none;
}
.modal-scroll::-webkit-scrollbar { display: none; }

.modal-tabs {
  background: var(--color-glass-strong);
}

/* ── Unlock-visible elements (add/remove buttons) ── */
.modal-panel .unlock-visible { display: none; }
.modal-panel.unlocked .unlock-visible { display: inline-flex; }
.modal-panel.unlocked div.unlock-visible { display: block; }
.modal-panel.unlocked div.unlock-visible.flex { display: flex; }
.modal-panel.unlocked p.unlock-visible { display: block; }
.modal-panel.unlocked .unlock-visible[data-controller~="address-autocomplete"] { display: block; width: 100%; }
.modal-panel.unlocked .unlock-show.hidden { display: block; }

/* ── Lock-visible elements (read-mode only) ── */
.modal-panel.unlocked .lock-visible { display: none; }

/* ── Mode selects (pickup/checkin, dropoff/checkout) ── */
.modal-panel.unlocked .mode-select {
  pointer-events: auto;
  cursor: pointer;
  appearance: auto;
  -webkit-appearance: menulist;
}

/* ── Day toggles ── */
.modal-panel.unlocked .day-toggle { cursor: pointer; }

/* ── Swap fields ── */
/* Wrapper for fields where read/edit are fundamentally different widgets
   (badge→select, pills→text, masked→plain). Activates on click when unlocked. */
.swap-field .edit-field { display: none; }
.swap-field.active .read-field { display: none; }
.swap-field.active .edit-field { display: revert; }
.modal-panel.unlocked .swap-field { cursor: pointer; }

/* ── Live inputs ── */
/* Always in DOM at same position as their text. Transparent + non-interactive
   in read mode; clickable in unlocked mode.
   Font/color inherit is in @layer components so Tailwind utilities override it
   when explicit classes are present; otherwise the input inherits from its parent. */

/* Structural — unlayered so nothing overrides these */
.live-input {
  background: transparent;
  border: none;
  outline: none;
  padding: 0;
  margin: 0;
  display: block;
  width: 100%;
  -webkit-appearance: none;
  appearance: none;
  pointer-events: none;
  resize: none;
}
.live-input::-webkit-inner-spin-button,
.live-input::-webkit-outer-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.live-input[type="number"] {
  -moz-appearance: textfield;
}

/* Typography — in components layer so Tailwind utilities win when present */
@layer components {
  .live-input {
    font-family: inherit;
    font-size: inherit;
    font-weight: inherit;
    font-style: inherit;
    line-height: inherit;
    letter-spacing: inherit;
    color: inherit;
  }
}

.modal-panel.unlocked .live-input {
  pointer-events: auto;
  cursor: text;
}

.modal-panel.unlocked .live-input:hover {
  background: rgba(26, 26, 46, 0.04);
  border-radius: 3px;
}

.modal-panel.unlocked .live-input:focus {
  outline: none;
  background: rgba(255, 107, 74, 0.06);
  border-radius: 3px;
}

/* ── Edit inputs ── */
/* Base class handles box only — no font-size/family so Tailwind utilities aren't clobbered. */
.edit-input {
  width: 100%;
  padding: 0.3rem 0.6rem;
  background: var(--color-glass-strong);
  border: 1.5px solid rgba(26, 26, 46, 0.1);
  border-radius: 0.5rem;
  font-size: 0.875rem;        /* default for body inputs */
  font-family: var(--font-body);
  color: var(--color-ink);
  outline: none;
  transition: border-color 0.15s, box-shadow 0.15s;
  -webkit-appearance: none;
  appearance: none;
}
.edit-input:focus {
  border-color: var(--color-brand);
  box-shadow: 0 0 0 3px rgba(255, 107, 74, 0.15);
}
.edit-input::placeholder { color: var(--color-ink-faint); }

/* Modifier: matches the modal header h2 (name field) */
.edit-input-title {
  font-size: 1.5rem;
  font-family: var(--font-display);
  font-weight: 800;
  letter-spacing: -0.025em;
  color: var(--color-ink);
  padding-top: 0.1rem;
  padding-bottom: 0.1rem;
}

/* Modifier: matches the breed/age subheading (text-sm font-600 text-brand) */
.edit-input-sub {
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--color-brand);
}

/* ── Field errors ── */
.field-error { font-size: 0.75rem; color: var(--color-rose); margin-top: 0.25rem; }

/* (.error-banner removed — Plan 196: the dog modal renders shared/_error_summary.) */

/* ── Radial photo menu ── */
.starpaw-overlay {
  background: transparent;
  transition: background 0.2s ease, transform 0.5s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.5s ease;
}
/* Star visible — overflow clip + header z-index (starview and listening) */
body[data-quick-photo-state-value="starview"] .starpaw-overlay,
body[data-quick-photo-state-value="listening"] .starpaw-overlay {
  overflow: hidden;
}
body[data-quick-photo-state-value="starview"] > header,
body[data-quick-photo-state-value="listening"] > header {
  z-index: 201;
}

/* StarView: background + dimming + panels — blocks page interaction */
/* Deep space base with nebula clouds */
body[data-quick-photo-state-value="starview"] .starpaw-overlay,
body[data-quick-photo-state-value="listening"] .starpaw-overlay {
  background:
    /* (Removed: a "dark mask behind corona" radial-gradient that was pinned to the
       screen centre — its --star-cx/--star-cy were never set, so it rendered as a
       dark hole in the middle of the starfield rather than tracking the star.) */
    radial-gradient(ellipse 55% 45% at 15% 20%,
      rgba(120, 60, 160, 0.25) 0%, rgba(80, 40, 130, 0.12) 40%, transparent 70%),
    radial-gradient(ellipse 50% 55% at 80% 75%,
      rgba(40, 140, 160, 0.2) 0%, rgba(30, 100, 140, 0.1) 40%, transparent 70%),
    radial-gradient(ellipse 35% 40% at 70% 40%,
      rgba(160, 60, 100, 0.15) 0%, rgba(120, 40, 80, 0.06) 50%, transparent 70%),
    radial-gradient(ellipse 40% 35% at 25% 80%,
      rgba(50, 80, 180, 0.18) 0%, rgba(40, 60, 140, 0.08) 45%, transparent 70%),
    rgba(12, 20, 42, 0.92);
  pointer-events: auto !important;
}
/* Twinkling star layers — injected dynamically by showStarView() */
.starview-stars {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 0;
}
.starview-stars-1 { animation: starfield-twinkle-1 4s ease-in-out infinite alternate; }
.starview-stars-2 { animation: starfield-twinkle-2 5s ease-in-out infinite alternate; }
@keyframes starfield-twinkle-1 {
  0%   { opacity: 0.5; }
  50%  { opacity: 1; }
  100% { opacity: 0.6; }
}
@keyframes starfield-twinkle-2 {
  0%   { opacity: 0.9; }
  50%  { opacity: 0.3; }
  100% { opacity: 0.8; }
}
body > main,
body > nav {
  transition: opacity 0.5s ease, transform 0.32s cubic-bezier(0.4, 0, 0.2, 1);
}

/* Nav drawer handle — a small grip above the bar to slide it away for more space.
   It's a child of <nav>, positioned just above the bar's top edge, so it rides
   down with the bar when collapsed and peeks at the bottom viewport edge. */
.nav-drawer-handle {
  position: absolute;
  left: 50%;
  bottom: 100%;
  transform: translateX(-50%);
  width: 56px;
  height: 20px;
  /* The handle is the bring-it-back affordance, shown only while collapsed; when
     the bar is up you hide it with a downward drag instead. */
  display: none;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  border-radius: 11px 11px 0 0;
  background: rgba(248, 249, 252, 0.82);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  box-shadow: 0 -3px 10px rgba(26, 26, 46, 0.10);
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}
.nav-drawer-grip {
  width: 38px;
  height: 5px;
  border-radius: 3px;
  background: var(--color-ink-faint);
  transition: background 0.15s ease;
}
.nav-drawer-handle:active .nav-drawer-grip { background: var(--color-ink-soft); }
body.nav-collapsed .nav-drawer-handle { display: flex; }

/* Collapsed: slide the whole bar (handle included) down by its own height, and
   let <main> reclaim the freed bottom space (overrides the inline padding). */
body.nav-no-anim > nav { transition: none; }
body.nav-collapsed > nav { transform: translateY(100%); }
body.nav-collapsed > main {
  padding-bottom: calc(1.75rem + env(safe-area-inset-bottom)) !important;
}
body > main { transition: padding-bottom 0.32s cubic-bezier(0.4, 0, 0.2, 1); }
body.nav-no-anim > main { transition: none; }

/* Walk-screen map controls (recenter / report / Paw·Pin) anchor above the bottom
   nav via var(--map-ctrl-bottom); when the nav is dragged away they drop down to
   the freed space. (Inline JS styles read this var — see mapbox_map_controller.) */
body { --map-ctrl-bottom: calc(3.5rem + env(safe-area-inset-bottom, 0px) + 12px); }
body.nav-collapsed { --map-ctrl-bottom: calc(env(safe-area-inset-bottom, 0px) + 12px); }
body[data-quick-photo-state-value="starview"] > main,
body[data-quick-photo-state-value="listening"] > main {
  opacity: 0.1;
}
body[data-quick-photo-state-value="starview"] > nav,
body[data-quick-photo-state-value="listening"] > nav {
  opacity: 0;
  pointer-events: none;
}
/* Plan 166 (experiment): keep the top header visible + usable while StarView is open. The header is
   z-50, but the StarView overlay rides at z-1000 (window_stack base), so it'd otherwise sit behind
   the nebula. Lift it above the StarView layers (action bar = base+1), below a stacked modal (2000).
   Listening (AI mode) gets the same lift (Plan 193) — it uses the same overlay, and the stale z-201
   rule left the header BEHIND the nebula there. */
body[data-quick-photo-state-value="starview"] > header,
body[data-quick-photo-state-value="listening"] > header {
  z-index: 1500;
}
body[data-quick-photo-state-value="starview"] .starpaw-context-bar,
body[data-quick-photo-state-value="starview"] .starpaw-context-panel {
  will-change: transform;
}

.starpaw-swipe-zone-line {
  position: fixed;
  bottom: calc(3.5rem + env(safe-area-inset-bottom));
  left: 12px;
  right: 12px;
  height: 0;
  border-top: 1px dashed rgba(255, 255, 255, 0.35);
  pointer-events: none;
}

/* Bottom action bar — panel-attached verbs (photos Share/Publish/Slideshow, …)
   in the repurposed gesture strip. The strip stays gesture-transparent; only the
   buttons are pointer-events islands, so the paw's fling/pull gestures still work
   in the gaps. Visible only in starview (no actions during listening). */
.starpaw-action-bar {
  position: fixed;
  bottom: env(safe-area-inset-bottom);
  left: 0; right: 0;
  height: calc(3.5rem - 7px);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  pointer-events: none;
  z-index: 201;
  opacity: 0;
  transition: opacity 0.2s;
}
body[data-quick-photo-state-value="starview"] .starpaw-action-bar {
  opacity: 1;
}
.starpaw-action-bar-btn {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  height: 38px;
  padding: 0 14px;
  border-radius: 19px;
  /* Glassy brand-blue pill — reads as part of the StarView (matches the core's
     electric blue) rather than a bright cream chip floating on the nebula. */
  background: rgba(74, 139, 255, 0.22);
  color: #fff;
  border: 1px solid rgba(74, 139, 255, 0.55);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.28),
              0 4px 14px rgba(0, 0, 0, 0.24);
  font-family: var(--font-display, inherit);
  font-weight: 600;
  font-size: 13px;
  line-height: 1;
  pointer-events: none;
  cursor: pointer;
  flex-shrink: 0;
  -webkit-user-select: none;
  user-select: none;
}
body[data-quick-photo-state-value="starview"] .starpaw-action-bar-btn {
  pointer-events: auto;
}
.starpaw-action-bar-btn .material-symbols-outlined {
  font-size: 20px;
  line-height: 1;
}
.starpaw-action-bar-btn.is-active {
  background: var(--color-brand-light, #e0e7ff);
  color: var(--color-brand, #4f46e5);
}
/* Always visible, but dimmed + inert when nothing is selected. */
.starpaw-action-bar-btn.is-disabled {
  opacity: 0.4;
}
.starpaw-action-bar-btn:not(.is-disabled):active {
  filter: brightness(0.9);
}

/* Recording indicator — glow on paw control while mic is live */
.star-group.ai-recording {
  box-shadow: 0 0 10px 3px rgba(99, 102, 241, 0.3) !important;
  animation: ai-record-pulse 2s ease-in-out infinite;
}
.star-group.ai-recording::after {
  border-color: rgba(99, 102, 241, 0.5);
  box-shadow: 0 0 8px 2px rgba(99, 102, 241, 0.3);
}
.star-group.ai-recording.ai-voice-active {
  box-shadow: 0 0 16px 6px rgba(99, 102, 241, 0.55) !important;
  animation: ai-voice-pulse 0.8s ease-in-out infinite;
}
.star-group.ai-recording.ai-voice-active::after {
  border-color: rgba(99, 102, 241, 0.7);
  box-shadow: 0 0 12px 4px rgba(99, 102, 241, 0.5);
}
.star-group.ai-recording .orbit-slot-group-dots {
  display: none;
}
@keyframes ai-record-pulse {
  0%, 100% { box-shadow: 0 0 10px 3px rgba(99, 102, 241, 0.2); }
  50% { box-shadow: 0 0 14px 5px rgba(99, 102, 241, 0.4); }
}
@keyframes ai-voice-pulse {
  0%, 100% { box-shadow: 0 0 12px 4px rgba(99, 102, 241, 0.35); }
  50% { box-shadow: 0 0 22px 8px rgba(99, 102, 241, 0.65); }
}

/* Equalizer — vertical bars in the gesture zone strip at the bottom */
.core-eq-container {
  position: fixed;
  bottom: calc(3.5rem + env(safe-area-inset-bottom));
  left: 12px;
  right: 12px;
  height: 3rem;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 3px;
  pointer-events: none;
  z-index: 201;
}
.core-eq-bar {
  width: 3px;
  border-radius: 1px;
  background: rgba(99, 102, 241, 0.8);
  will-change: height;
  flex-shrink: 0;
}

/* AI transcript text — inside the gesture area */
.ai-gesture-text {
  position: fixed;
  bottom: calc(env(safe-area-inset-bottom) + (3.5rem / 2) - 10px);
  left: 16px;
  right: 16px;
  font-family: var(--font-display);
  font-size: 12px;
  font-weight: 500;
  line-height: 1.3;
  color: rgba(255, 255, 255, 0.8);
  pointer-events: none;
  transition: opacity 0.2s ease-out;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ai-gesture-text:has(.ai-gesture-action) {
  display: flex;
  align-items: center;
  justify-content: space-between;
  overflow: visible;
}
.ai-gesture-action {
  pointer-events: auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  margin: -10px 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.15);
  border: none;
  color: white;
  cursor: pointer;
  flex-shrink: 0;
}
.ai-gesture-action:active {
  background: rgba(255, 255, 255, 0.3);
}
.ai-gesture-action .material-symbols-outlined {
  font-size: 20px;
  line-height: 1;
}

/* ── Listening mode ── */

/* Art deco ornament ring — solid silver/platinum sunburst bezel.
   Inspired by 1930s wall clocks: bold stepped wedges, thick bands, raised studs.
   Hidden by default, fades in during conversation/listening mode.
   Slowly counter-rotates against the starburst for visual depth. */
.star-deco-ring {
  position: absolute;
  width: 260px;
  height: 260px;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.6s ease-out;
  animation: deco-ring-spin 120s linear infinite;
  /* Combined mask: radial (center hole + edge fade) intersected with
     conic (5 segments at 72° intervals, each ~52° wide, 20° gaps).
     Pentagonal symmetry echoes the starburst rays. */
  -webkit-mask-image:
    radial-gradient(circle, transparent 22%, white 26%, white 88%, transparent 95%),
    conic-gradient(
      from 0deg,
      white 0deg, white 26deg,
      transparent 26deg, transparent 46deg,
      white 46deg, white 98deg,
      transparent 98deg, transparent 118deg,
      white 118deg, white 170deg,
      transparent 170deg, transparent 190deg,
      white 190deg, white 242deg,
      transparent 242deg, transparent 262deg,
      white 262deg, white 314deg,
      transparent 314deg, transparent 334deg,
      white 334deg, white 360deg
    );
  -webkit-mask-composite: destination-in;
  mask-image:
    radial-gradient(circle, transparent 22%, white 26%, white 88%, transparent 95%),
    conic-gradient(
      from 0deg,
      white 0deg, white 26deg,
      transparent 26deg, transparent 46deg,
      white 46deg, white 98deg,
      transparent 98deg, transparent 118deg,
      white 118deg, white 170deg,
      transparent 170deg, transparent 190deg,
      white 190deg, white 242deg,
      transparent 242deg, transparent 262deg,
      white 262deg, white 314deg,
      transparent 314deg, transparent 334deg,
      white 334deg, white 360deg
    );
  mask-composite: intersect;
}
@keyframes deco-ring-spin {
  from { transform: translate(-50%, -50%) rotate(0deg); }
  to   { transform: translate(-50%, -50%) rotate(-360deg); }
}

/* Shooting stars — brief luminous streaks across the sky during conversation */
.shooting-star {
  position: absolute;
  width: var(--shoot-len, 60px);
  height: 1.5px;
  border-radius: 1px;
  pointer-events: none;
  z-index: 1;
  transform-origin: 100% 50%;
  transform: rotate(var(--shoot-angle, 30deg));
  background: linear-gradient(90deg, transparent 0%, rgba(220, 230, 255, 0.15) 20%, rgba(240, 245, 255, 0.9) 85%, #fff 100%);
  animation: shooting-star-fly 1s ease-in forwards;
}
.shooting-star::after {
  content: "";
  position: absolute;
  right: -1px;
  top: 50%;
  transform: translateY(-50%);
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 0 4px 1px rgba(200, 220, 255, 0.8);
}
@keyframes shooting-star-fly {
  0% {
    opacity: 0;
    translate: 0 0;
  }
  10% {
    opacity: 1;
  }
  70% {
    opacity: 1;
  }
  100% {
    opacity: 0;
    translate: var(--shoot-dx, -30vw) var(--shoot-dy, 20vh);
  }
}

/* Speaking ripples — concentric refractive waves emanating from the star body
   during RESPONDING state. Simulates spacetime distortion via layered ring
   gradients with mix-blend-mode for a light-bending look. */
.star-ripple-container {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 0;
  height: 0;
  pointer-events: none;
  z-index: 0;
}
.star-ripple {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 40px;
  height: 40px;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  pointer-events: none;
  /* Double ring: bright leading edge + dark trough behind it, like a water wave
     refracting light. The bright/dark bands create a lens-like distortion illusion. */
  background: radial-gradient(
    circle,
    transparent 40%,
    rgba(180, 200, 240, 0.04) 55%,
    rgba(100, 120, 160, 0.08) 62%,
    rgba(200, 220, 255, 0.15) 68%,
    rgba(255, 255, 255, 0.10) 72%,
    rgba(200, 220, 255, 0.06) 78%,
    rgba(100, 120, 160, 0.03) 85%,
    transparent 95%
  );
  mix-blend-mode: screen;
  animation: star-ripple-expand var(--ripple-duration, 2s) ease-out forwards;
}
@keyframes star-ripple-expand {
  0% {
    width: 40px;
    height: 40px;
    opacity: 0;
  }
  10% {
    opacity: 1;
  }
  60% {
    opacity: 0.7;
  }
  100% {
    width: var(--ripple-size, 350px);
    height: var(--ripple-size, 350px);
    opacity: 0;
  }
}

/* Body centering transition */

/* Fade out StarView UI during listening mode (tabs, panels, empty state,
   date, joystick arrows). Starburst rays and corona stay visible. */
body[data-quick-photo-state-value="listening"] .starpaw-context-bar,
body[data-quick-photo-state-value="listening"] .starpaw-empty-state,
body[data-quick-photo-state-value="listening"] .starpaw-context-panel,
body[data-quick-photo-state-value="listening"] .starpaw-joystick-arrows {
  opacity: 0 !important;
  pointer-events: none !important;
  transition: opacity 0.4s ease-out;
}

/* Dialogue bubble shared */
.listening-bubble {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  max-width: 85vw;
  width: max-content;
  padding: 10px 18px;
  border-radius: 18px;
  background: #fff;
  border: 2px solid #222;
  color: #1a1a1a;
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: 0.01em;
  text-align: left;
  pointer-events: none;
  z-index: 10;
  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}

/* Clickable links in conversation bubbles (phone numbers, addresses) */
.conv-link {
  color: var(--color-brand, #6366f1);
  text-decoration: underline;
  text-underline-offset: 2px;
  pointer-events: auto;
}


/* Star bubble — above body */
.listening-bubble-star {
  bottom: calc(50% + 60px);
}

/* User bubble — below paw control */
.listening-bubble-user {
  top: calc(50% + 100px);
}
.listening-bubble-user .listening-bubble-text {
  display: block;
  max-height: calc(1.4em * 4);
  overflow: hidden;
}

/* Tail — curved comic-book style via SVG */
.listening-bubble-tail {
  position: absolute;
  width: 20px;
  height: 18px;
  overflow: visible;
}
.listening-bubble-tail svg {
  display: block;
  width: 100%;
  height: 100%;
  fill: #fff;
  stroke: #222;
  stroke-width: 1.5;
  stroke-linejoin: round;
}
/* Star bubble tail — bottom-center, curves down toward the core */
.listening-bubble-star .listening-bubble-tail {
  bottom: -16px;
  left: 50%;
  transform: translateX(-50%);
}
/* User bubble tail — bottom, horizontal position set by JS */
.listening-bubble-user .listening-bubble-tail {
  bottom: -16px;
  left: 50%;
  transform: translateX(-50%);
}

/* Entrance animation */
.listening-bubble-enter {
  animation: bubble-pop 0.2s ease-out both;
}
@keyframes bubble-pop {
  from { opacity: 0; transform: translateX(-50%) scale(0.8); }
  to   { opacity: 1; transform: translateX(-50%) scale(1); }
}

/* Exit animation */
.listening-bubble-exit {
  animation: bubble-fade 0.15s ease-in both;
}
@keyframes bubble-fade {
  to { opacity: 0; transform: translateX(-50%) scale(0.9); }
}

/* Thinking bubble — cloud-shaped thought bubble */
.thinking-bubble {
  padding: 0;
  border: none;
  border-radius: 0;
  background: none;
  box-shadow: none;
  min-width: 0;
  width: auto;
  transform: translateX(-25%);
  /* Cloud SVG: single fluffy outline path, white fill, no internal lines */
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 140 90'%3E%3Cpath d='M32 72 C18 72 8 64 10 54 C4 48 4 38 12 30 C14 20 24 12 38 12 C42 4 56 0 68 4 C76 -2 90 -1 100 6 C108 4 120 10 124 20 C134 26 138 38 132 50 C138 60 132 72 118 72 C112 80 96 84 84 78 C74 84 56 82 46 76 C40 78 34 76 32 72 Z' fill='%23fff' stroke='%23222' stroke-width='2'/%3E%3C/svg%3E") no-repeat center / 100% 100%;
  overflow: visible;
}
.thinking-bubble.listening-bubble-enter {
  animation: think-pop 0.2s ease-out both;
}
.thinking-bubble.listening-bubble-exit {
  animation: think-fade 0.15s ease-in both;
}
@keyframes think-pop {
  from { opacity: 0; transform: translateX(-25%) scale(0.8); }
  to   { opacity: 1; transform: translateX(-25%) scale(1); }
}
@keyframes think-fade {
  to { opacity: 0; transform: translateX(-25%) scale(0.9); }
}
.thinking-bubble .listening-bubble-tail { display: none; }

.thinking-dodecahedron {
  display: block;
  width: 120px;
  height: 60px;
  /* Inset the canvas well inside the cloud outline */
  margin: 22px 26px 22px;
}

/* Thought-cloud dots — angled trail from bubble down to star.
   Comic-book style: well-spaced, ~1 dot-diameter gap between each. */
.thinking-tail {
  position: absolute;
  bottom: -52px;
  left: 0;
  right: 0;
  height: 46px;
  pointer-events: none;
}
.thinking-dot {
  position: absolute;
  border-radius: 50%;
  background: #fff;
  border: 1.5px solid #222;
}
/* Dot 1 (largest) — near left third of bubble, gap from bubble edge */
.thinking-dot-1 { width: 12px; height: 12px; top: 0; left: 33%; transform: translateX(-50%); }
/* Dot 2 (medium) — angled toward star, ~1 diameter gap from dot 1 */
.thinking-dot-2 { width: 8px; height: 8px; top: 20px; left: 28%; transform: translateX(-50%); }
/* Dot 3 (smallest) — near star center, ~1 diameter gap from dot 2 */
.thinking-dot-3 { width: 5px; height: 5px; top: 36px; left: 25%; transform: translateX(-50%); }

/* Body action icon in listening mode — must sit above the core circuit overlay (z-index: 2).
   The ::before disc covers the circuit traces so the icon reads clearly. */
.listening-action-icon {
  font-size: 28px;
  color: #000;
  position: relative;
  z-index: 3;
}

/* ── Confirm summary box (plan 127) ──
   A plain, factual "what you're about to confirm" receipt, shown above the star
   while a confirm-required command's icon is pending on the core. Distinct from
   the white spoken bubble: a dark, monospace-ish ticket so it reads as a system
   summary of the change, not conversation. Positioned above the star bubble. */
.listening-confirm-summary {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(50% + 132px);
  /* width: max-content (like .listening-bubble) so the box sizes to its text
     instead of collapsing to the small radial container and wrapping into a
     tall column. max-width keeps a long summary from spanning the whole screen. */
  width: max-content;
  max-width: 85vw;
  padding: 8px 13px;
  border-radius: 12px;
  background: rgba(20, 24, 33, 0.94);
  border: 1px solid rgba(255, 255, 255, 0.16);
  color: #eef2f8;
  font-family: var(--font-display, ui-sans-serif);
  font-size: 13px;
  font-weight: 600;
  line-height: 1.35;
  letter-spacing: 0.01em;
  text-align: center;
  pointer-events: none;
  z-index: 11;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.32);
  white-space: pre-line;
  opacity: 0;
  transition: opacity 0.18s ease;
}
.listening-confirm-summary.visible { opacity: 1; }
.listening-confirm-summary .lcs-label {
  display: block;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #7f93ad;
  margin-bottom: 2px;
}

/* ── StarPaw context bar ── */
.starpaw-context-bar {
  display: flex;
  gap: 8px;
}
.starpaw-context-btn {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 5px 12px;
  border-radius: 20px;
  border: none;
  background: #1e3a5f;
  color: #8aa8c4;
  font-size: 13px;
  font-weight: 600;
  -webkit-tap-highlight-color: transparent;
  transition: background 0.15s, color 0.15s;
}
.starpaw-context-btn.active {
  background: #1e3a5f;
  color: #fff;
  box-shadow: 0 0 0 1px rgba(255,255,255,0.7), 0 0 0 3px #4A8BFF;
}
.starpaw-context-btn:active {
  background: rgba(255, 255, 255, 0.3);
}
.starpaw-context-btn.starpaw-tab-disabled {
  opacity: 0.3;
  pointer-events: none;
}

.starpaw-sound-toggle {
  position: fixed;
  left: 16px;
  bottom: calc(4rem + env(safe-area-inset-bottom));
  z-index: 201;
  background: rgba(255, 255, 255, 0.1);
  border: none;
  border-radius: 50%;
  width: 36px;
  height: 36px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: rgba(255, 255, 255, 0.6);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  pointer-events: auto;
}
.starpaw-sound-toggle .material-symbols-outlined {
  font-size: 18px;
}
.starpaw-sound-toggle:active {
  color: rgba(255, 255, 255, 1);
  background: rgba(255, 255, 255, 0.2);
}
.starpaw-datetime {
  position: fixed;
  right: 16px;
  bottom: calc(4rem + env(safe-area-inset-bottom));
  z-index: 201;
  color: rgba(255, 255, 255, 0.5);
  font-family: var(--font-display);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.02em;
  line-height: 36px;
  pointer-events: none;
  white-space: nowrap;
}

/* ── StarPaw logistics panel ── */
.starpaw-logistics-panel {
  background: rgba(255, 255, 255, 0.12);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  border-radius: 12px;
  padding: 0;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.25);
  max-height: calc(100dvh - env(safe-area-inset-top) - 104px - 12px - 3.5rem - env(safe-area-inset-bottom));
  touch-action: pan-y;
  overflow-y: auto;
  overscroll-behavior: contain;
  scroll-behavior: smooth;
}
.starpaw-logistics-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 12px;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
/* ── Shared dog badge (thumbnail + name column on left of sections) ── */
.starpaw-dog-badge {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  width: 48px;
  flex-shrink: 0;
  padding: 4px 4px 4px 8px;
  background: rgba(255, 255, 255, 0.05);
  border-right: 1px solid rgba(255, 255, 255, 0.08);
}
.starpaw-dog-badge-thumb {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  object-fit: cover;
  flex-shrink: 0;
}
.starpaw-dog-badge-placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(255, 255, 255, 0.15);
  color: rgba(255, 255, 255, 0.6);
}
.starpaw-dog-badge-name {
  font-size: 10px;
  font-weight: 600;
  color: rgba(255, 255, 255, 0.9);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 40px;
  text-align: center;
}
.starpaw-section-content {
  flex: 1;
  min-width: 0;
  align-self: stretch;
  /* Vertically center the contact rows against the (full-height) dog badge so a
     short card (one contact) reads as centered rather than top-aligned. */
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.starpaw-logistics-title {
  font-size: 12px;
  font-weight: 600;
  color: #fff;
  letter-spacing: 0.02em;
}
.starpaw-logistics-chevron {
  color: rgba(255, 255, 255, 0.7);
}
.starpaw-logistics-body {
  padding: 4px 12px 10px;
}
.starpaw-logistics-row {
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 8px;
  padding: 6px 10px;
  margin-bottom: 6px;
}
.starpaw-logistics-row:last-child {
  margin-bottom: 0;
}
.starpaw-logistics-row-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.starpaw-logistics-dog-name {
  font-size: 12px;
  font-weight: 600;
  color: #fff;
  padding-left: 8px;
  border-left: 2px solid var(--color-brand);
}
.starpaw-logistics-check {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, 0.3);
  background: transparent;
  display: flex;
  align-items: center;
  justify-content: center;
  color: rgba(255, 255, 255, 0.5);
  flex-shrink: 0;
  -webkit-tap-highlight-color: transparent;
  transition: background 0.15s, border-color 0.15s, color 0.15s;
}
.starpaw-logistics-check:active {
  background: var(--color-mint);
  border-color: var(--color-mint);
  color: #fff;
}
.starpaw-logistics-detail {
  font-size: 11px;
  color: rgba(255, 255, 255, 0.7);
  margin-top: 2px;
}
.starpaw-logistics-detail a {
  color: rgba(255, 255, 255, 0.85);
}
.starpaw-logistics-contact {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 4px;
}
.starpaw-logistics-contact-thumb {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  object-fit: cover;
  flex-shrink: 0;
}
.starpaw-logistics-contact-placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(255, 255, 255, 0.15);
  color: rgba(255, 255, 255, 0.6);
}
.starpaw-logistics-contact-name {
  font-size: 11px;
  color: rgba(255, 255, 255, 0.8);
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.starpaw-logistics-action-btn {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: rgba(52, 199, 160, 0.25);
  display: flex;
  align-items: center;
  justify-content: center;
  color: #5ee8c5;
  flex-shrink: 0;
  -webkit-tap-highlight-color: transparent;
}
.starpaw-logistics-action-btn:active {
  background: rgba(255, 255, 255, 0.3);
}
.starpaw-logistics-eta-btn {
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 3px 8px;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.15);
  color: #fff;
  font-size: 11px;
  font-weight: 600;
  flex-shrink: 0;
  border: none;
  -webkit-tap-highlight-color: transparent;
}
.starpaw-logistics-eta-btn:active {
  background: rgba(255, 255, 255, 0.3);
}

/* ── StarPaw Photos Panel ─────────────────────────── */
.starpaw-photos-panel {
  /* Dark glass (Plan 166): the photos read more vividly on a dark surround — like the lightbox's
     near-black backdrop — than on the old bright white-tint, which washed them out by contrast.
     Tone matches the dark sticky header. */
  background: rgba(18, 20, 28, 0.55);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  border-radius: 12px;
  padding: 0;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.25);
  max-height: calc(100dvh - env(safe-area-inset-top) - 104px - 12px - 3.5rem - env(safe-area-inset-bottom));
  overflow-y: auto;
  overflow-anchor: none;
  overscroll-behavior: contain;
  touch-action: pan-y;
}
.starpaw-photos-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 8px 12px;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  position: sticky;
  top: 0;
  z-index: 1;
  background: rgba(20, 22, 30, 0.97);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
}
.starpaw-photos-title {
  font-size: 14px;
  font-weight: 600;
  color: #fff;
  letter-spacing: 0.02em;
}
.starpaw-photos-chevron {
  color: rgba(255, 255, 255, 0.7);
}
/* The multi-select toggle is a header affordance, tapped directly — opt it back out of
   the "panel buttons are joystick-gated" rule (.starpaw-context-panel button →
   pointer-events:none). The .starpaw-context-panel prefix is needed to beat its specificity. */
.starpaw-context-panel .starpaw-photos-previewtoggle,
.starpaw-context-panel .starpaw-photos-clear,
.starpaw-context-panel .starpaw-photos-filter,
.starpaw-context-panel .starpaw-photos-preview-nav,
.starpaw-context-panel .starpaw-photos-preview-handle {
  pointer-events: auto;
}
.starpaw-photos-header-actions {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
}
.starpaw-photos-header-actions .starpaw-photos-previewtoggle { margin-left: 0; }
.starpaw-photos-body {
  display: flex;
  flex-direction: column;
  padding: 4px 12px 10px;
}
.starpaw-photos-row {
  background: rgba(0, 0, 0, 0.22);
  border: 1px solid rgba(255, 255, 255, 0.07);
  border-radius: 8px;
  padding: 4px 0;
  margin-bottom: 6px;
  overflow: hidden;
}
.starpaw-photos-row:last-child {
  margin-bottom: 0;
}
/* Plan 166 — inline preview drawer: slides down below a strip, showing the latest-selected photo
   at size. Inline (inside the row) so it pushes the strips below it down. */
.starpaw-photos-preview {
  /* Full strip width (the 8px side margins match the strip) and SQUARE for now: aspect-ratio makes
     the height track the width, so the preview is a 1:1 box (Plan 166 — trying square first; the
     max-height-of-width variant is the alternative). max-height animates the reveal and caps the
     square at 78vh so a wide (landscape) strip can't grow taller than the viewport. */
  max-height: 0;
  opacity: 0;
  overflow: hidden;
  margin: 0 8px;
  border-radius: 8px;
  aspect-ratio: 1 / 1;
  position: relative;
  display: flex;
  flex-direction: column;
  transition: max-height 0.22s ease, opacity 0.18s ease, margin-top 0.22s ease;
}
.starpaw-photos-preview.is-open {
  max-height: min(100vw, 78vh);
  opacity: 1;
  margin-top: 6px;
}
.starpaw-photos-preview-handle {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  width: 100%;
  padding: 6px 0 4px;
  background: transparent;
  border: 0;
  cursor: pointer;
  touch-action: manipulation;
}
.starpaw-photos-preview-handle > span {
  display: block;
  width: 40px;
  height: 4px;
  border-radius: 9999px;
  background: rgba(255, 255, 255, 0.55);
  transition: background 0.15s ease;
}
.starpaw-photos-preview-handle:active > span { background: rgba(255, 255, 255, 0.85); }
/* Wraps the photo + its overlays so the selection ring and chevrons frame exactly the image box
   (the handle sits below, outside this). */
.starpaw-photos-preview-imgwrap {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
}
.starpaw-photos-preview-img {
  display: block;
  width: 100%;
  flex: 1 1 auto;
  min-height: 0;
  object-fit: contain;
  border-radius: 8px;
  background: rgba(0, 0, 0, 0.25);
}
/* Selected previewed photo gets the same dashed white "marching ants" ring as a selected thumb. */
.starpaw-photos-preview.is-photo-selected .starpaw-photos-preview-imgwrap::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 8px;
  pointer-events: none;
  z-index: 1;
  background:
    linear-gradient(90deg, #fff 50%, transparent 50%) repeat-x,
    linear-gradient(90deg, #fff 50%, transparent 50%) repeat-x,
    linear-gradient(0deg, #fff 50%, transparent 50%) repeat-y,
    linear-gradient(0deg, #fff 50%, transparent 50%) repeat-y;
  background-size: 12px 2px, 12px 2px, 2px 12px, 2px 12px;
  background-position: 0 0, 0 100%, 0 0, 100% 0;
  animation: starpaw-sel-ants-lg 0.6s linear infinite;
}
@keyframes starpaw-sel-ants-lg {
  to { background-position: 12px 0, -12px 100%, 0 -12px, 100% 12px; }
}
/* Spinner overlaid on the photo while a slow swap loads (toggled by .is-loading after 200ms). */
.starpaw-photos-preview-spinner {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 34px;
  height: 34px;
  margin: -17px 0 0 -17px;
  border: 3px solid rgba(255, 255, 255, 0.25);
  border-top-color: rgba(255, 255, 255, 0.9);
  border-radius: 50%;
  z-index: 3;
  opacity: 0;
  pointer-events: none;
  animation: spin 0.7s linear infinite;
  transition: opacity 0.15s ease;
}
.starpaw-photos-preview.is-loading .starpaw-photos-preview-spinner { opacity: 1; }
/* Prev / next chevrons overlaid on the photo, centered on the image box. */
.starpaw-photos-preview-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  padding: 0;
  border: 0;
  border-radius: 9999px;
  background: rgba(8, 10, 16, 0.5);
  color: #fff;
  cursor: pointer;
  touch-action: manipulation;
  transition: background 0.15s ease;
}
.starpaw-photos-preview-nav:active { background: rgba(8, 10, 16, 0.8); }
.starpaw-photos-preview-nav .material-symbols-outlined { font-size: 26px; }
.starpaw-photos-preview-nav-prev { left: 8px; }
.starpaw-photos-preview-nav-next { right: 8px; }
.starpaw-photos-dog {
  position: sticky;
  left: 0;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  width: 36px;
  flex-shrink: 0;
  align-self: stretch;
  pointer-events: auto;
  cursor: pointer;
  background: linear-gradient(90deg, rgba(30, 40, 60, 0.95) 70%, transparent);
  padding-right: 4px;
}
.starpaw-photos-dog-thumb {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  object-fit: cover;
  flex-shrink: 0;
}
.starpaw-photos-dog-placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(255, 255, 255, 0.15);
  color: rgba(255, 255, 255, 0.6);
}
.starpaw-photos-dog-name {
  font-size: 10px;
  font-weight: 600;
  color: rgba(255, 255, 255, 0.9);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 36px;
  text-align: center;
}
.starpaw-photos-strip {
  position: relative;
  /* Query container so thumbs can size to a fraction of the strip's VISIBLE width
     (100cqw) and dynamically fit ~4 across, responsive to the strip width. */
  container-type: inline-size;
  display: flex;
  gap: 4px;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  align-items: flex-end;
  touch-action: pan-x pan-y;
  /* On release, always settle on a thumb's left edge so none is ever left
     half-hidden under the sticky dog badge; the padding offsets past the badge. */
  scroll-snap-type: x mandatory;
  scroll-padding-left: 44px;
  background:
    repeating-linear-gradient(
      90deg,
      transparent 0px, transparent 3px,
      rgba(60, 55, 45, 0.7) 3px, rgba(60, 55, 45, 0.7) 9px,
      transparent 9px, transparent 14px
    ) top 2px left / 14px 6px repeat-x,
    repeating-linear-gradient(
      90deg,
      transparent 0px, transparent 3px,
      rgba(60, 55, 45, 0.7) 3px, rgba(60, 55, 45, 0.7) 9px,
      transparent 9px, transparent 14px
    ) bottom 2px left / 14px 6px repeat-x,
    rgba(20, 18, 15, 0.8);
  background-attachment: local, local, scroll;
  border-radius: 4px;
  padding: 12px 8px;
  border-top: 3px solid rgba(40, 35, 30, 0.9);
  border-bottom: 3px solid rgba(40, 35, 30, 0.9);
}
.starpaw-photos-strip::-webkit-scrollbar {
  display: none;
}
.starpaw-photos-date-group {
  display: flex;
  flex-direction: column;
  flex-shrink: 0;
  gap: 2px;
}
.starpaw-photos-date-group-thumbs {
  display: flex;
  gap: 4px;
}
.starpaw-photos-thumb {
  /* Dynamically fit ~4 across the strip's visible width: subtract the sticky dog
     badge (~44px incl. its gap) and the 3 inter-thumb gaps (~12px), divide by 4. */
  width: calc((100cqw - 56px) / 4);
  height: calc((100cqw - 56px) / 4);
  border-radius: 3px;
  object-fit: cover;
  flex-shrink: 0;
  cursor: pointer;
  transition: transform 0.15s ease-out, opacity 0.15s;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
  /* Suppress the iOS long-press image callout so hold-to-peek isn't hijacked. */
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}
/* Hold-to-peek preview overlay — full image, no buttons, closes on release. */
.starpaw-photo-peek {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.72);
  pointer-events: none; /* preview only — the release (pointerup) closes it */
  opacity: 0;
  transition: opacity 0.12s ease-out;
}
.starpaw-photo-peek.is-visible { opacity: 1; }
.starpaw-photo-peek-img {
  max-width: 88vw;
  max-height: 80vh;
  object-fit: contain;
  border-radius: 12px;
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.6);
}
.starpaw-photos-thumb:active {
  transform: scale(0.92);
  opacity: 0.8;
}
/* Selected photo (multi-select) — the Photos-tab verbs (Slideshow/Share/Publish)
   act on the whole selection. Tapping a thumb toggles this. */
/* Selected thumbs get a dashed white "marching ants" ring — the dashes rotate around
   the perimeter. It lives on the WRAP (an <img> can't host ::after); the joystick's
   blue focus ring stays on the thumb itself, so a selected+focused thumb shows both.
   Four edge gradients (top/bottom/left/right) animate their positions for the march. */
.starpaw-photos-thumb-wrap:has(> .starpaw-photos-thumb.is-selected)::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 4px;
  pointer-events: none;
  z-index: 1;
  background:
    linear-gradient(90deg, #fff 50%, transparent 50%) repeat-x,
    linear-gradient(90deg, #fff 50%, transparent 50%) repeat-x,
    linear-gradient(0deg, #fff 50%, transparent 50%) repeat-y,
    linear-gradient(0deg, #fff 50%, transparent 50%) repeat-y;
  background-size: 8px 2px, 8px 2px, 2px 8px, 2px 8px;
  background-position: 0 0, 0 100%, 0 0, 100% 0;
  animation: starpaw-sel-ants 0.6s linear infinite;
}
@keyframes starpaw-sel-ants {
  to { background-position: 8px 0, -8px 100%, 0 -8px, 100% 8px; }
}
/* Header multi-select toggle — a label + a sliding switch (Photos panel). */
.starpaw-photos-previewtoggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-left: auto;
  padding: 3px 2px;
  background: transparent;
  border: none;
  font-size: 11px;
  font-weight: 600;
  color: rgba(255, 255, 255, 0.7);
  cursor: pointer;
}
.starpaw-toggle-label { white-space: nowrap; }
/* The switch: a pill track with a thumb that slides left (off) → right (on). */
.starpaw-toggle-switch {
  position: relative;
  width: 28px;
  height: 16px;
  flex-shrink: 0;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.22);
  transition: background 0.18s ease;
}
.starpaw-toggle-switch::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
  transition: transform 0.18s ease;
}
.is-on .starpaw-toggle-switch { background: #4A8BFF; }
.is-on .starpaw-toggle-switch::after { transform: translateX(12px); }
.starpaw-photos-clear {
  display: inline-flex;
  align-items: center;
  padding: 3px 9px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  /* Dimmed by default (nothing selected → clearing is a no-op). */
  color: rgba(255, 255, 255, 0.4);
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid rgba(255, 255, 255, 0.1);
  cursor: pointer;
}
/* Enabled-looking when there's a selection (toggled by refreshActionBar). */
.starpaw-photos-clear.is-active {
  color: #fff;
  background: rgba(255, 255, 255, 0.16);
  border-color: rgba(255, 255, 255, 0.32);
}
.starpaw-photos-clear:active { filter: brightness(0.85); }
/* Header published-filter chip — opens a dropdown (All / Published / Unpublished),
   mirroring the dogs-index status/range dropdowns. Lit blue when filtering a subset. */
.starpaw-photos-filter {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 3px 6px 3px 10px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  color: rgba(255, 255, 255, 0.55);
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.12);
  cursor: pointer;
}
.starpaw-photos-filter-caret { font-size: 15px; opacity: 0.6; }
.starpaw-photos-filter.is-active {
  color: #fff;
  background: rgba(74, 139, 255, 0.25);
  border-color: rgba(74, 139, 255, 0.6);
}
.starpaw-photos-filter:active { filter: brightness(0.85); }
/* The dropdown is appended to <body> (escapes the panel's backdrop-filter containing
   block) and sits above the panels + action bar but below the dog modals. */
.starpaw-photos-filter-menu {
  position: fixed;
  z-index: 250;
  min-width: 156px;
  padding: 4px 0;
  border-radius: 12px;
  background: rgba(26, 34, 52, 0.97);
  -webkit-backdrop-filter: blur(16px) saturate(1.4);
  backdrop-filter: blur(16px) saturate(1.4);
  border: 1px solid rgba(255, 255, 255, 0.14);
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.5);
}
.starpaw-photos-filter-opt {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 9px 14px;
  text-align: left;
  font-size: 13px;
  font-weight: 600;
  color: rgba(255, 255, 255, 0.7);
  cursor: pointer;
}
.starpaw-photos-filter-opt .material-symbols-outlined { font-size: 17px; opacity: 0.85; }
.starpaw-photos-filter-opt.is-selected { color: #4A8BFF; font-weight: 700; }
.starpaw-photos-filter-opt:active { background: rgba(255, 255, 255, 0.08); }
/* Published filter — hide thumbs (and their now-empty date groups) that don't match.
   `data-published` is "1"/"0" on each thumb; CSS `:has()` re-evaluates live, so
   publishing/unpublishing a photo while filtered makes it appear/vanish in place. */
/* Collapse a date group with no matching thumbs (drop its separator too). */
/* Published indicator on a strip thumb (Plan 136) */
.starpaw-photos-thumb-wrap {
  position: relative;
  flex-shrink: 0;
  line-height: 0;
  /* Always snap the thumb's LEFT edge (just past the badge). No end-align needed:
     the joystick wraps around at the end and the strip keeps the focused thumb
     visible, so the last thumb never needs to settle flush-right. */
  scroll-snap-align: start;
}
/* Centered, prominent published watermark over the thumb — mirrors the gallery
   grid's `[data-photo-published]` indicator (a large filled `send` with a soft
   shadow), scaled to the 64px strip thumb. */
.starpaw-photos-thumb-pub {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
}
.starpaw-photos-thumb-pub .material-symbols-outlined {
  font-size: 32px;
  color: #fff;
  font-variation-settings: 'FILL' 1, 'wght' 500;
  text-shadow: 0 2px 8px rgba(0, 0, 0, 0.7), 0 0 2px rgba(0, 0, 0, 0.5);
}

/* Plan 182 — Pool | Stream tab toggle in the photos panel header. */
.starpaw-photos-tabs {
  display: inline-flex;
  gap: 2px;
  background: rgba(255, 255, 255, 0.08);
  border-radius: 999px;
  padding: 2px;
}
.starpaw-photos-tab {
  border: none;
  background: transparent;
  color: rgba(255, 255, 255, 0.6);
  font: inherit;
  font-size: 12px;
  font-weight: 600;
  padding: 3px 12px;
  border-radius: 999px;
  cursor: pointer;
  touch-action: manipulation;
}
.starpaw-photos-tab.is-active {
  background: var(--color-brand, #ff7a59);
  color: #fff;
}

/* Plan 182 — Pool: a small paw marks walk-sourced candidates; already-published ones recede. */
.starpaw-photos-thumb-paw {
  position: absolute;
  left: 3px;
  bottom: 3px;
  display: flex;
  pointer-events: none;
}
.starpaw-photos-thumb-paw .material-symbols-outlined {
  font-size: 13px;
  color: #fff;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.85);
}
.starpaw-photos-thumb-wrap.is-pool-published {
  opacity: 0.45;
}
/* Plan 183 — in the Streams tab, drafts (in the stream but not yet owner-visible) recede so the
   published (live) photos stand out. */
.starpaw-photos-thumb-wrap.is-stream-draft {
  opacity: 0.5;
}
/* Photo modal: expanded image + Publish/Share (Plan 136) */
.starpaw-action-modal {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none; /* dim only — a joystick core-tap must reach the star body */
}
.starpaw-action-modal-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.62);
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
  pointer-events: none;
}
.starpaw-action-modal-card {
  position: relative;
  pointer-events: auto;
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-width: 84vw;
  padding: 10px;
  border-radius: 16px;
  background: rgba(20, 22, 28, 0.95);
  border: 1px solid rgba(255, 255, 255, 0.12);
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.6);
  /* Start hidden; revealed (faded in) only once the image content has loaded, so the
     window never flashes an empty/loading frame. Opacity-only — a transform would
     distort getBoundingClientRect while the card is positioned over the star core. */
  opacity: 0;
  transition: opacity 0.16s ease-out;
}
.starpaw-action-modal-card.is-ready {
  opacity: 1;
}
/* Tile variant (e.g. contacts): a fixed-width card so every modal of this kind is
   identical in size regardless of the photo or name length. */
.starpaw-action-modal-card-tile {
  width: 256px;
  max-width: 84vw;
}
.starpaw-action-modal-close {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 1;
  width: 30px;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  cursor: pointer;
  touch-action: manipulation;
}
.starpaw-action-modal-close .material-symbols-outlined {
  font-size: 20px;
}
.starpaw-action-modal-close:active {
  background: rgba(0, 0, 0, 0.75);
}
.starpaw-action-modal-img {
  display: block;
  max-width: 100%;
  max-height: 50vh;
  object-fit: contain;
  border-radius: 10px;
}
/* Slideshow chrome — prev/next chevrons + "n / total" counter over the image. */
.starpaw-action-modal-navwrap {
  position: relative;
  display: inline-block;
  line-height: 0;
  align-self: center;
}
.starpaw-action-modal-chevron {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 36px;
  height: 36px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.45);
  color: #fff;
  border: none;
  cursor: pointer;
  pointer-events: auto;
}
.starpaw-action-modal-chevron .material-symbols-outlined { font-size: 24px; }
.starpaw-action-modal-chevron-left { left: 6px; }
.starpaw-action-modal-chevron-right { right: 6px; }
.starpaw-action-modal-chevron:active { background: rgba(0, 0, 0, 0.65); }
.starpaw-action-modal-counter {
  position: absolute;
  bottom: 8px;
  left: 50%;
  transform: translateX(-50%);
  color: rgba(255, 255, 255, 0.85);
  font-size: 11px;
  font-weight: 600;
  background: rgba(0, 0, 0, 0.45);
  padding: 2px 10px;
  border-radius: 999px;
  line-height: 1.4;
  pointer-events: none;
  white-space: nowrap;
}
/* Tapping the image advances to the next photo (photo modal only). */
.starpaw-action-modal-img-tappable {
  cursor: pointer;
}
/* Fixed standard-size square photo (contacts) — matted with proportional padding and
   framed, so every modal looks the same whether the photo is portrait, landscape, or
   absent. Padding on the <img> insets the photo and reveals the frame background. */
.starpaw-action-modal-img-tile {
  width: 160px;
  height: 160px;
  box-sizing: border-box;
  padding: 14px;
  align-self: center;
  object-fit: cover;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 16px;
  background: rgba(255, 255, 255, 0.06);
}
/* Icon tile shown when there's no image (e.g. a contact with no photo). Same frame. */
.starpaw-action-modal-img-placeholder {
  width: 160px;
  height: 160px;
  box-sizing: border-box;
  align-self: center;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 16px;
  background: rgba(255, 255, 255, 0.06);
}
.starpaw-action-modal-img-placeholder .material-symbols-outlined {
  font-size: 84px;
  color: rgba(255, 255, 255, 0.45);
}
.starpaw-action-modal-caption {
  text-align: center;
  color: #fff;
  font-weight: 700;
  font-size: 15px;
  margin-top: -2px;
  max-width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.starpaw-action-modal-actions {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.starpaw-action-menu-item {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 11px 10px;
  border: none;
  background: rgba(255, 255, 255, 0.08);
  border-radius: 10px;
  color: rgba(255, 255, 255, 0.9);
  font-family: inherit;
  font-weight: 700;
  font-size: 15px;
  cursor: pointer;
  touch-action: manipulation;
}
.starpaw-action-menu-item .material-symbols-outlined {
  font-size: 19px;
}
.starpaw-action-menu-item:active {
  background: rgba(255, 255, 255, 0.16);
}
.starpaw-action-menu-item.is-focused {
  background: var(--color-brand);
  color: #fff;
}
.starpaw-photos-date-sep {
  font-size: 10px;
  font-weight: 600;
  color: rgba(200, 180, 140, 0.7);
  letter-spacing: 0.03em;
  white-space: nowrap;
  padding-left: 1px;
  margin-top: -8px;
  /* Don't let a press/drag over the date label start a text selection. */
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}
.starpaw-photos-empty {
  font-size: 13px;
  color: rgba(255, 255, 255, 0.4);
  font-style: italic;
  padding: 10px 0;
  min-height: 64px;
  display: flex;
  align-items: center;
}
.starpaw-photos-spinner {
  font-size: 20px;
  color: rgba(255, 255, 255, 0.3);
  animation: spin 1s linear infinite;
}
/* Skeleton loading boxes — the thumbs' exact size, so the strip reserves its height (no expansion
   when photos arrive) and each photo fades in over its box (the reveal in _buildThumb). */
.starpaw-photos-empty.starpaw-photos-skeletons {
  display: flex; align-items: flex-end;
  padding: 0; min-height: 64px; font-style: normal;
}
.starpaw-photos-skeleton {
  width: 64px; height: 64px; border-radius: 3px; flex-shrink: 0;
  background: linear-gradient(100deg, rgba(255, 255, 255, 0.05) 30%, rgba(255, 255, 255, 0.14) 50%, rgba(255, 255, 255, 0.05) 70%);
  background-size: 200% 100%;
  animation: starpaw-skeleton-shimmer 1.3s ease-in-out infinite;
}
/* Date-separator placeholder — keeps .starpaw-photos-date-sep's height/offset (so no jump) but
   renders as a short shimmer bar instead of text. */
.starpaw-photos-skeleton-bar {
  width: 42px; color: transparent; border-radius: 3px;
  background: linear-gradient(100deg, rgba(255, 255, 255, 0.05) 30%, rgba(255, 255, 255, 0.14) 50%, rgba(255, 255, 255, 0.05) 70%);
  background-size: 200% 100%;
  animation: starpaw-skeleton-shimmer 1.3s ease-in-out infinite;
}
@keyframes starpaw-skeleton-shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

/* ── Contacts panel ── */
.starpaw-contacts-panel {
  background: rgba(255, 255, 255, 0.12);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  border-radius: 12px;
  padding: 0;
  overflow-y: auto;
  overflow-anchor: none;
  max-height: calc(100dvh - env(safe-area-inset-top) - 104px - 12px - 3.5rem - env(safe-area-inset-bottom));
  touch-action: pan-y;
}
.starpaw-contacts-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 12px;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  position: sticky;
  top: 0;
  z-index: 1;
  background: inherit;
}
.starpaw-contacts-title {
  font-size: 14px;
  font-weight: 700;
  color: white;
  display: flex;
  align-items: center;
  gap: 4px;
}
.starpaw-contacts-body {
  display: flex;
  flex-direction: column;
  padding: 4px 12px 10px;
}
.starpaw-contacts-section {
  display: flex;
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 8px;
  margin-bottom: 6px;
}
.starpaw-contacts-section:last-child {
  margin-bottom: 0;
}
/* Contact row (Plan 152): tapping a phone-bearing contact selects it (single-select);
   the bottom action bar's Text/Call then act on it. */
.starpaw-contacts-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 8px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}
.starpaw-contacts-item:last-child {
  border-bottom: none;
}
.starpaw-contacts-thumb {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  object-fit: cover;
  flex-shrink: 0;
}
.starpaw-contacts-icon {
  font-size: 18px;
  color: rgba(255, 255, 255, 0.5);
  flex-shrink: 0;
}
.starpaw-contacts-info {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: row;
  align-items: baseline;
  gap: 6px;
}
.starpaw-contacts-name {
  font-size: 14px;
  font-weight: 600;
  color: white;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
.starpaw-contacts-role {
  font-size: 12px;
  color: rgba(255, 255, 255, 0.5);
  text-transform: capitalize;
  flex-shrink: 0;
}
/* Inline per-contact actions — round Call / Text / Email buttons at the right of each
   row (Plan 153). Real tel:/sms:/mailto: anchors; each is also a joystick L2 element. */
.starpaw-contacts-actions {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
}
.starpaw-contacts-action {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.1);
  color: #fff;
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
}
.starpaw-contacts-action .material-symbols-outlined { font-size: 17px; }
.starpaw-contacts-action:active { background: rgba(255, 255, 255, 0.22); }
.starpaw-contacts-empty {
  font-size: 13px;
  color: rgba(255, 255, 255, 0.4);
  font-style: italic;
  padding: 10px 0;
}

/* StarPaw Home panel */
.starpaw-home-panel {
  background: rgba(255, 255, 255, 0.12);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  border-radius: 12px;
  padding: 0;
  /* Header + a separately-scrolling body below it: the body clips the rolodex cards so
     they never slide up into the (glass) header. The panel itself doesn't scroll. */
  display: flex;
  flex-direction: column;
  overflow: hidden;
  max-height: calc(100dvh - env(safe-area-inset-top) - 104px - 12px - 3.5rem - env(safe-area-inset-bottom));
  touch-action: pan-y;
}
.starpaw-home-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 12px;
  flex-shrink: 0;
  position: relative;
  z-index: 1;
  /* Brand-blue glass (black is reserved for the Photos header). Stays translucent —
     nothing slides behind it now that the body clips the rolodex cards. */
  background: rgba(36, 58, 120, 0.55);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
}
.starpaw-home-title {
  font-size: 14px;
  font-weight: 700;
  color: white;
  display: flex;
  align-items: center;
  gap: 4px;
}
.starpaw-home-body {
  display: flex;
  flex-direction: column;
  padding: 4px 12px 10px;
  /* The scroll + clip region, below the glass header. A rolodex card sliding up clips
     at this container's top edge instead of showing through the header. */
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  overflow-anchor: none;
}
.starpaw-home-section {
  display: flex;
  position: relative; /* anchors the marching-ants selection ring (::after) */
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 8px;
  margin-bottom: 6px;
}
.starpaw-home-section:last-child {
  margin-bottom: 0;
}
/* Contacts lead each Home section (Plan 134); divide them from the home rows
   below. No divider when the section is contacts-only. */
.starpaw-home-contacts:not(:last-child) {
  border-bottom: 1px solid rgba(255, 255, 255, 0.12);
}
.starpaw-home-row {
  display: flex;
  align-items: flex-start;
  gap: 6px;
  padding: 5px 8px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}
.starpaw-home-row:last-of-type {
  border-bottom: none;
}
.starpaw-home-icon {
  font-size: 16px;
  color: rgba(255, 255, 255, 0.5);
  flex-shrink: 0;
  margin-top: 1px;
}
/* Countdown digit replaces the lock icon while PIN is revealed */
.starpaw-countdown {
  font-family: var(--font-display), system-ui, sans-serif;
  font-size: 14px;
  font-weight: 700;
  min-width: 1em;
  text-align: center;
}
.starpaw-home-link {
  font-size: 14px;
  font-weight: 600;
  color: #8bbcff;
  text-decoration: underline;
  text-decoration-color: rgba(139, 188, 255, 0.35);
}
.starpaw-home-link:active {
  color: inherit;
}
.starpaw-home-code {
  font-size: 15px;
  font-weight: 700;
  color: rgba(255, 255, 255, 0.85);
  font-family: monospace;
  letter-spacing: 2px;
}
.starpaw-home-reveal {
  background: none;
  border: none;
  color: rgba(255, 255, 255, 0.5);
  padding: 0 2px;
  cursor: pointer;
  flex-shrink: 0;
}
.starpaw-home-reveal:active {
  color: white;
}
.starpaw-home-instructions .starpaw-home-text {
  font-size: 14px;
  color: rgba(255, 255, 255, 0.7);
  line-height: 1.4;
  white-space: pre-wrap;
}
.starpaw-home-photos {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 5px 8px;
}
.starpaw-home-photo {
  width: 56px;
  height: 56px;
  object-fit: cover;
  border-radius: 8px;
  cursor: pointer;
}
.starpaw-home-photo:active {
  opacity: 0.7;
}
.starpaw-home-empty {
  font-size: 13px;
  color: rgba(255, 255, 255, 0.4);
  font-style: italic;
  padding: 10px 0;
}

/* Freeze underlying page scroll while a Call-in animation is playing
   (ad-hoc 2026-04-10). Scoped narrowly to the arrival animation — not
   the whole visible lifetime — so regular page scroll still works once
   the Dog Star has landed. `overflow: hidden` covers desktop + Android;
   `touch-action: none` covers iOS Safari, which otherwise ignores
   `overflow: hidden` for touch-driven scroll. Applied to both <html>
   and <body> because browsers differ on which element actually owns
   the viewport scroll. */
html:has(body.star-incoming),
body.star-incoming {
  overflow: hidden;
  touch-action: none;
  overscroll-behavior: none;
}

/* ── Radial photo menu orbit ── */
[data-quick-photo-target="radial"],
.orbit-ring,
.orbit-ring ~ button,
[data-dog-thumb] {
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
  -webkit-user-drag: none;
}
[data-dog-thumb] img {
  -webkit-user-drag: none;
  -webkit-touch-callout: none;
  user-select: none;
  pointer-events: none;
}

/* Body (shutter) button — centering transform is inline (must stay inline
   so .tactile:active doesn't replace it). Collapse rules use !important
   to override during the hide/show animation. */

.orbit-ring {
  position: relative;
  width: 272px;
  height: 272px;
  pointer-events: none;
}

/* Plan 038: inner-gap hit target. A transparent circular layer centered
   on the radial, sized to fill the annulus between the Body (96px
   diameter → 48px radius) and Ring 1 (slot inner edge at ~80px radius).
   In edit mode it becomes a rotation start zone; in view mode it's
   inert so it doesn't swallow taps that would otherwise extend/contract
   the Dog Star through the Body. */
.star-inner-gap {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 160px;
  height: 160px;
  margin: -80px 0 0 -80px;
  border-radius: 50%;
  pointer-events: none;
  /* No z-index: rely on DOM paint order. The Body button is a later
     sibling, so its 96px Body naturally wins hit-testing inside the
     inner-gap's disc (the core + crust stay draggable in edit mode),
     while the inner-gap still claims the surrounding annulus up to
     Ring 1's inner edge. With an explicit z-index, the inner-gap
     floated above the Body button and swallowed every core/crust
     tap in edit mode. */
}
body.photo-orbit-edit .star-inner-gap {
  pointer-events: auto;
}

/* Gather dot — red pip on the action planet when dogs are gathered.
   Centered on top of the action planet. */
.orbit-slot-preselect-dot {
  position: absolute;
  top: -3px;
  left: 50%;
  transform: translateX(-50%);
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: #ef4444;
  box-shadow: 0 0 0 1.5px var(--color-brand), 0 0 0 3px rgba(255, 255, 255, 0.9);
  pointer-events: none;
  z-index: 2;
}


/* Batch-capacity indicator dots on action planets. Sits just outside the
   planet on the outer edge (away from Body center), arranged tangentially
   (perpendicular to the radial direction). 1 dot = single-dog action,
   2 dots = batch action. The wrapper rotates by the slot angle so its
   local Y axis aligns with the radial; the dots line up along local X
   (the tangent). */
.orbit-action-capacity {
  position: absolute;
  top: 50%;
  left: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  /* Rotate to align with radial, then push outward along local -Y
     (toward the outer edge, away from Body center). */
  transform: translate(-50%, -50%)
             rotate(var(--slot-angle))
             translateY(-29px);
  pointer-events: none;
  z-index: 2;
}
.orbit-action-capacity-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(0,0,0,0.35) 2px, rgba(255,255,255,0.85) 2px);
  box-shadow: 0 0 0 0.5px rgba(0, 0, 0, 0.15);
}

/* Anchor button (plan 032) — pins the Dog Star in place so accidental drags
   while scrolling can't move it. Positioned just outside the Body at the
   bottom-right (~315°, offset 46px from radial center). Visible only when
   the rings are retracted; the `:has()` rule hides it whenever the orbit
   ring is visible (no `.hidden` class). Base look is a small brand circle;
   active (anchored) state takes on the frosted-glass look from the Body
   core so the state is instantly readable. */
/* Schedule tape day tap — small radiating ring from the tapped dot */
@keyframes tape-day-pulse {
  0%   { box-shadow: 0 0 0 0 var(--color-brand-a40, rgba(255, 107, 74, 0.5)); }
  100% { box-shadow: 0 0 0 10px transparent; }
}

/* Plan 037: satellite mirror of the anchor button. Cycles the active
   dog group on tap, toggles alphabetical on long-press (manual groups
   only), slides along a fixed-radius arc in edit mode. Default 225°
   (bottom-left) — diagonally opposite the anchor's default 45°. Icon
   glyph (digit/letter/paw) stays upright regardless of orbit angle. */
/* Ad-hoc 2026-04-11: `@property` registration for the group button's
   radial-pull offset, same rationale as `--anchor-pull`. */
@property --group-pull {
  syntax: "<length>";
  inherits: false;
  initial-value: 0px;
}
/* Register orbit/group radii so they can be transitioned for the
   retract-to-center animation when planets are hidden. */
@property --orbit-radius {
  syntax: "<length>";
  inherits: false;
  initial-value: -152px;
}
@property --group-radius {
  syntax: "<length>";
  inherits: false;
  initial-value: 65px;
}
.star-group {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 26px;
  height: 26px;
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
  border-radius: 9999px;
  --group-angle: 45deg;
  --group-radius: 65px;
  /* Ad-hoc 2026-04-11: radial-pull offset added to the base radius.
     In edit mode the walker can drag the group button outward along
     its angle; on release the value transitions back to 0 via
     `.group-pull-return`. Pulling past the commit threshold clears
     the currently active dog group. */
  --group-pull: 0px;
  transform: translate(-50%, -50%)
             translate(calc(cos(var(--group-angle)) * (var(--group-radius) + var(--group-pull))),
                       calc(sin(var(--group-angle)) * (var(--group-radius) + var(--group-pull))));
  display: flex;
  align-items: center;
  justify-content: center;
  /* Match the anchor's glass aesthetic so the button reads as a
     control rather than a notification badge. Brand-filled + white
     digit + dark backdrop looked like an unread-count pip; glass +
     brand-colored glyph reads as a sibling of the anchor knob. */
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 26 26'%3E%3Cdefs%3E%3CradialGradient id='s' cx='40%25' cy='35%25' r='55%25'%3E%3Cstop offset='0%25' stop-color='%23D8E8FF'/%3E%3Cstop offset='40%25' stop-color='%23A0C4F0'/%3E%3Cstop offset='75%25' stop-color='%235882B0'/%3E%3Cstop offset='100%25' stop-color='%231E3550'/%3E%3C/radialGradient%3E%3CradialGradient id='h' cx='38%25' cy='28%25' r='25%25'%3E%3Cstop offset='0%25' stop-color='%23fff' stop-opacity='0.7'/%3E%3Cstop offset='100%25' stop-color='%23fff' stop-opacity='0'/%3E%3C/radialGradient%3E%3C/defs%3E%3Ccircle cx='13' cy='13' r='13' fill='url(%23s)'/%3E%3Ccircle cx='13' cy='13' r='13' fill='url(%23h)'/%3E%3C/svg%3E") no-repeat center / contain;
  color: white;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
  border: none;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);
  cursor: pointer;
  transition: background 0.2s ease, backdrop-filter 0.2s ease,
              border-color 0.2s ease, color 0.2s ease, opacity 0.2s ease;
  pointer-events: auto;
  padding: 0;
  z-index: 5;
}
.star-group:active {
  transform: translate(-50%, -50%)
             translate(calc(cos(var(--group-angle)) * (var(--group-radius) + var(--group-pull))),
                       calc(sin(var(--group-angle)) * (var(--group-radius) + var(--group-pull))))
             scale(0.9);
}
.star-group::before {
  content: "";
  position: absolute;
  inset: -16px;
  border-radius: 9999px;
}
/* Gravitational lensing — Sirius B warps spacetime around it.
   Light bends around the white dwarf rather than passing through,
   concentrating at the limb (Einstein ring). Thin bright rim where
   deflected light paths converge, plus soft outer glow. */
.star-group::after {
  content: "";
  position: absolute;
  inset: -6px;
  border-radius: 9999px;
  border: 1.5px solid rgba(200, 220, 255, 0.4);
  box-shadow: 0 0 6px 1px rgba(200, 220, 255, 0.25);
  z-index: -1;
  pointer-events: none;
}
.star-group-inner {
  width: 100%;
  height: 100%;
  border-radius: 9999px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0;
  transform: translateY(-1px);
  filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.6))
          drop-shadow(0 2px 4px rgba(0, 0, 0, 0.4))
          drop-shadow(0 0 8px rgba(0, 0, 0, 0.25));
}
/* Coin-spin loading state: the paw/group control flips on its vertical axis
   (rotateY) like a spinning coin while we wait for the AI to respond. Toggled
   by show/hideThinkingBubble() in listening_mode.js. perspective() gives the 3D
   coin depth; the static -1px nudge is preserved; default backface stays visible
   so the reverse face shows mid-flip, like a real coin. */
@keyframes star-group-coin-spin {
  from { transform: perspective(240px) translateY(-1px) rotateY(0deg); }
  to   { transform: perspective(240px) translateY(-1px) rotateY(360deg); }
}
.star-group-inner.coin-spin {
  animation: star-group-coin-spin 1s linear infinite;
}
@keyframes group-tap-wave {
  0%   { box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.5); }
  100% { box-shadow: 0 0 0 14px rgba(255, 255, 255, 0); }
}
.group-tap-wave {
  animation: group-tap-wave 0.35s ease-out forwards;
}
.star-group .orbit-slot-group-paw {
  font-size: 16px;
  font-variation-settings: "FILL" 1, "wght" 500;
  line-height: 1;
}
.star-group .orbit-slot-group-calendar-paw {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
}
.star-group .orbit-slot-group-cal-icon {
  font-size: 18px;
  font-variation-settings: "FILL" 0, "wght" 400;
  line-height: 1;
}
.star-group .orbit-slot-group-cal-paw {
  position: absolute;
  top: calc(55% + 1px);
  left: 50%;
  transform: translate(-50%, -50%);
  font-size: 8px;
  font-variation-settings: "FILL" 1, "wght" 500;
  line-height: 1;
}
/* Numbered badge marking which Today group when Today overflows into several. */
.star-group .orbit-slot-group-today-badge {
  position: absolute;
  bottom: -1px;
  right: -1px;
  min-width: 13px;
  height: 13px;
  padding: 0 2px;
  box-sizing: border-box;
  border-radius: 7px;
  background: #4A8BFF;
  color: #fff;
  font-size: 8px;
  font-weight: 700;
  line-height: 13px;
  text-align: center;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
}
.star-group .orbit-slot-group-dots {
  display: flex;
  gap: 2px;
  margin-top: -1px;
}
.star-group .orbit-slot-group-dot {
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.4);
  flex-shrink: 0;
}
.star-group .orbit-slot-group-dot-filled {
  background: white;
}
/* Frown arc — outer dots sit higher than center dot */
.star-group .orbit-slot-group-dot:first-child,
.star-group .orbit-slot-group-dot:last-child {
  transform: translateY(-1.5px);
}
.star-group.group-grip {
  animation: group-grip-wave 0.9s ease-out infinite;
}
@keyframes group-grip-wave {
  0%   { box-shadow: 0 0 0 0 rgba(74, 139, 255, 0.7); }
  100% { box-shadow: 0 0 0 50px rgba(74, 139, 255, 0); }
}
/* Group satellite feedback: white confirmation ring (matches core tap). */
.star-group.group-save-pulse {
  animation: group-save-wave 0.4s ease-out;
}
@keyframes group-save-wave {
  0%   { box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.5); }
  100% { box-shadow: 0 0 0 24px rgba(255, 255, 255, 0); }
}
/* Snap-back transition for the group's radial-pull gesture.
   Ease-out-back curve for a tactile rubber-band rebound feel. */
.star-group.group-pull-return {
  transition: --group-pull 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
}

/* Hide star behind the white dwarf — elements translate toward the dwarf
   while simultaneously shrinking, as if receding into the distance behind
   it. Uses --collapse-dx/dy set by JS from the dwarf's computed position. */
.star-collapsed .orbit-ring,
.star-collapsed .star-corona-layer,
.star-collapsed .star-burst-layer,
.star-collapsed .star-deco-ring,
.star-collapsed .star-inner-gap,
.star-collapsed .star-rotate-arrows {
  transform: translate(var(--collapse-dx, 0px), var(--collapse-dy, 0px)) scale(0);
  opacity: 0;
  transition: transform 0.45s cubic-bezier(0.4, 0, 1, 1), opacity 0.35s ease;
}
.star-collapsed .star-body-btn {
  transform: translate(calc(-50% + var(--collapse-dx, 0px)), calc(-50% + var(--collapse-dy, 0px))) scale(0) !important;
  opacity: 0 !important;
  transition: transform 0.45s cubic-bezier(0.4, 0, 1, 1), opacity 0.35s ease !important;
}
/* Reverse: star emerges from behind the dwarf, growing as it approaches. */
.star-uncollapsing .orbit-ring,
.star-uncollapsing .star-corona-layer,
.star-uncollapsing .star-burst-layer,
.star-uncollapsing .star-deco-ring,
.star-uncollapsing .star-inner-gap,
.star-uncollapsing .star-rotate-arrows,
.star-uncollapsing .star-body-btn {
  transition: transform 0.4s cubic-bezier(0, 0, 0.2, 1), opacity 0.3s ease !important;
}

/* Idle rest — the CONCEALED star, untouched for a few seconds (JS adds
   body.star-resting via _scheduleStarRest), settles: the corona fades out and
   the core DIMS. Any interaction (wakeStar) removes the class and they
   return. Only ever present alongside .star-concealed, so it never affects the
   extended star. The fade-out is gentle (0.6s); the fade-back on wake is the
   element's own quicker transition, so waking feels responsive.
   Dimming is a brightness FILTER, not opacity — the resting core floats over
   panel content (strips of dog avatars), and an opacity dim made it see-through,
   showing "dog planets" lined up behind the core (reported 2026-07-02). A
   filter keeps the core opaque (it still occludes) while reading as asleep. */
body.star-resting .star-corona-layer {
  opacity: 0;
  transition: opacity 0.6s ease;
}
body.star-resting .star-body-btn {
  filter: brightness(0.45) saturate(0.75);
  transition: filter 0.6s ease;
}

/* Generic long-press confirmation wave, reused for any small circular
   target that wants a post-fire "done" feedback. Applied by
   `_playLongPressWave(el)` to an element that is already circular
   (e.g. `.orbit-slot-img`). Also reused by group button feedback. */
@keyframes long-press-wave {
  0%   { box-shadow: 0 0 0 0 rgba(255, 107, 74, 0.7); }
  100% { box-shadow: 0 0 0 50px rgba(255, 107, 74, 0); }
}
.long-press-wave {
  animation: long-press-wave 0.6s ease-out !important;
}

@keyframes orbit-tap-wave {
  0%   { box-shadow: 0 0 0 0 rgba(255, 107, 74, 0.6); }
  100% { box-shadow: 0 0 0 18px rgba(255, 107, 74, 0); }
}
.orbit-tap-wave {
  animation: orbit-tap-wave 0.35s ease-out !important;
  border-radius: 50%;
}

.orbit-slot {
  position: absolute;
  top: 50%; left: 50%;
  width: 39px;
  margin-left: -19.5px;
  margin-top: -19.5px;
  --orbit-radius: -152px;
  --slot-angle: calc(var(--angle) + var(--orbit-rotation, 0deg));
  transform: rotate(var(--slot-angle)) translateY(var(--orbit-radius)) rotate(calc(-1 * var(--slot-angle)));
  display: flex;
  flex-direction: column;
  align-items: center;
  cursor: pointer;
  transition: box-shadow 0.15s;
  pointer-events: auto;
}
/* Invisible expanded hit area for dog planets — 59px tap target
   around the 39px visual. Action planets excluded via [data-quick-action]. */
.orbit-slot:not([data-quick-action])::before {
  content: "";
  position: absolute;
  inset: -10px;
  border-radius: 50%;
}

/* Plan 088: orbit-expand/retract animations removed — orbits are always
   visible when the star is shown. Slots use their final position directly
   via the inline transform set by the renderer. */
.orbit-slot-img {
  width: 39px; height: 39px;
  border-radius: 50%;
  overflow: hidden;
  /* Layered shadow: a tight contact shadow + a wider ambient glow, both
     soft, so the planet reads as lifted off the page without looking
     stamped. */
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.22),
              0 4px 12px rgba(0, 0, 0, 0.18);
}
/* Ad-hoc 2026-04-11: suppress iOS Safari's native image long-press
   behaviour on orbit thumbnails. Without these, iOS hijacks our 500ms
   long-press timer by starting its own image drag-preview (the raw,
   un-clipped image) and share/save callout, which manifests as a
   "square thumbnail" appearing over the properly-clipped planet and
   being draggable away from it. `-webkit-touch-callout: none` kills
   the share menu; `-webkit-user-drag: none` kills the drag preview;
   `user-select: none` blocks any text-selection fallback. Applied to
   both the `.orbit-slot-img` div and the child `<img>` so iOS has
   nothing to latch onto. */
.orbit-slot-img,
.orbit-slot-img img {
  -webkit-touch-callout: none;
  -webkit-user-drag: none;
  user-select: none;
  -webkit-user-select: none;
}
.orbit-slot-img.orbit-just-added {
  animation: orbit-add-wave 0.5s ease-out both;
}
@keyframes orbit-add-wave {
  0%   { box-shadow: 0 0 0 0 rgba(74, 139, 255, 0.5),
                     0 1px 2px rgba(0,0,0,0.22), 0 4px 12px rgba(0,0,0,0.18); }
  100% { box-shadow: 0 0 0 10px rgba(74, 139, 255, 0),
                     0 1px 2px rgba(0,0,0,0.22), 0 4px 12px rgba(0,0,0,0.18); }
}
.orbit-slot:has(.orbit-slot-empty) .orbit-slot-img {
  box-shadow: none;
}
.orbit-slot:has(.orbit-slot-empty) {
  cursor: default;
}
body:not(.photo-orbit-edit) .orbit-slot:has(.orbit-slot-empty) {
  visibility: hidden;
  pointer-events: none;
}
.orbit-slot.orbit-dim {
  opacity: 0 !important;
}
/* Subtle de-emphasis on non-gathered dog planets when at least one dog
   is gathered.  Just enough to let the gathered outlines pop without
   making the rest feel hidden. */
.orbit-slot-img.orbit-ungathered {
  opacity: 0.9;
  transition: opacity 0.15s ease-out;
}
/* Grey/cancel dogs already look distinct — keep image at full opacity,
   only dim via the name rule below. */
.orbit-slot-img.orbit-ungathered[data-unsatisfiable] {
  opacity: 1;
}
.orbit-slot-img.orbit-ungathered ~ .orbit-slot-name {
  opacity: 1;
  transition: opacity 0.15s ease-out;
}
/* Ensure gathered / deselecting slots snap back to full opacity
   in case the class was briefly present before being replaced. */
.orbit-slot-img.orbit-gathered {
  opacity: 1;
}
/* Steady pulse on Body core in editable mode — uses box-shadow glow
   instead of opacity so the element always receives pointer events. */
@keyframes shutter-edit-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(255, 107, 74, 0.7), 0 8px 30px rgba(0,0,0,0.35); }
  50% { box-shadow: 0 0 0 14px rgba(255, 107, 74, 0), 0 8px 30px rgba(0,0,0,0.35); }
}
.shutter-edit-pulse {
  /* Edit mode indicated by faster ray spin + "Edit" label instead */
}
/* Star core — 3D sphere (Sirius blue-white) */
.star-core-sphere {
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 60 60'%3E%3Cdefs%3E%3CradialGradient id='s' cx='50%25' cy='50%25' r='50%25'%3E%3Cstop offset='0%25' stop-color='%23FFFFFF'/%3E%3Cstop offset='25%25' stop-color='%23E0EEFF'/%3E%3Cstop offset='55%25' stop-color='%23A0CCFF'/%3E%3Cstop offset='80%25' stop-color='%235A9AE8'/%3E%3Cstop offset='100%25' stop-color='%233070C0'/%3E%3C/radialGradient%3E%3C/defs%3E%3Ccircle cx='30' cy='30' r='30' fill='url(%23s)'/%3E%3C/svg%3E") no-repeat center / contain;
}

/* Sphere shading overlay — curves photo thumbnails to match the core shape.
   Hidden when core is empty (sphere background acts as its own light source). */
[data-quick-photo-target="shutterCore"]:not(.star-core-sphere)::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 60 60'%3E%3Cdefs%3E%3CradialGradient id='s' cx='40%25' cy='35%25' r='50%25'%3E%3Cstop offset='0%25' stop-color='%23fff' stop-opacity='0.12'/%3E%3Cstop offset='15%25' stop-color='%23fff' stop-opacity='0.06'/%3E%3Cstop offset='30%25' stop-color='%23fff' stop-opacity='0'/%3E%3Cstop offset='70%25' stop-color='%23000' stop-opacity='0'/%3E%3Cstop offset='80%25' stop-color='%23000' stop-opacity='0.03'/%3E%3Cstop offset='88%25' stop-color='%23000' stop-opacity='0.08'/%3E%3Cstop offset='94%25' stop-color='%23000' stop-opacity='0.15'/%3E%3Cstop offset='100%25' stop-color='%23000' stop-opacity='0.25'/%3E%3C/radialGradient%3E%3C/defs%3E%3Ccircle cx='30' cy='30' r='30' fill='url(%23s)'/%3E%3C/svg%3E") no-repeat center / contain;
  pointer-events: none;
  z-index: 1;
}

/* Hide starburst rays when star is floating without StarView */
.star-burst-layer {
  opacity: 0;
  transition: opacity 0.25s ease;
}
body[data-quick-photo-state-value="starview"] .star-burst-layer,
body[data-quick-photo-state-value="listening"] .star-burst-layer {
  opacity: 1;
}
/* Listening mode — intense, realistic corona without planets to obscure.
   Both corona and starburst outer edges are heavily feathered to avoid a disc look. */
body.listening-adorned .star-corona-layer {
  transform: translate(-50%, -50%) scale(1.56);
  background: radial-gradient(
    circle at center,
    rgba(220, 235, 255, 0.95) 5%,
    rgba(200, 225, 255, 0.8) 8%,
    rgba(180, 215, 255, 0.65) 11%,
    rgba(170, 200, 245, 0.5) 14%,
    rgba(180, 190, 230, 0.38) 18%,
    rgba(200, 180, 210, 0.28) 23%,
    rgba(220, 170, 180, 0.20) 28%,
    rgba(240, 160, 140, 0.14) 34%,
    rgba(255, 150, 110, 0.09) 40%,
    rgba(255, 140, 90, 0.06) 47%,
    rgba(255, 130, 75, 0.035) 55%,
    rgba(255, 120, 60, 0.02) 63%,
    rgba(255, 110, 50, 0.01) 72%,
    rgba(255, 100, 42, 0.004) 82%,
    rgba(255, 95, 35, 0.001) 92%,
    transparent 100%
  );
  filter: blur(8px);
  transition: width 0.8s ease-out, height 0.8s ease-out, filter 0.8s ease-out;
}
body.listening-adorned .star-burst-layer {
  -webkit-mask-image: radial-gradient(circle,
    transparent 6%,
    rgba(255,255,255,0.02) 10%,
    rgba(255,255,255,0.05) 14%,
    rgba(255,255,255,0.1) 18%,
    rgba(255,255,255,0.18) 22%,
    rgba(255,255,255,0.3) 26%,
    rgba(255,255,255,0.5) 30%,
    rgba(255,255,255,0.7) 34%,
    rgba(255,255,255,0.85) 38%,
    white 42%,
    rgba(255,255,255,0.6) 52%,
    rgba(255,255,255,0.3) 62%,
    rgba(255,255,255,0.12) 72%,
    rgba(255,255,255,0.04) 82%,
    transparent 92%);
  mask-image: radial-gradient(circle,
    transparent 6%,
    rgba(255,255,255,0.02) 10%,
    rgba(255,255,255,0.05) 14%,
    rgba(255,255,255,0.1) 18%,
    rgba(255,255,255,0.18) 22%,
    rgba(255,255,255,0.3) 26%,
    rgba(255,255,255,0.5) 30%,
    rgba(255,255,255,0.7) 34%,
    rgba(255,255,255,0.85) 38%,
    white 42%,
    rgba(255,255,255,0.6) 52%,
    rgba(255,255,255,0.3) 62%,
    rgba(255,255,255,0.12) 72%,
    rgba(255,255,255,0.04) 82%,
    transparent 92%);
}


/* Star corona — inside radial, rendered before orbit so it sits beneath planets */
.star-corona-layer {
  position: absolute;
  width: 320px;
  height: 320px;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  transition: transform 0.6s ease-out, filter 0.6s ease-out;
  background: radial-gradient(
    circle at center,
    rgba(200, 220, 255, 0.7) 9%,
    rgba(180, 210, 255, 0.56) 12%,
    rgba(170, 200, 245, 0.45) 15%,
    rgba(180, 190, 230, 0.36) 18%,
    rgba(200, 180, 210, 0.29) 22%,
    rgba(220, 170, 180, 0.23) 26%,
    rgba(240, 160, 140, 0.18) 30%,
    rgba(255, 150, 110, 0.14) 35%,
    rgba(255, 140, 90, 0.11) 40%,
    rgba(255, 130, 75, 0.08) 45%,
    rgba(255, 120, 60, 0.05) 52%,
    rgba(255, 110, 50, 0.03) 60%,
    rgba(255, 100, 42, 0.01) 70%,
    transparent 85%
  );
  pointer-events: none;
}
/* Starburst rays — five-fold luminous glow, old-world esoteric feel.
   Layered conic gradients create soft light wedges in pentagonal symmetry.
   Sirius blue-white tones bleed outward through the corona. */
.star-burst-layer {
  position: absolute;
  width: 320px;
  height: 320px;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  pointer-events: none;
  animation: star-burst-spin 90s linear infinite;
  /* 5 primary rays — diffuse light beams with heavy feathering.
     Each ray ~22° wide with 6° fade-in, 10° plateau, 6° fade-out.
     Extra sub-threshold steps near the edges dissolve the boundary. */
  background:
    conic-gradient(
      from 0deg,
      transparent 0deg,
      rgba(210, 228, 255, 0.02) 1deg,
      rgba(210, 228, 255, 0.06) 3deg,
      rgba(210, 228, 255, 0.12) 5deg,
      rgba(220, 235, 255, 0.2) 7deg,
      rgba(220, 235, 255, 0.25) 9deg,
      rgba(220, 235, 255, 0.25) 13deg,
      rgba(220, 235, 255, 0.2) 15deg,
      rgba(210, 228, 255, 0.12) 17deg,
      rgba(210, 228, 255, 0.06) 19deg,
      rgba(210, 228, 255, 0.02) 21deg,
      transparent 22deg,
      transparent 72deg,
      rgba(210, 228, 255, 0.02) 73deg,
      rgba(210, 228, 255, 0.06) 75deg,
      rgba(210, 228, 255, 0.12) 77deg,
      rgba(220, 235, 255, 0.2) 79deg,
      rgba(220, 235, 255, 0.25) 81deg,
      rgba(220, 235, 255, 0.25) 85deg,
      rgba(220, 235, 255, 0.2) 87deg,
      rgba(210, 228, 255, 0.12) 89deg,
      rgba(210, 228, 255, 0.06) 91deg,
      rgba(210, 228, 255, 0.02) 93deg,
      transparent 94deg,
      transparent 144deg,
      rgba(210, 228, 255, 0.02) 145deg,
      rgba(210, 228, 255, 0.06) 147deg,
      rgba(210, 228, 255, 0.12) 149deg,
      rgba(220, 235, 255, 0.2) 151deg,
      rgba(220, 235, 255, 0.25) 153deg,
      rgba(220, 235, 255, 0.25) 157deg,
      rgba(220, 235, 255, 0.2) 159deg,
      rgba(210, 228, 255, 0.12) 161deg,
      rgba(210, 228, 255, 0.06) 163deg,
      rgba(210, 228, 255, 0.02) 165deg,
      transparent 166deg,
      transparent 216deg,
      rgba(210, 228, 255, 0.02) 217deg,
      rgba(210, 228, 255, 0.06) 219deg,
      rgba(210, 228, 255, 0.12) 221deg,
      rgba(220, 235, 255, 0.2) 223deg,
      rgba(220, 235, 255, 0.25) 225deg,
      rgba(220, 235, 255, 0.25) 229deg,
      rgba(220, 235, 255, 0.2) 231deg,
      rgba(210, 228, 255, 0.12) 233deg,
      rgba(210, 228, 255, 0.06) 235deg,
      rgba(210, 228, 255, 0.02) 237deg,
      transparent 238deg,
      transparent 288deg,
      rgba(210, 228, 255, 0.02) 289deg,
      rgba(210, 228, 255, 0.06) 291deg,
      rgba(210, 228, 255, 0.12) 293deg,
      rgba(220, 235, 255, 0.2) 295deg,
      rgba(220, 235, 255, 0.25) 297deg,
      rgba(220, 235, 255, 0.25) 301deg,
      rgba(220, 235, 255, 0.2) 303deg,
      rgba(210, 228, 255, 0.12) 305deg,
      rgba(210, 228, 255, 0.06) 307deg,
      rgba(210, 228, 255, 0.02) 309deg,
      transparent 310deg,
      transparent 360deg
    ),
    /* 5 secondary rays — dimmer, offset 36°, same feathering profile */
    conic-gradient(
      from 36deg,
      transparent 0deg,
      rgba(200, 220, 255, 0.02) 1deg,
      rgba(200, 220, 255, 0.05) 3deg,
      rgba(200, 220, 255, 0.1) 5deg,
      rgba(200, 220, 255, 0.14) 7deg,
      rgba(200, 220, 255, 0.14) 11deg,
      rgba(200, 220, 255, 0.1) 13deg,
      rgba(200, 220, 255, 0.05) 15deg,
      rgba(200, 220, 255, 0.02) 17deg,
      transparent 18deg,
      transparent 72deg,
      rgba(200, 220, 255, 0.02) 73deg,
      rgba(200, 220, 255, 0.05) 75deg,
      rgba(200, 220, 255, 0.1) 77deg,
      rgba(200, 220, 255, 0.14) 79deg,
      rgba(200, 220, 255, 0.14) 83deg,
      rgba(200, 220, 255, 0.1) 85deg,
      rgba(200, 220, 255, 0.05) 87deg,
      rgba(200, 220, 255, 0.02) 89deg,
      transparent 90deg,
      transparent 144deg,
      rgba(200, 220, 255, 0.02) 145deg,
      rgba(200, 220, 255, 0.05) 147deg,
      rgba(200, 220, 255, 0.1) 149deg,
      rgba(200, 220, 255, 0.14) 151deg,
      rgba(200, 220, 255, 0.14) 155deg,
      rgba(200, 220, 255, 0.1) 157deg,
      rgba(200, 220, 255, 0.05) 159deg,
      rgba(200, 220, 255, 0.02) 161deg,
      transparent 162deg,
      transparent 216deg,
      rgba(200, 220, 255, 0.02) 217deg,
      rgba(200, 220, 255, 0.05) 219deg,
      rgba(200, 220, 255, 0.1) 221deg,
      rgba(200, 220, 255, 0.14) 223deg,
      rgba(200, 220, 255, 0.14) 227deg,
      rgba(200, 220, 255, 0.1) 229deg,
      rgba(200, 220, 255, 0.05) 231deg,
      rgba(200, 220, 255, 0.02) 233deg,
      transparent 234deg,
      transparent 288deg,
      rgba(200, 220, 255, 0.02) 289deg,
      rgba(200, 220, 255, 0.05) 291deg,
      rgba(200, 220, 255, 0.1) 293deg,
      rgba(200, 220, 255, 0.14) 295deg,
      rgba(200, 220, 255, 0.14) 299deg,
      rgba(200, 220, 255, 0.1) 301deg,
      rgba(200, 220, 255, 0.05) 303deg,
      rgba(200, 220, 255, 0.02) 305deg,
      transparent 306deg,
      transparent 360deg
    );
  /* Radial fade — very gradual inner dissolve so rays emerge softly from the core.
     Many sub-threshold steps at the inner edge prevent hard wedge bases. */
  -webkit-mask-image: radial-gradient(circle,
    transparent 8%,
    rgba(255,255,255,0.02) 12%,
    rgba(255,255,255,0.05) 15%,
    rgba(255,255,255,0.10) 18%,
    rgba(255,255,255,0.18) 21%,
    rgba(255,255,255,0.30) 25%,
    rgba(255,255,255,0.45) 29%,
    rgba(255,255,255,0.65) 33%,
    rgba(255,255,255,0.85) 37%,
    white 42%,
    white 50%,
    transparent 80%);
  mask-image: radial-gradient(circle,
    transparent 8%,
    rgba(255,255,255,0.02) 12%,
    rgba(255,255,255,0.05) 15%,
    rgba(255,255,255,0.10) 18%,
    rgba(255,255,255,0.18) 21%,
    rgba(255,255,255,0.30) 25%,
    rgba(255,255,255,0.45) 29%,
    rgba(255,255,255,0.65) 33%,
    rgba(255,255,255,0.85) 37%,
    white 42%,
    white 50%,
    transparent 80%);
}
@keyframes star-burst-spin {
  from { transform: translate(-50%, -50%) rotate(0deg); }
  to   { transform: translate(-50%, -50%) rotate(360deg); }
}
/* Edit mode: rays spin faster + gentle pulsate */
.photo-orbit-edit .star-burst-layer {
  animation: star-burst-spin 30s linear infinite, star-burst-pulse 1s ease-in-out infinite;
}
@keyframes star-burst-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.3; }
}
/* Rotation hint arrows — rotate with planets via --orbit-rotation */
.star-rotate-arrows {
  position: absolute;
  width: 200px;
  height: 200px;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%) rotate(calc(var(--orbit-rotation, 0deg) - 45deg));
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.3s ease;
}
.photo-orbit-edit .star-rotate-arrows {
  opacity: 1;
}
/* Small arrows flanking the white dwarf — arcs on same orbit, rotate with dwarf */
.star-group-arrows {
  position: absolute;
  width: 160px;
  height: 160px;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%) rotate(var(--group-angle, 45deg));
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.3s ease;
}
.photo-orbit-edit .star-group-arrows {
  opacity: 1;
}

/* Joystick mode — pull offset without scale change */
.core-joystick [data-quick-photo-target="shutterCore"] {
  transform: translate(
    var(--body-pull-x, 0px),
    var(--body-pull-y, 0px)
  );
  transition: transform 0.15s ease-out;
}

/* Single outward pulse from the core on tap / joystick pull — preserves the blue outline */
@keyframes core-tap-wave {
  0%   { box-shadow: 0 0 0 1px rgba(255,255,255,0.7), 0 0 0 3px #4A8BFF, 0 0 0 3px rgba(255, 255, 255, 0.5); }
  100% { box-shadow: 0 0 0 1px rgba(255,255,255,0.7), 0 0 0 3px #4A8BFF, 0 0 0 18px rgba(255, 255, 255, 0); }
}
.core-tap-wave {
  animation: core-tap-wave 0.35s ease-out;
}

/* State class on core when a dog thumbnail is displayed (no visual effect) */
.core-dog-selected {
}

/* Charging wave while the finger holds the Body before edit mode fires.
   Tighter cadence than the edit pulse — feels like building pressure. */
@keyframes shutter-charging {
  0%, 100% { box-shadow: 0 0 0 0 rgba(255, 107, 74, 0.5), 0 8px 30px rgba(0,0,0,0.35); }
  50% { box-shadow: 0 0 0 8px rgba(255, 107, 74, 0), 0 8px 30px rgba(0,0,0,0.35); }
}
.shutter-charging {
  animation: shutter-charging 0.9s ease-in-out infinite;
}
/* Lift when joystick drag mode activates; drop on release */
.star-dragging {
  scale: 1.04;
  transition: scale 150ms ease-out;
}
.star-drop {
  scale: 1;
  transition: scale 200ms ease-in;
}
/* Gathered-dogs "ready to go" pulse on the Body — outward-only wave
   (expand + fade, then restart). Distinct from the edit-mode charging
   pulse which breathes in and out. */
@keyframes shutter-gather-wave {
  0%   { box-shadow: 0 0 0 0 rgba(255, 107, 74, 0.5), 0 8px 30px rgba(0,0,0,0.35); }
  100% { box-shadow: 0 0 0 16px rgba(255, 107, 74, 0), 0 8px 30px rgba(0,0,0,0.35); }
}
.shutter-gather-pulse {
  animation: shutter-gather-wave 0.9s ease-out infinite;
}
/* Single large burst wave when edit mode activates — then hands off to
   the normal edit pulse. */
@keyframes shutter-edit-burst {
  0%   { box-shadow: 0 0 0 0 rgba(255, 107, 74, 0.8), 0 8px 30px rgba(0,0,0,0.35); }
  100% { box-shadow: 0 0 0 40px rgba(255, 107, 74, 0), 0 8px 30px rgba(0,0,0,0.35); }
}
.shutter-edit-burst {
  animation: shutter-edit-burst 0.5s ease-out both;
}

/* Header Dog Star icon press indicator — sustained radiating pulse while
   the user's finger is on the icon. Doubles as tap feedback (since the
   icon is tiny) and as a "you've got hold of the Dog Star" signal when the
   finger-chase drag engages. Uses the same ring pulse as
   `shutter-edit-pulse` but at a tighter cadence to feel more immediate. */
@keyframes header-star-press {
  0%   { box-shadow: 0 0 0 0 rgba(255, 107, 74, 0.75); }
  100% { box-shadow: 0 0 0 14px rgba(255, 107, 74, 0); }
}
.header-star-press {
  animation: header-star-press 0.9s ease-out infinite;
}

/* Lock hint — first-dog onboarding pulse + tooltip */
.lock-hint-pulse {
  animation: lock-hint-wave 1.6s ease-out infinite;
}
@keyframes lock-hint-wave {
  0%   { box-shadow: 0 0 0 0 var(--color-brand-a40, rgba(255, 107, 74, 0.4)); }
  70%  { box-shadow: 0 0 0 10px transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}
.lock-hint-pulse {
  border-radius: 9999px;
}
/* Steady pulse on the lock button while the card is unlocked */
.modal-panel.unlocked [data-dog-modal-target="lockHint"] {
  animation: lock-hint-wave 1.6s ease-out infinite;
  border-radius: 9999px;
}
.lock-hint-tooltip {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  white-space: nowrap;
  font-family: var(--font-display);
  font-size: 11px;
  font-weight: 600;
  color: white;
  background: var(--color-brand);
  padding: 4px 10px;
  border-radius: 6px;
  pointer-events: none;
  animation: lock-hint-tooltip-fade 1.6s ease-in-out infinite;
}
.lock-hint-tooltip::before {
  content: "";
  position: absolute;
  bottom: 100%;
  right: 12px;
  border: 5px solid transparent;
  border-bottom-color: var(--color-brand);
}
@keyframes lock-hint-tooltip-fade {
  0%, 100% { opacity: 0.7; }
  50%      { opacity: 1; }
}

/* Header walk indicator: pulsing slot icon when a walk is in progress. */
/* Active-walk indicator: the bottom-nav Walk icon flashes in brand colour while a
   walk is in progress (server adds .nav-walk-pulse from current_walk_id; the
   quick-photo controller keeps it in sync from the current-walk meta). Replaces
   the old flashing header paw. paw-pulse is the sanctioned opacity-loop exception
   (equal 0.3→1→0.3 endpoints, so the restart boundary is continuous). */
.nav-walk-pulse {
  color: var(--color-brand);
  animation: paw-pulse 2s ease-in-out infinite;
}
@keyframes paw-pulse {
  0%, 100% { opacity: 0.3; }
  50%      { opacity: 1; }
}


/* Swap-selected dog slot in edit mode: white + brand double ring.
   Ad-hoc 2026-04-11: the outline settles in from a larger size
   (swap-select-settle, 0.28s) before entering the infinite idle pulse
   (swap-select-pulse). The settle is a one-shot `cubic-bezier` ease-out
   so the ring visibly contracts onto the planet — reads as "locked on". */
/* ── Planet drop shadow shorthand ──
   Every outline / pulse / fade keyframe must include these two trailing
   layers so the planet's lift shadow isn't wiped by the animation's
   box-shadow override.  Search "drop-shadow-layers" when editing. */
.orbit-slot-swap-selected {
  box-shadow: 0 0 0 1px rgba(255,255,255,0.7), 0 0 0 3px var(--color-brand),
              0 1px 2px rgba(0,0,0,0.22), 0 4px 12px rgba(0,0,0,0.18);
  animation:
    swap-select-settle 0.28s cubic-bezier(0.2, 0.8, 0.2, 1) both,
    swap-select-pulse 1s ease-in-out 0.28s infinite;
}
@keyframes swap-select-settle {
  0%   { box-shadow: 0 0 0 1.5px rgba(255,255,255,0), 0 0 0 14px rgba(255, 107, 74, 0),
                     0 1px 2px rgba(0,0,0,0.22), 0 4px 12px rgba(0,0,0,0.18); }
  60%  { box-shadow: 0 0 0 1px rgba(255,255,255,0.7), 0 0 0 6px var(--color-brand),
                     0 1px 2px rgba(0,0,0,0.22), 0 4px 12px rgba(0,0,0,0.18); }
  100% { box-shadow: 0 0 0 1px rgba(255,255,255,0.7), 0 0 0 3px var(--color-brand),
                     0 1px 2px rgba(0,0,0,0.22), 0 4px 12px rgba(0,0,0,0.18); }
}
@keyframes swap-select-pulse {
  0%, 100% { box-shadow: 0 0 0 1px rgba(255,255,255,0.7), 0 0 0 3px var(--color-brand),
                         0 1px 2px rgba(0,0,0,0.22), 0 4px 12px rgba(0,0,0,0.18); }
  50%      { box-shadow: 0 0 0 1px rgba(255,255,255,0.7), 0 0 0 5px var(--color-brand),
                         0 1px 2px rgba(0,0,0,0.22), 0 4px 12px rgba(0,0,0,0.18); }
}
/* Pre-selected dog (dog-first flow) — same settle-in wave + continuous
   pulse as gathered dogs so selected vs unselected is unmistakable. */
.orbit-pre-selected {
  animation: gather-settle 0.45s cubic-bezier(0.2, 0.8, 0.2, 1) both,
             gather-pulse 0.9s ease-out 0.45s infinite;
  border-radius: 50%;
}
/* Gather-mode dog selection — outline settles in from a wider ring,
   then a subtle continuous pulse keeps it visible. */
.orbit-gathered {
  animation: gather-settle 0.45s cubic-bezier(0.2, 0.8, 0.2, 1) both,
             gather-pulse 0.9s ease-out 0.45s infinite;
  border-radius: 50%;
}
@keyframes gather-settle {
  0%   { box-shadow: 0 0 0 1px rgba(255,255,255,0.7), 0 0 0 3px #4A8BFF,
                     0 1px 2px rgba(0,0,0,0.22), 0 4px 12px rgba(0,0,0,0.18); }
  50%  { box-shadow: 0 0 0 1.5px #fff, 0 0 0 4.25px #4A8BFF, 0 0 4px 0.5px rgba(74, 139, 255, 0.15),
                     0 1px 2px rgba(0,0,0,0.22), 0 4px 12px rgba(0,0,0,0.18); }
  100% { box-shadow: 0 0 0 1px rgba(255,255,255,0.7), 0 0 0 3px #4A8BFF,
                     0 1px 2px rgba(0,0,0,0.22), 0 4px 12px rgba(0,0,0,0.18); }
}
@keyframes gather-pulse {
  0%   { box-shadow: 0 0 0 1px rgba(255,255,255,0.7), 0 0 0 3px #4A8BFF, 0 0 0 3px rgba(74, 139, 255, 0.5),
                     0 1px 2px rgba(0,0,0,0.22), 0 4px 12px rgba(0,0,0,0.18); }
  100% { box-shadow: 0 0 0 1px rgba(255,255,255,0.7), 0 0 0 3px #4A8BFF, 0 0 0 9px rgba(74, 139, 255, 0),
                     0 1px 2px rgba(0,0,0,0.22), 0 4px 12px rgba(0,0,0,0.18); }
}
/* Fade-out for deselected gathered dogs (single-dog action replacement). */
.orbit-gathered-deselecting {
  animation: gather-deselect-fade 0.3s ease-out forwards !important;
  border-radius: 50%;
}
@keyframes gather-deselect-fade {
  from { box-shadow: 0 0 0 1px rgba(255,255,255,0.7), 0 0 0 3px #4A8BFF,
                     0 1px 2px rgba(0,0,0,0.22), 0 4px 12px rgba(0,0,0,0.18); }
  to   { box-shadow: 0 0 0 1px rgba(255,255,255,0), 0 0 0 3px rgba(74,139,255,0),
                     0 1px 2px rgba(0,0,0,0.22), 0 4px 12px rgba(0,0,0,0.18); }
}
/* Gathered-dog count badge on the Body core. Small brand-color circle
   at the top-right of the core showing how many dogs are gathered. */
/* Badge on the Body button (not the core) so it sits above
   the outer ring. Positioned at the core's top-right edge — the
   core is 60px centered in the 96px Body, so its edge is 18px
   from the Body edge; offset a few px inward to overlap. */
.core-gather-badge {
  position: absolute;
  top: 18px;
  right: 18px;
  min-width: 16px;
  height: 16px;
  padding: 0 3.5px;
  border-radius: 8px;
  background: var(--color-brand);
  color: white;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 12px;
  line-height: 18px;
  text-align: center;
  box-shadow: 0 0 0 1px white;
  pointer-events: none;
  z-index: 10;
}
/* Dog name on the outer ring, just below the core image. */
.core-gather-name {
  position: absolute;
  bottom: 12px;
  left: 50%;
  transform: translateX(-50%);
  white-space: nowrap;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 9px;
  line-height: 1;
  color: #fff;
  text-align: center;
  pointer-events: none;
  z-index: 10;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
  background: rgba(20, 45, 76, 0.85);
  padding: 2px 4px;
  border-radius: 3px;
}
/* Plan 033: group cycle fade. When the active group cursor changes, the
   dog planets fade out → swap → fade in while the action planets
   (including the group digit itself) stay lit so the walker sees the
   new number immediately. The `:not([data-quick-action])` selector
   narrows the fade to dog planets only.

   The !important on the class rule ensures the opacity override wins
   even if other rules set opacity on the slot. */
.orbit-ring .orbit-slot:not([data-quick-action]) {
  transition: opacity 0.2s ease;
}
.orbit-ring.orbit-cycle-fade .orbit-slot:not([data-quick-action]) {
  opacity: 0 !important;
}

/* Plan 033: centred digit inside the group planet. Font and weight
   match the other display-font numerals in the app; size tuned to sit
   alongside the 16px Material Symbols glyphs without looking out of
   place. */
.orbit-slot-group-digit {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  color: #fff;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 18px;
  line-height: 1;
}

/* Plan 035: paw glyph shown in the Group planet while the walk group
   is the active group. Sized to match the 18px digit it replaces so
   the Ring 3 centreline stays stable when the walker transitions in
   and out of a walk. */
.orbit-slot-group-paw {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  font-size: 18px;
  font-variation-settings: "FILL" 1, "wght" 500;
  line-height: 1;
}

/* Core group-icon placeholder — shown in the Body when the selected
   dog has no photo. Mirrors the group satellite's icon + dots style. */
.core-group-placeholder {
  position: relative;
  background: linear-gradient(135deg, #C8DBF0, #7BA4CC);
  color: white;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
  gap: 0;
}
.core-group-icon {
  font-size: 22px;
  font-variation-settings: "FILL" 1, "wght" 500;
  line-height: 1;
  margin-top: -6px;
}
.core-group-calendar {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: -6px;
}
.core-group-cal-icon {
  font-size: 24px;
  font-variation-settings: "FILL" 0, "wght" 400;
  line-height: 1;
}
.core-group-cal-paw {
  position: absolute;
  top: calc(55% + 1px);
  left: 50%;
  transform: translate(-50%, -50%);
  font-size: 10px;
  font-variation-settings: "FILL" 1, "wght" 500;
  line-height: 1;
}
.core-group-dots {
  display: flex;
  gap: 3px;
  margin-top: 2px;
}
.core-group-dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.35);
  flex-shrink: 0;
}
.core-group-dot-filled {
  background: white;
}
.core-group-dot:first-child,
.core-group-dot:last-child {
  transform: translateY(-1.5px);
}
.core-group-label {
  font-size: 11px;
  font-weight: 700;
  font-family: var(--font-display);
  letter-spacing: 0.03em;
  line-height: 1;
  text-shadow:
    0 1px 1px rgba(0, 0, 0, 0.6),
    0 2px 4px rgba(0, 0, 0, 0.4),
    0 0 8px rgba(0, 0, 0, 0.25);
}

.orbit-slot-name {
  margin-top: -1px;
  font-family: var(--font-display);
  font-size: 0.5625rem;
  font-weight: 700;
  color: #222;
  text-align: center;
  white-space: nowrap;
  text-shadow: none;
  background: rgba(255, 255, 255, 0.85);
  padding: 0px 3px;
  border-radius: 4px;
  transition: background 0.2s, color 0.2s;
}
body:not([data-quick-photo-state-value="hidden"]) .orbit-slot-name {
  color: #fff;
  text-shadow: 0 1px 4px rgba(0, 0, 0, 0.6);
  background: rgba(20, 45, 76, 0.85);
}

/* ── Select arrow (shared chevron for all custom selects) ── */
select.edit-input,
.dog-status-select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='16' viewBox='0 -960 960 960' width='16' fill='%23A0A0B2'%3E%3Cpath d='M480-345 240-585l56-56 184 184 184-184 56 56-240 240Z'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 4px center;
  background-size: 14px;
  padding-right: 20px;
  appearance: none;
  -webkit-appearance: none;
}

/* ── Photo filter pills ── */
.photo-pill {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 12px;
  padding: 3px 8px;
  border-radius: 9999px;
  border: 1px solid rgba(122, 122, 144, 0.3);
  background: transparent;
  color: var(--color-ink-faint);
  cursor: pointer;
  white-space: nowrap;
  touch-action: manipulation;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.photo-pill-active {
  background: var(--color-brand);
  color: white;
  border-color: var(--color-brand);
}

/* Photo tag gallery — selection outline */
.photo-tag-selected {
  outline: 3px solid #4A8BFF;
  outline-offset: -3px;
}

/* ── Inline style replacements (plan 049) ── */

.lightbox-badge {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 11px;
  color: white;
  background: rgba(0, 0, 0, 0.6);
  padding: 3px 10px;
  border-radius: 9999px;
  line-height: 1.3;
  white-space: nowrap;
}

.no-transition {
  transition: none !important;
}

/* Thinking spinner — orbiting dot ring on star core while waiting for Gemini */
.core-thinking::before {
  content: "";
  position: absolute;
  inset: -5px;
  border-radius: 50%;
  border: 2px solid transparent;
  border-top-color: rgba(99, 102, 241, 0.9);
  border-right-color: rgba(99, 102, 241, 0.4);
  animation: core-think-spin 0.8s linear infinite;
  pointer-events: none;
  z-index: 2;
}
@keyframes core-think-spin {
  to { transform: rotate(360deg); }
}

.core-glass {
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 60 60'%3E%3Cdefs%3E%3CradialGradient id='s' cx='40%25' cy='35%25' r='55%25'%3E%3Cstop offset='0%25' stop-color='%23F0F5FF'/%3E%3Cstop offset='35%25' stop-color='%23D0E2FF'/%3E%3Cstop offset='65%25' stop-color='%23B0D0F0'/%3E%3Cstop offset='100%25' stop-color='%2385ADCC'/%3E%3C/radialGradient%3E%3CradialGradient id='h' cx='38%25' cy='28%25' r='30%25'%3E%3Cstop offset='0%25' stop-color='%23fff' stop-opacity='0.8'/%3E%3Cstop offset='100%25' stop-color='%23fff' stop-opacity='0'/%3E%3C/radialGradient%3E%3C/defs%3E%3Ccircle cx='30' cy='30' r='30' fill='url(%23s)'/%3E%3Ccircle cx='30' cy='30' r='30' fill='url(%23h)'/%3E%3C/svg%3E") no-repeat center / contain;
}


.action-dim {
  opacity: 0.2;
}

.modal-fling {
  transition: transform 0.3s ease-out, opacity 0.3s ease-out;
}

.modal-snapback {
  transition: transform 0.18s ease-out;
}

.sortable-flip {
  transition: transform 400ms cubic-bezier(0.25, 1, 0.5, 1);
}

/* Address autocomplete dropdown */
.address-autocomplete-dropdown {
  background: rgba(255, 255, 255, 0.92);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  border-radius: 12px;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.12), 0 0 0 1px rgba(0, 0, 0, 0.04);
  overflow: hidden;
  max-height: 240px;
  overflow-y: auto;
}

.address-autocomplete-item {
  padding: 10px 14px;
  font-size: 13px;
  color: var(--color-ink);
  cursor: pointer;
  border-bottom: 1px solid var(--color-canvas-warm);
}

.address-autocomplete-item:last-child {
  border-bottom: none;
}

.address-autocomplete-item:hover,
.address-autocomplete-item-active {
  background: var(--color-canvas-warm);
}

/* ── Help / Guide content ── */
.help-content h2 {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.25rem;
  color: var(--color-ink);
  margin-top: 2rem;
  margin-bottom: 0.75rem;
  padding-bottom: 0.5rem;
  border-bottom: 1px solid var(--color-canvas-warm);
  /* Jump-list chips target h2 ids — land clear of the fixed header. */
  scroll-margin-top: calc(4.5rem + env(safe-area-inset-top, 0px));
}

/* Cross-links between guide sections (See also, More:, the typical-day hub). */
.help-content a {
  color: var(--color-brand);
  font-weight: 600;
  text-decoration: none;
}

.help-content h2:first-child {
  margin-top: 0;
}

.help-content h3 {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1rem;
  color: var(--color-ink);
  margin-top: 1.5rem;
  margin-bottom: 0.5rem;
}

.help-content p {
  font-family: var(--font-body);
  font-size: 0.9375rem;
  line-height: 1.65;
  color: var(--color-ink-soft);
  margin-bottom: 0.75rem;
}

.help-content ul,
.help-content ol {
  font-family: var(--font-body);
  font-size: 0.9375rem;
  line-height: 1.65;
  color: var(--color-ink-soft);
  padding-left: 1.25rem;
  margin-bottom: 0.75rem;
}

.help-content li {
  margin-bottom: 0.375rem;
}

.help-content strong {
  color: var(--color-ink);
  font-weight: 600;
}

.help-content .guide-card {
  background: var(--glass-strong);
  border-radius: 1rem;
  padding: 1rem 1.125rem;
  margin-bottom: 0.75rem;
}

.help-content .guide-step {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  margin-bottom: 0.75rem;
}

.help-content .guide-step-num {
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 50%;
  background: rgba(255, 107, 74, 0.1);
  color: var(--color-brand);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.8125rem;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  margin-top: 0.125rem;
}

.help-content .guide-step p {
  margin-bottom: 0;
}

.help-content .guide-tip {
  padding: 0.875rem 1rem;
  background: rgba(255, 107, 74, 0.04);
  border-radius: 0.875rem;
  border-left: 3px solid var(--color-brand-soft);
  font-family: var(--font-body);
  font-size: 0.875rem;
  line-height: 1.65;
  color: var(--color-ink-soft);
  margin-bottom: 0.75rem;
}

.help-content .guide-tip strong {
  color: var(--color-brand);
}

/* Stacked term→meaning entries — the mobile-first replacement for two-column
   guide tables, whose cells wrapped into unreadable multi-row blocks at phone
   width (Plan 187 feedback). The term sits on its own line, meaning beneath. */
.help-content .guide-def {
  margin: 0;
}

.help-content .guide-def > div {
  padding: 0.625rem 0;
  border-bottom: 1px solid rgba(0,0,0,0.05);
}

.help-content .guide-def > div:first-child {
  padding-top: 0;
}

.help-content .guide-def > div:last-child {
  border-bottom: none;
  padding-bottom: 0;
}

.help-content .guide-def dt {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.9375rem;
  color: var(--color-ink);
  margin-bottom: 0.125rem;
}

.help-content .guide-def dt .guide-def-note {
  font-family: var(--font-body);
  font-weight: 400;
  font-size: 0.8125rem;
  color: var(--color-ink-faint);
  margin-left: 0.375rem;
}

.help-content .guide-def dd {
  font-family: var(--font-body);
  font-size: 0.875rem;
  line-height: 1.6;
  color: var(--color-ink-soft);
  margin: 0;
}

.help-content .guide-table {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  font-family: var(--font-body);
  font-size: 0.875rem;
  margin-bottom: 1rem;
  border-radius: 0.75rem;
  overflow: hidden;
}

.help-content .guide-table th {
  text-align: left;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 0.8125rem;
  color: var(--color-ink-faint);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 0.5rem 0.75rem;
  background: var(--color-canvas-warm);
  border-bottom: 1px solid rgba(0,0,0,0.06);
}

.help-content .guide-table td {
  padding: 0.5rem 0.75rem;
  color: var(--color-ink-soft);
  border-bottom: 1px solid rgba(0,0,0,0.04);
  line-height: 1.5;
  /* Mobile-first: long unbreakable tokens (Accounts::PaymentsController) must wrap
     mid-word or the table widens past the phone viewport and the whole PAGE scrolls
     sideways (the dev guide's controllers page did exactly that at 390px). */
  overflow-wrap: anywhere;
}

.help-content .guide-table th {
  overflow-wrap: anywhere;
}

.help-content .guide-table tr:last-child td {
  border-bottom: none;
}

.help-content .guide-icon {
  vertical-align: middle;
  font-size: 18px;
  margin-right: 0.25rem;
}

.help-content .guide-screenshot {
  margin: 1.25rem 0;
  text-align: center;
}

.help-content .guide-screenshot img {
  max-width: 280px;
  width: 100%;
  border-radius: 1rem;
  box-shadow: 0 2px 12px rgba(0,0,0,0.10), 0 0.5px 2px rgba(0,0,0,0.06);
  border: 1px solid rgba(0,0,0,0.06);
}

/* Placeholder when a guide screenshot asset is missing (guide_img fallback) —
   keeps the layout intact instead of 500ing the page. */
.help-content .guide-screenshot-missing {
  max-width: 280px;
  aspect-ratio: 9 / 16;
  margin: 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 1rem;
  background: var(--color-canvas-warm);
  border: 1px dashed rgba(0,0,0,0.12);
  color: var(--color-ink-faint);
}
.help-content .guide-screenshot-missing .material-symbols-outlined {
  font-size: 40px;
}

.help-content .guide-screenshot figcaption {
  font-family: var(--font-body);
  font-size: 0.8125rem;
  color: var(--color-ink-faint);
  margin-top: 0.5rem;
  line-height: 1.4;
}

.help-content pre {
  background: var(--color-canvas-warm, #f5f3ef);
  border: 1px solid rgba(0,0,0,0.06);
  border-radius: 0.75rem;
  padding: 1rem 1.25rem;
  overflow-x: auto;
  margin: 1rem 0;
  -webkit-overflow-scrolling: touch;
}

.help-content pre code {
  font-family: ui-monospace, "SF Mono", "Cascadia Code", "Fira Code", Menlo, Consolas, monospace;
  font-size: 0.8125rem;
  line-height: 1.55;
  color: var(--color-ink);
  white-space: pre;
}

.help-content code {
  font-family: ui-monospace, "SF Mono", "Cascadia Code", "Fira Code", Menlo, Consolas, monospace;
  font-size: 0.875em;
  background: var(--color-canvas-warm, #f5f3ef);
  border-radius: 0.25rem;
  padding: 0.125rem 0.375rem;
  overflow-wrap: anywhere; /* inline route/class names must not widen the phone viewport */
}

/* ── StarPaw panel shared positioning ── */
/* Links/buttons in panels are non-interactive until their element is focused */
.starpaw-context-panel a,
.starpaw-context-panel button {
  -webkit-tap-highlight-color: transparent;
  pointer-events: none;
}
.starpaw-context-panel .starpaw-joystick-focus a,
.starpaw-context-panel .starpaw-joystick-focus button,
.starpaw-context-panel .starpaw-joystick-focus-dog a,
.starpaw-context-panel .starpaw-joystick-focus-dog button {
  pointer-events: auto;
}
/* Star concealed — no joystick, all panel links/buttons are directly tappable */
body.star-concealed .starpaw-context-panel a,
body.star-concealed .starpaw-context-panel button {
  pointer-events: auto;
}
/* Core always has electric blue outline — visible in both extended and retracted states */
body:not([data-quick-photo-state-value="hidden"]) [data-quick-photo-target="shutterCore"] {
  box-shadow: 0 0 0 1px rgba(255,255,255,0.7), 0 0 0 3px #4A8BFF;
}
/* Remove the blue outline in listening/conversation mode */
body.listening-adorned [data-quick-photo-target="shutterCore"] {
  box-shadow: none;
}
.starpaw-context-panel {
  position: absolute;
  top: calc(env(safe-area-inset-top) + 104px);
  left: 12px;
  right: 12px;
  z-index: 10;
  pointer-events: auto;
  opacity: 0;
  transform: translateY(-10px);
  transition: opacity 0.25s ease-out, transform 0.25s ease-out;
}
.starpaw-empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 24px 16px;
  color: rgba(255, 255, 255, 0.25);
  font-size: 14px;
  font-style: italic;
  text-align: center;
  transform: none;
}
.starpaw-empty-group {
  font-size: 16px;
  font-weight: 700;
  font-style: normal;
  font-family: var(--font-display);
  letter-spacing: 0.03em;
  color: rgba(255, 255, 255, 0.5);
}


/* ── StarPaw joystick navigation focus ── */
.starpaw-joystick-focus {
  box-shadow: 0 0 0 1px rgba(255,255,255,0.7), 0 0 0 3px #4A8BFF;
  border-radius: 6px;
}
.starpaw-joystick-focus-dog {
  box-shadow: 0 0 0 1px rgba(255,255,255,0.7), 0 0 0 3px #4A8BFF;
  border-radius: 8px;
}

/* Body pull visual feedback — shift the core in the pull direction (non-joystick only) */
[data-quick-photo-target="coreBarrel"]:not(.core-joystick) > [data-quick-photo-target="shutterCore"] {
  translate: var(--body-pull-x, 0) var(--body-pull-y, 0);
}
.body-pull-return {
  transition: translate 0.2s ease-out !important;
}

/* ── StarPaw joystick directional arrows ── */
.starpaw-joystick-arrows {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 1;
}
.photo-orbit-edit .starpaw-joystick-arrows {
  display: none;
}
.starpaw-joystick-chevron {
  position: absolute;
  width: 6px;
  height: 6px;
  border-right: 1.5px solid rgba(255,255,255,0.9);
  border-bottom: 1.5px solid rgba(255,255,255,0.9);
}
.starpaw-joystick-chevron-up {
  top: 4px;
  left: 50%;
  transform: translateX(-50%) rotate(-135deg);
}
.starpaw-joystick-chevron-down {
  bottom: 4px;
  left: 50%;
  transform: translateX(-50%) rotate(45deg);
}
.starpaw-joystick-chevron-left {
  left: 4px;
  top: 50%;
  transform: translateY(-50%) rotate(135deg);
}
.starpaw-joystick-chevron-right {
  right: 4px;
  top: 50%;
  transform: translateY(-50%) rotate(-45deg);
}

/* Grip ring: rotatable tick marks between the directional chevrons.
   Visually turns during rotation gestures to give "dial" feedback. */

/* ── Crop overlay (dog_modal thumbnail crop) ── */
.crop-overlay {
  position: fixed;
  inset: 0;
  z-index: 500;
  background: rgba(0,0,0,0.92);
  display: flex;
  align-items: center;
  justify-content: center;
  touch-action: none;
  user-select: none;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  padding: env(safe-area-inset-top, 0) env(safe-area-inset-right, 0) env(safe-area-inset-bottom, 0) env(safe-area-inset-left, 0);
}
.crop-wrapper {
  position: relative;
  max-width: 100%;
  max-height: 100%;
  overflow: hidden;
}
.crop-image {
  width: 100%;
  height: 100%;
  object-fit: contain;
  pointer-events: none;
  user-select: none;
  -webkit-touch-callout: none;
}
.crop-square {
  position: absolute;
  border: 2px solid white;
  border-radius: 8px;
  box-shadow: 0 0 0 9999px rgba(0,0,0,0.55);
  z-index: 10;
  touch-action: none;
  cursor: grab;
}
.crop-close-btn {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 20;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: rgba(0,0,0,0.4);
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  cursor: pointer;
}

/* Completed-card lock tooltip — flashed when tapping the lock on a completed walk / past
   daycare or boarding card. Appended to <body> (fixed) by completed_lock_controller so the
   card's overflow-hidden can't clip it; positioned in JS. */
.cal-locked-tooltip {
  position: fixed;
  z-index: 60;
  white-space: nowrap;
  font-family: var(--font-display);
  font-size: 11px;
  font-weight: 600;
  /* Match the app's tooltip colour language — brand fill + white text, same as the
     dog-modal lock-hint tooltip — with an upward caret toward the (right-aligned) lock. */
  color: #fff;
  background: var(--color-brand);
  padding: 4px 10px;
  border-radius: 6px;
  pointer-events: none;
  opacity: 0;
  transform: translateY(-2px);
  transition: opacity 0.15s ease, transform 0.15s ease;
  box-shadow: 0 4px 12px rgba(26, 26, 46, 0.18);
}
.cal-locked-tooltip::before {
  content: "";
  position: absolute;
  bottom: 100%;
  right: 12px;
  border: 5px solid transparent;
  border-bottom-color: var(--color-brand);
}
.cal-locked-tooltip.is-visible { opacity: 1; transform: translateY(0); }

/* Calendar card lock — gate transfer buttons when card is locked */
[data-controller~="card-lock"]:not(.cal-card-unlocked) .cal-edit-action {
  pointer-events: none;
  cursor: default;
}
[data-controller~="card-lock"]:not(.cal-card-unlocked) .cal-unlock-visible {
  display: none;
}

/* Pencil icon on transfer buttons when card is unlocked */
.cal-card-unlocked .cal-edit-action {
  position: relative;
}
.cal-card-unlocked .cal-edit-action::after {
  content: "edit";
  font-family: "Material Symbols Outlined";
  font-size: 14px;
  position: absolute;
  right: 4px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--color-brand);
  opacity: 0.5;
}

/* Walk replay slider — a tall, transparent hit box (easy to grab, esp. on touch) with a
   thin visible track drawn by the runnable-track. The 28px box ≫ the 4px line it shows. */
input[type="range"].walk-slider {
  -webkit-appearance: none;
  appearance: none;
  height: 28px;
  background: transparent;
  outline: none;
  cursor: pointer;
  touch-action: none; /* claim the drag so a horizontal swipe scrubs rather than pans */
}
input[type="range"].walk-slider::-webkit-slider-runnable-track {
  height: 4px;
  background: rgba(255,255,255,0.3);
  border-radius: 2px;
}
input[type="range"].walk-slider::-moz-range-track {
  height: 4px;
  background: rgba(255,255,255,0.3);
  border-radius: 2px;
}
input[type="range"].walk-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 22px;
  height: 22px;
  margin-top: -9px; /* center the 22px thumb on the 4px track */
  border-radius: 50%;
  background: var(--color-ink);
  cursor: pointer;
  border: 2px solid white;
  box-shadow: 0 1px 4px rgba(0,0,0,0.3);
}
input[type="range"].walk-slider::-moz-range-thumb {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--color-ink);
  cursor: pointer;
  border: 2px solid white;
  box-shadow: 0 1px 4px rgba(0,0,0,0.3);
}

/* Incident marker pulse */
@keyframes incident-pulse {
  0%   { box-shadow: 0 0 0 0 rgba(244, 63, 94, 0.5); }
  100% { box-shadow: 0 0 0 10px rgba(244, 63, 94, 0); }
}

/* ── Gallery dog filter — search + autocomplete + removable pills (Plan 160).
   Type to find a dog, pick to add a pill, tap a pill to remove. Replaces the
   Plan 158 chip row + drag-to-tag (retired). JS-built rows/pills need real CSS
   (Tailwind won't emit classes set on createElement'd nodes). ── */
.gallery-search-wrap { background: var(--color-glass-strong); border: 1px solid rgba(26, 26, 46, 0.08); min-height: 2.75rem; }
.gallery-search-input { color: var(--color-ink); }
.gallery-search-input::placeholder { color: var(--color-ink-faint); }

/* Facet toggles (Plan 160 polish) — one bonded segmented control replacing the
   floating circles. Active segment uses the brand accent, matching the pills, so
   "orange = an active filter" reads as a system. */
/* Gallery accent — the light gallery (Plan 169) wears the app's brand coral, so it reads as part
   of the regular light app (calendar/dogs/accounts) rather than a StarView-style dark media surface.
   One var drives every active/selected state so it stays consistent. */
[data-gallery-overlay] { --gallery-accent: var(--color-brand); }

.gallery-facets { background: rgba(26, 26, 46, 0.04); border: 1px solid rgba(26, 26, 46, 0.10); }
.gallery-facet { background: transparent; border: none; color: var(--color-ink-soft); cursor: pointer; transition: background 0.15s, color 0.15s; }
.gallery-facet + .gallery-facet { border-left: 1px solid rgba(26, 26, 46, 0.10); }
.gallery-facet-label { font-family: var(--font-display, inherit); font-weight: 600; font-size: 0.75rem; }
.gallery-facet.is-active { background: var(--gallery-accent); color: #fff; }
.gallery-facet.is-active .material-symbols-outlined { font-variation-settings: "FILL" 1; }

/* Date picker lives at the search level (it's a value input, not a boolean toggle) —
   a square button beside the search box, same height + rectangular geometry. */
.gallery-datebtn { width: 2.75rem; background: var(--color-glass-strong); border: 1px solid rgba(26, 26, 46, 0.08); color: var(--color-ink-soft); cursor: pointer; transition: background 0.15s, color 0.15s; }
.gallery-datebtn.is-active { background: var(--gallery-accent); color: #fff; }
.gallery-datebtn.is-active .material-symbols-outlined { font-variation-settings: "FILL" 1; }

/* Bottom action bar (Plan 160 polish) — make the selection→action state loud.
   Dimmed + inert-looking with no selection; full white + accent icon once
   ≥1 photo is selected. Still tappable when dimmed so a dead tap can hint. */
.gallery-action { transition: opacity 0.15s, color 0.15s; }
.gallery-action.is-dimmed { opacity: 0.35; }
.gallery-action.is-armed { opacity: 1; color: var(--color-ink); }
.gallery-action.is-armed .material-symbols-outlined { color: var(--gallery-accent); }
/* Photo-selection checkbox (was Tailwind bg-brand) → the gallery accent. */
.gallery-check-on { background: var(--gallery-accent); border-color: var(--gallery-accent); }
.gallery-action-divider { width: 1px; align-self: stretch; background: rgba(26, 26, 46, 0.14); margin: 0.3rem 0.25rem; }

/* Title-bar Select / Done — a pill so it reads as a button, not a label (Plan 161).
   Subtle outlined chip in view mode; accent-filled while selecting (same "accent = active"
   language as the facets/pills/checkbox). */
.gallery-select-toggle {
  display: flex; align-items: center; gap: 0.3rem;
  margin-right: 0.15rem; padding: 0.34rem 0.7rem 0.34rem 0.55rem;
  min-height: 2.1rem; border-radius: 9999px;
  background: rgba(26, 26, 46, 0.05);
  border: 1px solid rgba(26, 26, 46, 0.15);
  color: var(--color-ink-soft);
  transition: background 0.15s, border-color 0.15s, color 0.15s;
}
.gallery-select-toggle.is-active {
  background: var(--gallery-accent); border-color: var(--gallery-accent); color: #fff;
}
.gallery-select-toggle.is-active .material-symbols-outlined { font-variation-settings: "FILL" 1; }

/* A soft lift around each photo so thumbnails read as matted cards on the warm canvas
   ground (rather than floating), matching the light app's depth-sm card treatment. */
[data-gallery-grid] [data-photo-id] { box-shadow: 0 1px 4px rgba(26, 26, 46, 0.10), 0 0 0 1px rgba(26, 26, 46, 0.05); }

/* Select mode (Plan 161): the per-photo checkboxes + the selection actions only show
   while selecting. View mode is a clean tap-to-open grid (Upload stays). */
[data-gallery-overlay]:not(.gallery-selecting) [data-photo-select] { display: none !important; }
/* The bottom bar is mode-exclusive (Plan 161): view mode = only Upload (add photos);
   select mode = only the selection operators (Share / Publish / Delete). Upload doesn't
   act on a selection, so it's hidden while selecting. The old Upload↔actions divider is
   pointless now (the two groups never coexist) — hidden. */
[data-gallery-overlay]:not(.gallery-selecting) .gallery-action { display: none !important; }
/* Owner expanded view (Plan 193): the footer actions ARE the photo's actions —
   selection = the expanded photo, so they show armed while expanded. */
[data-gallery-overlay].gallery-expanded-mode .gallery-action { display: flex !important; opacity: 1; }
[data-gallery-overlay].gallery-selecting [data-gallery-upload] { display: none !important; }
.gallery-action-divider { display: none !important; }

.gallery-suggestions {
  background: var(--color-glass-strong);
  border: 1px solid rgba(26, 26, 46, 0.10);
  box-shadow: 0 8px 24px rgba(26, 26, 46, 0.14);
  max-height: 16rem;
  overflow-y: auto;
}
.gallery-suggestion {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  width: 100%;
  padding: 0.5rem 0.75rem;
  background: transparent;
  border: none;
  cursor: pointer;
  text-align: left;
}
.gallery-suggestion.active,
.gallery-suggestion:hover { background: rgba(26, 26, 46, 0.05); }
.gallery-suggestion-thumb {
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 9999px;
  object-fit: cover;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}
.gallery-suggestion-thumb--empty {
  background: rgba(26, 26, 46, 0.06);
  color: var(--color-ink-faint);
  font-size: 16px;
}
.gallery-suggestion-name {
  font-family: var(--font-display, inherit);
  font-weight: 600;
  font-size: 0.875rem;
  color: var(--color-ink);
}

.gallery-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.2rem 0.4rem 0.2rem 0.6rem;
  border-radius: 9999px;
  border: none;
  cursor: pointer;
  background: var(--gallery-accent);
  color: #fff;
  font-family: var(--font-display, inherit);
  font-weight: 700;
  font-size: 0.75rem;
  touch-action: manipulation;
}
.gallery-pill-x { font-size: 15px; opacity: 0.85; }

/* ── Map glass circle button (plan 148 — was a recipe pasted 8× across the map
   controller). Borderless by default; the few buttons that kept a subtle border
   re-add it inline (preserving the pre-existing look exactly). ── */
.map-glass-btn {
  width: 36px; height: 36px; border-radius: 50%;
  background: var(--color-glass-strong);
  backdrop-filter: blur(24px) saturate(1.5);
  -webkit-backdrop-filter: blur(24px) saturate(1.5);
  display: flex; align-items: center; justify-content: center;
  color: var(--color-ink);
  border: none; cursor: pointer; touch-action: manipulation;
}

/* ── Nearby-parks list rows (plan 148 Phase B — server-rendered partial) ── */
.park-row { display: flex; flex-direction: column; gap: 3px; }
.park-row--bordered { border-top: 1px solid var(--color-glass-subtle); padding-top: 8px; margin-top: 8px; }
.park-row--expandable { cursor: pointer; }
.park-row-head { display: flex; align-items: baseline; justify-content: space-between; gap: 6px; }
.park-name { font-size: 13px; font-weight: 700; color: var(--color-ink); line-height: 1.3; min-width: 0; }
.park-dist { font-size: 10px; color: var(--color-ink-faint); white-space: nowrap; flex-shrink: 0; }
.park-badges { display: flex; flex-wrap: wrap; gap: 4px; }
.park-badge { font-size: 10px; font-weight: 600; padding: 1px 6px; border-radius: 9999px; line-height: 1.4; }
.park-badge--off_leash { color: #22c55e; background: rgba(34, 197, 94, 0.12); }
.park-badge--on_leash { color: var(--color-amber); background: rgba(245, 158, 11, 0.12); }
.park-badge--mixed { color: var(--color-brand); background: rgba(255, 107, 74, 0.12); }
.park-badge--fenced { color: var(--color-ink-soft); background: rgba(107, 107, 128, 0.1); }
.park-badge--water { color: #3b82f6; background: rgba(59, 130, 246, 0.1); }
.park-badge--surface { color: var(--color-ink-faint); background: rgba(107, 107, 128, 0.08); }
.park-hours { font-size: 11px; font-weight: 600; line-height: 1.3; color: var(--color-ink-faint); }
.park-hours--open { color: #22c55e; }
.park-addr-row { display: flex; align-items: center; gap: 6px; }
.park-addr { font-size: 11px; color: var(--color-ink-faint); line-height: 1.3; min-width: 0; flex: 1; }
.park-dir-btn { flex-shrink: 0; width: 28px; height: 28px; border-radius: 50%; background: var(--color-brand); display: flex; align-items: center; justify-content: center; border: none; cursor: pointer; }
.park-dir-btn .material-symbols-outlined { font-size: 16px; color: #fff; }
.park-detail { display: none; flex-direction: column; gap: 6px; padding-top: 4px; }
.park-row.is-expanded .park-detail { display: flex; }
.park-notes { font-size: 11px; color: var(--color-ink-soft); line-height: 1.4; }
.park-thumbs { display: flex; gap: 4px; overflow-x: auto; }
.park-thumb { width: 56px; height: 56px; border-radius: 8px; object-fit: cover; flex-shrink: 0; cursor: pointer; }

/* Boarding's standard violet (matches .month-dot-boarding). Defined here rather than in the
   Tailwind @theme because no utility class references it, so v4 would tree-shake it away. */
:root { --color-violet: #8B5CF6; --color-violet-light: #EFE9FE; }

/* Account STATUS line — triage words under the dog's name ("240 unpaid · 3 walks · 2 wks" /
   "paid through Jun 14" / "settled"), replacing the signed balance gauge. OWING deliberately gets
   NO tint: performing services in advance of payment is the routine state of a cash business — a
   warm colour read as an alarm (a deepened amber was tried and pulled the same day). Colour only
   marks the notable states: prepaid mint, settled faint. Words + the aging figure carry urgency. */
.account-status-line { display: block; font-size: 12px; line-height: 1.3; margin-top: 1px; font-weight: 600; color: var(--color-ink-soft); font-variant-numeric: tabular-nums; }
.account-status-line.is-owing   { color: var(--color-ink-soft); } /* routine — plain ink, no alarm */
.account-status-line.is-prepaid { color: #117D66; } /* deepened mint — measured 5.0:1 on glass */
.account-status-line.is-settled { color: #87879B; } /* quietest by design, but stepped up from ink-faint's 2.5:1 */

/* Owner Account status headline (Plan 194) — the same tint doctrine as the walker's
   .account-status-line hooks, at the status card's headline size: ink when owing (owed is
   navy/ink, a gauge, never a rose alarm), the measured deepened mint for prepaid AND
   settled (both are good news addressed to the owner). */
.owner-acct-status.is-owing { color: var(--color-ink); }
.owner-acct-status.is-prepaid, .owner-acct-status.is-settled { color: #117D66; }

/* ── Dog accounts — service strip (Plan 139) ──
   Joined service-bucket squares (oldest left), each filling left→right by how paid it is
   (FIFO → the strip reads as the paid frontier advancing). A small day-of-month sits under
   each square and a scroll-synced month label above, mirroring the dog-index schedule tape.
   The strip shows ~7 cells (a week) at a time and paginates by the week via the chevrons. */
/* wide + short → cells read as longitudinal (landscape) bars, not squares; the height also
   matches the chevrons so the strip + its controls share a proportionate band. --zig is the zigzag
   tooth depth at month seams; --stub-r the radius of the ticket-stub pinch notches.
   IMPORTANT: cells carry NO horizontal margins — every cell is exactly --acct-cell-w wide so the
   strip stays uniform-width. The pagination controller (account_strip_controller.js) maps scroll
   position → cell index assuming a constant cell width and pages by a fixed viewport width; any
   per-cell margin (gap or overlap) desyncs snap/paging. All seam treatments are therefore drawn
   with clip-path + pseudo-element overlays, which don't affect layout. */
.account-strip-wrap { --acct-cell-w: 34px; --acct-cell-h: 38px; --zig: 4px; --stub-r: 3px; }
.account-strip {
  display: flex;
  width: 100%;
  min-width: 0;
  overflow-x: auto;
  scroll-behavior: smooth;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  padding: 2px 0 0;
  /* Snap each cell's RIGHT edge to the strip's right wall. This is a future-oriented element — the
     right side (today / the paid-through frontier) is what matters, and it reads right-to-left — so
     the right edge is the anchor (a partially-clipped cell, if any, falls on the less-important
     left). Scrolling to the end therefore lands exactly on the true end with "next" disabled. */
  scroll-snap-type: x mandatory;
}
.account-strip::-webkit-scrollbar { display: none; }

.account-cell {
  position: relative; /* positioning context for ticket-detail pseudo-elements (perf dots, stub
                         notches, dashed frontier) drawn over the cell seams */
  flex: 0 0 auto;
  display: flex; flex-direction: column; align-items: center;
  gap: 2px;
  scroll-snap-align: end; /* right edge → right wall (see .account-strip) */
}

.account-segment {
  position: relative;
  width: var(--acct-cell-w); height: var(--acct-cell-h);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 0;
  overflow: hidden; /* clip the fill to the rounded / puzzle shape */
  color: #fff; /* value + icon always white — flat (no outline); high contrast on both the saturated
                  paid fills and the near-black unpaid base */
  /* empty base = the UNPAID portion, rendered as a WINDOW INTO ONE CONTINUOUS SPACE: the StarView
     deep-space nebula (purple/teal/pink/blue clouds) over deep navy, laid out as a single wide
     backdrop (500px tile) and offset per cell by its index (--neb-x = -index*cell-width) so the
     clouds flow continuously across a run of owed cells instead of repeating per cell. An owed cell
     is a porthole onto that shared cosmos; white text stays legible. (Grain rides the coloured fill
     only; this nebula shows through wherever a cell is unpaid.) */
  background-color: rgb(12 20 42);
  background-image:
    /* nebula clouds */
    radial-gradient(ellipse 60% 60% at 14% 24%, rgba(120, 60, 160, 0.42), rgba(80, 40, 130, 0.14) 45%, transparent 78%),
    radial-gradient(ellipse 54% 66% at 62% 80%, rgba(40, 140, 160, 0.36), rgba(30, 100, 140, 0.12) 45%, transparent 78%),
    radial-gradient(ellipse 40% 50% at 84% 30%, rgba(170, 60, 105, 0.30), transparent 72%),
    radial-gradient(ellipse 46% 46% at 36% 78%, rgba(50, 80, 185, 0.34), transparent 72%);
  background-repeat: repeat-x;
  background-size: 500px 100%;
  background-position-x: var(--neb-x, 0px);
  /* Ticket texture: a faint top sheen + bottom shade gives each cell a slight stub relief, so the
     strip reads as a run of individual ticket stubs. Inset so it survives the clip-path shapes. */
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.22), inset 0 -1px 0 rgba(0, 0, 0, 0.06);
}
.account-segment-fill {
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: var(--paid, 0%);
  background: var(--seg-color, var(--color-mint));
  z-index: 0;
}
/* Faint paper grain (inline SVG fractal noise, no asset / CSP-safe) over the COLOURED fill only —
   sized to var(--paid) so it never touches the StarView-blue unpaid base. Reads as printed ticket
   stock; sits below the text/details (z-index 0). */
.account-segment-grain {
  position: absolute; left: 0; top: 0; bottom: 0; width: var(--paid, 0%);
  z-index: 0; pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 120px 120px;
  opacity: 0.3;
  mix-blend-mode: overlay;
}
/* A strip too short to overflow sticks to the LEFT (no margin trickery). When it DOES overflow, the
   controller still scrolls to the right end on load (most recent), so long histories open at today. */
.account-strip > .account-cell:first-child .account-segment {
  border-top-left-radius: 8px; border-bottom-left-radius: 8px;
}
.account-strip > .account-cell:last-child .account-segment {
  border-top-right-radius: 8px; border-bottom-right-radius: 8px;
}
/* Credit value + icon stack inside the segment; both readable on filled + empty cells. */
.account-segment-credits {
  /* Printed-ticket / fare type: monospace tabular figures read as a stamped coupon amount. */
  font-family: ui-monospace, "SF Mono", "Cascadia Code", "Fira Code", Menlo, Consolas, monospace;
  font-variant-numeric: tabular-nums;
  font-size: 8px; line-height: 1; font-weight: 700; letter-spacing: -0.02em;
  /* DARK edge, not the old white "relief" shadow: white glyphs measured 2.0:1 on the amber fill and
     2.8:1 on brand (a white shadow blurs the edge further). The dark halo defines them on every
     light fill and disappears on the navy unpaid base. Ghost cells override with the blue stroke. */
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.45), 0 0 1px rgba(0, 0, 0, 0.35);
  position: relative; z-index: 1;
}
.account-segment-icon {
  font-size: 15px;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.45), 0 0 1px rgba(0, 0, 0, 0.35);
  position: relative; z-index: 1;
}

/* ── Ticket-stock detailing ── */
/* Perforated tear-off edges: a faint dashed line just inside every cell's top & bottom; adjacent
   cells' lines join into a continuous perforation running the length of the strip (a ticket roll). */
.account-segment::before {
  content: ""; position: absolute; z-index: 1; pointer-events: none;
  left: 0; right: 0; top: 2px; bottom: 2px;
  border-top: 1px dashed rgba(255, 255, 255, 0.45);
  border-bottom: 1px dashed rgba(255, 255, 255, 0.45);
}
/* Punch hole on REDEEMED tickets — a ticket is punched when it's paid in full (settled), like a
   conductor punching a fare. Owed/partial cells stay un-punched (unredeemed), and future ghost
   cells stay un-punched (not yet used). So punched = a completed, paid service. */
.account-cell:not(.is-ghost) .account-segment.is-settled::after {
  content: ""; position: absolute; z-index: 2; pointer-events: none;
  top: 3px; right: 3px; width: 4px; height: 4px; border-radius: 50%;
  background: var(--color-glass-strong);
  box-shadow: inset 0 0.5px 1px rgba(0, 0, 0, 0.35);
}

/* Ghost cells — prepaid credit projected forward into scheduled services (Plan 140). Future cells
   render as a faded, ethereal version of a real cell — the whole segment (nebula + fill + label) is
   dropped to a low opacity, and the fill colour is muted further toward white — so "upcoming" reads
   unmistakably as a ghost at a glance. Unpaid portion stays StarView blue; how far the mint fill
   reaches across the ghosts IS how far prepaid covers (no separate marker — the colour is the gauge). */
.account-cell.is-ghost .account-segment {
  opacity: 0.85;
}
.account-cell.is-ghost .account-segment-fill {
  background: color-mix(in srgb, var(--seg-color, var(--color-mint)) 55%, white);
}
/* Ghost glyphs use the SAME white-with-dark-halo treatment as real cells (the base rule above) —
   an electric-blue outline was tried here but was one hue too many on a money surface (the palette
   is brand · navy · mint · inks; colour is a financial claim, not decoration). The dark halo
   defines white glyphs on the pale ghost fill exactly as it does on the amber-era fills. */
/* day-of-month + weekday letter below each cell. */
.account-cell-date {
  font-size: 8px; line-height: 1; font-weight: 500;
  color: var(--color-ink-soft);
}
.account-cell-dow {
  margin-top: -1px;
  font-size: 7px; line-height: 1; font-weight: 600;
  color: #82829A; /* ink-faint measured 2.5:1 — this keeps the step below ink-soft at 3.7:1 */
}

/* Scroll-synced month label above the strip — matches the tape's data-tape-month
   (8px, font-700, uppercase, ink-faint). */
.account-strip-month {
  height: 10px;
  font-size: 8px; line-height: 10px;
  font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
  color: #82829A; /* stepped up from ink-faint (2.5:1 → 3.7:1) */
  text-align: center; /* centred over the strip by its flex-col items-center wrapper */
}

/* Zigzag month boundary: the first cell of each new month clips its left edge into a zigzag, so
   consecutive months meet on a torn/perforated line. month-end no longer carries a tab; the seam is
   drawn entirely by the next month's first cell. NO margin/overlap (cells must stay uniform-width —
   see .account-strip-wrap): because flush cells don't sit behind one another, the clipped zigzag
   valleys reveal the strip's background colour directly, giving the torn-paper gap for free even
   when both months are the same colour. A 1px drop-shadow in that colour keeps the toothed edge
   crisp against the prior cell; the perforation/stub overlays below add the rest of the cream. */
.account-cell.is-month-start .account-segment {
  clip-path: polygon(
    var(--zig) 0%, 100% 0%, 100% 100%, var(--zig) 100%,
    0 87.5%, var(--zig) 75%, 0 62.5%, var(--zig) 50%,
    0 37.5%, var(--zig) 25%, 0 12.5%
  );
  filter: drop-shadow(-1px 0 0 var(--color-glass-strong));
}
/* Month-seam detailing — the torn shape shows the BACKGROUND (cream) through it, so a tear reads as
   torn paper, not a painted band. (The week signal is a separate white line — see .acct-week-line.)
   ::before — a cream band BEHIND the segment at the seam; the zigzag clip lets it show through the
   valleys, filling the gap with the background colour even between same-coloured months.
   ::after — the classic ticket-stub pinch: a half-circle bitten out of the top + bottom edge. */
.account-cell.is-month-start::before {
  content: ""; position: absolute; z-index: -1; pointer-events: none;
  left: 0; top: 0; width: var(--zig); height: var(--acct-cell-h);
  background: var(--color-glass-strong);
}
.account-cell.is-month-start::after {
  content: ""; position: absolute; z-index: 2; pointer-events: none;
  left: calc(-1px - var(--stub-r)); top: 0;
  width: calc(var(--stub-r) * 2); height: var(--acct-cell-h);
  background-image:
    radial-gradient(circle at 50% 0, var(--color-glass-strong) var(--stub-r), transparent calc(var(--stub-r) + 0.5px)),
    radial-gradient(circle at 50% 100%, var(--color-glass-strong) var(--stub-r), transparent calc(var(--stub-r) + 0.5px));
}

/* The future run (Plan 142) — ghosts are plain faded cells with no per-cell dividers; the existing
   white WEEK line (.acct-week-line) is the only separator, so the ghosts cluster into week groups. */

/* Week boundary — a white line on the cell's left edge (weeks start Monday), rendered as a child
   <span> so it can coexist with the month-tear pseudos. White ALWAYS means "week boundary": it's
   drawn at every week boundary, including on a month tear, so a tear that's also a week boundary
   shows the torn shape (month) + this line (week), while an ordinary month tear has no white line. */
.account-cell .acct-week-line {
  content: ""; position: absolute; z-index: 3; pointer-events: none;
  left: 0; top: 2px; width: 0; height: calc(var(--acct-cell-h) - 4px);
  border-left: 1px solid rgba(255, 255, 255, 0.85);
}
/* On a month tear the seam is a cream gap, where a near-white line would vanish — so nudge the week
   line just past the torn edge onto the solid fill, keeping it visible. The torn shape (month) sits
   to its left, the white line (week) just inside: both read. */
.account-cell.is-month-start .acct-week-line { left: calc(var(--zig) + 1px); }

.account-strip-nav {
  flex: 0 0 auto;
  width: 20px; height: var(--acct-cell-h);
  /* Row is items-start; offset down past the month label (10px) + strip top padding (2px) so the
     chevron's vertical centre lines up with the cell segment's centre, not the segment+date+dow
     stack. */
  margin-top: 12px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 7px;
  color: var(--color-ink-soft);
  background: var(--color-glass-subtle);
}
/* Off-state keeps the chevron's footprint (visibility, not display:none — toggled as `.is-off` by
   the controller) so every card's strip shares the same right edge whether or not it overflows. */
.account-strip-nav.is-off { visibility: hidden; pointer-events: none; }
.account-strip-nav:disabled { opacity: 0.3; pointer-events: none; }
.account-strip-nav:active { transform: scale(0.92); }

/* Money controls on a card (Plan 142): the +◎ account button (a COIN — credits are currency-neutral). Quiet mint
   circular buttons (mint = our money colour, matching Pay / Enter); they open glass-window panels /
   pour the reserve. */
.account-pay-btn, .account-faucet-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; border-radius: 9999px; border: none;
  color: var(--color-mint); background: color-mix(in srgb, var(--color-mint) 16%, white);
  cursor: pointer; padding: 0;
  transition: transform 0.1s ease, background 0.15s ease, color 0.15s ease;
}
/* Standard tactile press: scale-down + brightness darken (matches .tactile/.btn-press). */
.account-pay-btn:active, .account-faucet-btn:active { transform: scale(0.92); filter: brightness(0.85); }
.account-pay-mark { font-size: 19px; line-height: 1; }
/* The HISTORY button beside the money button — the dog's full-page ledger statement. Same quiet
   circle, but INK, not mint: it's navigation, not a money action (colour is a claim about money
   state on these surfaces — see docs/accounts_ledger.md, "Colour rule for money surfaces"). */
.account-history-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; border-radius: 9999px; border: none;
  color: var(--color-ink-soft); background: color-mix(in srgb, var(--color-ink) 8%, white);
  cursor: pointer; padding: 0; text-decoration: none;
  transition: transform 0.1s ease, filter 0.15s ease;
}
.account-history-btn:active { transform: scale(0.92); filter: brightness(0.9); }
/* Dogs-index info hint (Plan 194 review): pinned to the card's top-right corner,
   out of the row's flex flow so the schedule tape never shifts. Smaller than the
   accounts buttons — it's a hint, not a control. */
.dog-card-info { position: absolute; top: 3px; right: 6px; width: 22px; height: 22px; opacity: 0.7; }
.account-history-mark { font-size: 18px; line-height: 1; }
/* The faucet shares the +$ button's exact look — same brand colour + soft background (inherited from
   the shared rule above), so the two money controls match. Always present; greyed when nothing to pour. */
.account-faucet-btn .material-symbols-outlined { transform: scaleX(-1); } /* flip the faucet to pour the other way */
.account-faucet-btn:disabled { opacity: 0.4; cursor: default; }
.account-faucet-btn:disabled:active { transform: none; }

/* ── Account panel (glass window content for the reserve + service ledgers, Plan 142) ── */
.account-panel { display: flex; flex-direction: column; gap: 10px; min-height: 0; }
/* When the viewport-capped drawer squeezes the panel, ONLY the claims block absorbs it —
   figures, title, toggle and hint are fixed-size (the keypad below never shrinks at all). */
.account-panel > * { flex-shrink: 0; }
.account-panel > .acct-claims { flex-shrink: 1; }
.account-panel-head { text-align: center; position: relative; } /* relative anchors the bonus float */
.account-panel-figure {
  font-family: var(--font-display); font-weight: 800; font-size: 30px;
  font-variant-numeric: tabular-nums; color: var(--color-ink); line-height: 1.1;
  transition: color 0.5s ease; /* eases back from the flow tint when the count-down settles */
}
/* While the figure counts down (deposited credits flowing onto services) it tints the credit blue. */
.account-panel-figure.is-flowing { color: #4A8BFF; transition: none; }
.account-panel-figure-label {
  display: block; font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--color-ink-soft);
}
/* Credits + Unpaid: two equal figures side by side, with an "apply →" affordance between them
   that pours all held credits onto the bills (Plan 144 reshaped). */
.account-panel-figures { display: flex; justify-content: center; align-items: flex-start; gap: 1rem; }
.account-panel-col { display: inline-flex; flex-direction: column; align-items: center; gap: 2px; }
.acct-apply-arrow {
  margin-top: 5px; /* center the pill on the 30px figure row, above the labels */
  display: inline-flex; align-items: center; gap: 4px;
  padding: 5px 12px; border: none; border-radius: 9999px;
  background: var(--color-brand-light); color: var(--color-brand);
  font-family: var(--font-display); font-weight: 700; font-size: 12px;
  text-transform: uppercase; letter-spacing: 0.04em; cursor: pointer;
  transition: transform 0.1s ease;
}
.acct-apply-arrow:active { transform: scale(0.95); }
.acct-apply-caret { font-size: 15px; line-height: 1; }
/* Per-dog "auto-apply credits" toggle (button_to), under the figures. */
.acct-auto-apply { display: flex; justify-content: center; margin-top: 10px; }
.acct-auto-apply-toggle {
  display: inline-flex; align-items: center; gap: 6px; cursor: pointer; border: none; background: none;
  padding: 4px 6px; font-family: var(--font-display); font-weight: 700;
  font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em; color: var(--color-ink-soft);
}
.acct-auto-apply-box {
  width: 16px; height: 16px; border-radius: 5px; border: 1.5px solid var(--color-ink-faint);
  display: inline-flex; align-items: center; justify-content: center; transition: all 0.12s ease;
}
.acct-auto-apply-toggle.is-on { color: var(--color-brand); }
.acct-auto-apply-toggle.is-on .acct-auto-apply-box { background: var(--color-brand); border-color: var(--color-brand); }
.acct-auto-apply-toggle.is-on .acct-auto-apply-box::after {
  content: ""; width: 4px; height: 8px; border: solid #fff; border-width: 0 2px 2px 0;
  transform: rotate(45deg) translate(-0.5px, -1px);
}
.account-panel-sub { display: block; font-size: 12px; color: var(--color-ink-soft); margin-top: 2px; }
.account-panel-error { color: #b91c1c; font-size: 12px; text-align: center; margin: 0; }
.acct-ref {
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; font-weight: 800;
  font-size: 24px; letter-spacing: 0.12em; color: var(--color-ink);
}
.acct-copy {
  margin-left: 8px; padding: 2px 10px; border: none; border-radius: 9999px;
  background: var(--color-glass-subtle); color: var(--color-ink-soft);
  font-size: 12px; font-weight: 700; cursor: pointer;
}
.acct-display {
  height: 38px; padding: 0 12px; border-radius: 10px; background: var(--color-glass-subtle);
  display: flex; align-items: center; justify-content: flex-end;
  font-family: var(--font-display); font-weight: 800; font-size: 22px;
  font-variant-numeric: tabular-nums; color: var(--color-ink);
}
/* Calculator pad: 4 columns — digits + ⌫ / + / − in the last column, and a full-width 0. */
.acct-pad { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; }
.acct-key {
  height: 44px; border: none; border-radius: 10px;
  background: var(--color-glass-subtle); color: var(--color-ink);
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-display); font-weight: 700; font-size: 18px;
  cursor: pointer; transition: transform 0.08s ease;
}
.acct-key:active { transform: scale(0.94); }
.acct-key:disabled { opacity: 0.3; cursor: default; } /* e.g. the − operator on the positive-only Pay panel */
.acct-key:disabled:active { transform: none; }
.acct-key-aux { color: var(--color-ink-soft); }
.acct-key-op { /* + / − operators — tinted so they read as the "calculator" keys */
  background: color-mix(in srgb, var(--color-brand) 14%, white);
  color: var(--color-brand); font-weight: 800; font-size: 22px;
}
.acct-key-zero { grid-column: 1 / -1; } /* wide 0 spanning the bottom row (no Enter key) */
.acct-key-half { grid-column: span 2; } /* 0 sharing the bottom row with the Enter key */
.acct-key-enter { /* the calculator's submit key (reserve panel) — solid mint, the action colour */
  grid-column: span 2; background: var(--color-mint); color: #fff; font-weight: 800; font-size: 16px;
}
.acct-sign-hint { margin: 0; font-size: 11px; color: var(--color-ink-faint); text-align: center; }
.acct-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.acct-act {
  height: 44px; border: none; border-radius: 9999px; color: #fff;
  font-family: var(--font-display); font-weight: 800; font-size: 14px; cursor: pointer;
  transition: transform 0.08s ease, opacity 0.15s ease;
}
.acct-act:active { transform: scale(0.96); }
.acct-act:disabled { opacity: 0.4; pointer-events: none; }
.acct-act-in { background: var(--color-mint); }
.acct-act-transfer { background: var(--color-brand); }
.acct-act-out { background: var(--color-ink-soft); }
/* ── Payment claims (Plan 194) ─────────────────────────────────────────────────────────────
   Owner "I sent ◎N" annotations surfacing walker-side: the roster card's "said sent" badge and
   the drawer panel's pending list / post-deposit match offer. Ink, not mint — a claim is money
   the CLIENT says is moving, not money in the books (the colour rule: tint = financial claim). */
.acct-claim-badge {
  display: inline-flex; align-items: center; gap: 3px; margin-top: 2px;
  padding: 1px 8px; border-radius: 9999px;
  background: var(--color-glass-subtle); color: var(--color-ink-soft);
  font-family: var(--font-display); font-weight: 700; font-size: 11px;
  font-variant-numeric: tabular-nums;
}
.acct-claim-badge .material-symbols-outlined { font-size: 13px; }
.acct-claims {
  margin: 10px 0 2px; padding: 8px 10px; border-radius: 12px;
  background: var(--color-glass-subtle);
  display: flex; flex-direction: column; min-height: 0;
}
.acct-claims > :not(.acct-claims-scroll) { flex-shrink: 0; } /* title + the picker's skip row stay put */
.acct-claims-title {
  margin: 0 0 6px; font-family: var(--font-display); font-weight: 800; font-size: 11px;
  color: var(--color-ink-soft); text-transform: uppercase; letter-spacing: 0.04em;
}
.acct-claim-row { display: flex; align-items: center; gap: 8px; min-height: 28px; }
.acct-claim-row + .acct-claim-row { margin-top: 6px; }
.acct-claim + .acct-claim { margin-top: 2px; }
/* The claim ROWS live in the drawer's one FLEXIBLE region (title + skip stay put): the drawer is
   hard-capped at the viewport and everything else is fixed-size, so this box gets exactly the
   leftover height — many claims scroll internally, few claims shrink it to content (no fixed cap).
   touch-action pan-y + the _wireFling exclusion keep an in-list swipe a SCROLL, never the
   drawer's drag-to-dismiss; overscroll containment stops a finished scroll leaking anywhere. */
.acct-claims-scroll {
  flex: 1 1 auto; min-height: 0; overflow-y: auto;
  overscroll-behavior: contain;
  touch-action: pan-y; /* overrides the tray's touch-action: none inside this zone */
}
.acct-claims-scroll + .acct-claim-row { margin-top: 6px; } /* the picker's skip row below the box */
.acct-claim-row form.button_to { display: contents; } /* keep button_to's form out of the flex layout */
.acct-claim-meta {
  flex: 1; min-width: 0; font-family: var(--font-display); font-weight: 600; font-size: 12px;
  color: var(--color-ink); font-variant-numeric: tabular-nums;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; /* ONE line, always — the actions share it */
}
.acct-claim-match {
  border: none; border-radius: 9999px; padding: 5px 12px; cursor: pointer;
  background: var(--color-mint); color: #fff;
  font-family: var(--font-display); font-weight: 800; font-size: 12px;
}
.acct-claim-match.is-primary { flex: 1; min-width: 0; padding: 9px 12px; text-align: center; }
/* Multi-claim picker (post-deposit offer): each pending claim is ONE full-width tappable row —
   owner · ◎amount · method · date · “note” on the left, a quiet "same amount" tag when the
   claim matches the just-recorded entry (those rows list first: the likeliest match is the
   top tap). White pill = the candidate surface, same vocabulary as .acct-claim-candidate. */
.acct-claim-pick {
  flex: 1; min-width: 0; display: flex; align-items: center; gap: 8px;
  border: none; border-radius: 10px; padding: 8px 10px; cursor: pointer;
  background: #fff; color: var(--color-ink); text-align: left;
  font-family: var(--font-display); font-weight: 600; font-size: 12px;
  font-variant-numeric: tabular-nums;
}
.acct-claim-pick-meta { flex: 1; min-width: 0; }
.acct-claim-same {
  flex: none; padding: 1px 7px; border-radius: 9999px;
  background: var(--color-mint-light); color: var(--color-mint);
  font-weight: 800; font-size: 10px; text-transform: uppercase; letter-spacing: 0.03em;
  white-space: nowrap;
}
.acct-claim-skip, .acct-claim-dismiss {
  border: none; background: none; padding: 5px 8px; cursor: pointer;
  font-family: var(--font-display); font-weight: 700; font-size: 12px;
  color: var(--color-ink-faint); white-space: nowrap;
}
/* LATE claim matching: each compact FYI row carries an inline "match" link — a bare <details>
   holding only the summary (so the drawer's summary-tap fling exclusion still covers it); its
   [open] state reveals the SIBLING .acct-claim-picker below the row via :has(), keeping the row
   itself one line. The picker lists candidate receipts (the dog's newest unmatched incoming
   payment entries, same-amount first) as white pills — the owner method-pill surface. */
.acct-claim-late { flex: none; }
.acct-claim-late summary {
  list-style: none; display: inline-flex; align-items: center; cursor: pointer; padding: 5px 8px;
  font-family: var(--font-display); font-weight: 700; font-size: 12px;
  color: var(--color-brand); white-space: nowrap;
}
.acct-claim-late summary::-webkit-details-marker { display: none; }
.acct-claim-late[open] summary { color: var(--color-ink-soft); }
.acct-claim-picker { display: none; }
.acct-claim:has(.acct-claim-late[open]) > .acct-claim-picker { display: block; }
.acct-claim-late-list { display: flex; flex-wrap: wrap; gap: 4px; padding: 4px 0 2px; }
.acct-claim-late-list form.button_to { display: contents; }
.acct-claim-candidate {
  border: none; border-radius: 9999px; padding: 6px 12px; cursor: pointer;
  background: #fff; color: var(--color-ink);
  font-family: var(--font-display); font-weight: 700; font-size: 12px;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.acct-claim-late-empty {
  margin: 0; padding: 4px 0 2px;
  font-family: var(--font-display); font-weight: 600; font-size: 11px;
  color: var(--color-ink-faint);
}
/* Owner side: the "I sent a payment" disclosure + its method pills (owner shell — no keypad). */
.claim-details summary { list-style: none; }
.claim-details summary::-webkit-details-marker { display: none; }
.claim-details[open] .claim-details-caret { transform: rotate(180deg); }
.claim-details-caret { transition: transform 0.15s ease; }
.claim-method-pill { position: relative; display: inline-flex; cursor: pointer; }
.claim-method-pill input { position: absolute; opacity: 0; pointer-events: none; }
.claim-method-pill span {
  display: inline-flex; align-items: center; padding: 6px 12px; border-radius: 9999px;
  background: #fff; color: var(--color-ink-soft);
  font-family: var(--font-display); font-weight: 700; font-size: 12px;
}
.claim-method-pill input:checked + span { background: var(--color-brand); color: #fff; }
/* The account KEYBOARD (Plan 144) — one persistent bottom tray shared by every money surface, summoned
   like an OS keyboard: slides up from below the viewport, slides back down when the surface closes, and
   can be FLUNG down (velocity gesture — but never dragged around). Above the glass overlay (z-300/301)
   so it stays tappable under an open Account window. */
.acct-keyboard {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 320;
  max-width: 480px; margin: 0 auto;
  /* The app's standard frosted glass (matches .glass-strong) — content scrolling under the drawer
     reads through the blur, like the OS keyboard. */
  background: var(--color-glass-strong);
  backdrop-filter: blur(24px) saturate(1.5);
  -webkit-backdrop-filter: blur(24px) saturate(1.5);
  border: 1px solid var(--color-glass-subtle); border-bottom: none;
  border-radius: 20px 20px 0 0;
  box-shadow: 0 -10px 36px rgba(12, 20, 42, 0.22);
  padding: 6px 12px calc(12px + env(safe-area-inset-bottom));
  display: flex; flex-direction: column; gap: 8px;
  /* HARD viewport cap (Plan 194 addendum): the drawer must NEVER extend above the viewport.
     Bottom-anchored, so 100dvh minus the top safe area (+8px breathing room) is the ceiling;
     inside, the keypad/display/actions are fixed flex items and the claims area is the ONLY
     flexible one (min-height 0 chain down to .acct-claims-scroll) — many claims scroll
     internally, few claims shrink. The translateY slide is transform-based and unaffected. */
  max-height: calc(100dvh - env(safe-area-inset-top, 0px) - 8px);
  transform: translateY(110%);
  transition: transform 0.28s cubic-bezier(0, 0, 0.2, 1); /* keyboard-like slide */
  touch-action: none; /* a vertical swipe on the tray is the fling, never page scroll */
}
.acct-keyboard.is-up { transform: none; }
.acct-keyboard-grip { width: 36px; height: 4px; border-radius: 9999px; background: rgba(12, 20, 42, 0.15); margin: 2px auto 0; flex-shrink: 0; }
/* The drawer's summary area — the tapped surface's panel (title · balance · hints) above the keys.
   It's the drawer's one SHRINKABLE flex item (the keys/display/actions below are fixed): a flex
   column of its own so the squeeze reaches the injected .account-panel and, through it, the
   claims scroller. */
.acct-keyboard-panel { display: flex; flex-direction: column; min-height: 0; }
.acct-keyboard-panel:not(:empty) { padding: 2px 2px 4px; border-bottom: 1px solid var(--color-glass-subtle); }
/* The keypad is ALWAYS fully visible — nothing below the summary area may shrink. */
.acct-keyboard > .acct-display, .acct-keyboard > .acct-pad, .acct-keyboard > .acct-actions { flex-shrink: 0; }
.acct-panel-title {
  margin: 0 0 2px; font-family: var(--font-display);
  font-weight: 700; font-size: 13px; color: var(--color-ink-soft);
  display: flex; align-items: center; justify-content: center; gap: 8px; /* thumb + "who · what · when" */
}
/* The date deep-links to that calendar day + service tab — brand-tinted with a small calendar glyph. */
.acct-date-link { color: var(--color-brand); text-decoration: none; white-space: nowrap; }
.acct-date-link .material-symbols-outlined { font-size: 13px; vertical-align: -2px; margin-right: 1px; }
/* Tap / dismiss confirmation — the ticket FLASHES TWICE (a white overlay blink on the whole segment).
   The tap flash fills the beat before the drawer rises ("you hit this one"); the dismissal flash is
   the parting "this is what you had open" cue. The overlay is a REAL element (.acct-flash-overlay in
   the cell partial) — both the segment's and the cell's pseudo-elements are already taken (settled
   punch hole, month-tear fragments), which kept hijacking the flash's geometry. */
@keyframes acct-cell-flash { 0%, 100% { opacity: 0; } 50% { opacity: 1; } }
.account-cell.is-acct-flash { z-index: 5; }
.acct-flash-overlay {
  position: absolute; top: 0; left: 0; right: 0; height: var(--acct-cell-h);
  pointer-events: none; z-index: 6;
  background: rgba(255, 255, 255, 0.9); opacity: 0;
}
.account-cell.is-acct-flash .acct-flash-overlay { animation: acct-cell-flash 0.15s ease 2; }
.account-panel-figure-of { font-size: 16px; font-weight: 700; color: var(--color-ink-soft); } /* the "/m" after the big paid figure */
/* Overpay bonus — the excess credited to the account EMANATES FROM THE "paid n/m" FRACTION itself,
   popping out of the number and floating up as it fades, video-game style ("+29"). A real element
   spawned by account_panel_controller, anchored to .account-panel-figure; mint = money. */
@keyframes acct-bonus-float {
  0% { opacity: 0; transform: translate(-50%, -50%) scale(0.7); }
  10% { opacity: 1; transform: translate(calc(-50% + 14px), calc(-50% - 28px)) scale(1.05); }
  70% { opacity: 1; transform: translate(calc(-50% + 54px), calc(-50% - 80px)) scale(1); } /* hold readable while drifting */
  100% { opacity: 0; transform: translate(calc(-50% + 76px), calc(-50% - 110px)) scale(1); }
}
.acct-bonus-float {
  position: absolute; left: 50%; top: 50%; pointer-events: none; z-index: 10;
  font-family: var(--font-display); font-weight: 800; font-size: 18px; color: var(--color-mint);
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.8); white-space: nowrap;
  animation: acct-bonus-float 2.2s ease-out forwards;
}

/* The side note ("· n unpaid") hangs OFF the centred figure (absolute → it doesn't shift the big
   number off-centre). The figure gets position:relative to anchor it. */
.account-panel-figure { position: relative; display: inline-block; }
.account-panel-credits {
  position: absolute; left: calc(100% + 8px); top: 50%; transform: translateY(-50%);
  font-size: 14px; font-weight: 600; color: var(--color-ink-soft); white-space: nowrap;
}
/* The ledger reads as a TABLE: a fixed header + grid rows (amount · detail · type · date), amounts and
   dates right-aligned, faint row rules. The header sits above the scroll area so it stays put. */
.acct-history-head, .acct-row {
  display: grid; grid-template-columns: 52px 1fr 62px 52px; gap: 6px; align-items: start; padding: 5px 4px;
}
.acct-history-head {
  font-size: 9px; text-transform: uppercase; letter-spacing: 0.06em; font-weight: 700;
  color: var(--color-ink-faint); margin-top: 6px; padding-top: 6px;
  border-top: 1px solid var(--color-glass-subtle);
}
.acct-history-head .h-amt, .acct-history-head .h-date { text-align: right; }
.acct-history { list-style: none; margin: 0; padding: 0; max-height: 180px; overflow-y: auto; }
.acct-row { font-size: 13px; border-bottom: 1px solid color-mix(in srgb, var(--color-ink) 6%, transparent); }
.acct-row:last-child { border-bottom: none; }
.acct-amt { font-variant-numeric: tabular-nums; font-weight: 800; text-align: right; }
.acct-amt.is-in { color: color-mix(in srgb, var(--color-mint) 70%, #000); }
.acct-amt.is-out { color: var(--color-ink-soft); }
.acct-kind { color: var(--color-ink); text-transform: capitalize; display: flex; flex-direction: column; line-height: 1.2; min-width: 0; }
.acct-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.acct-source { text-transform: none; color: var(--color-ink-faint); font-size: 11px; font-weight: 400; }
.acct-type { color: var(--color-ink-soft); text-transform: capitalize; font-size: 12px; }
.acct-date { color: var(--color-ink-faint); font-size: 12px; text-align: right; }
.acct-empty { color: var(--color-ink-faint); font-size: 12px; text-align: center; padding: 8px; }
/* The Pay panel's statement ending balance — the rows above reconcile to this `available` total. */
.acct-statement-end {
  display: flex; align-items: baseline; justify-content: space-between;
  margin-top: 6px; padding-top: 8px; border-top: 1px solid var(--color-glass-subtle);
}
.acct-statement-end span { font-size: 12px; color: var(--color-ink-soft); text-transform: uppercase; letter-spacing: 0.04em; }
.acct-statement-end strong { font-variant-numeric: tabular-nums; font-weight: 800; font-size: 16px; color: var(--color-ink); }
/* The account HISTORY SHEET (Plan 194 addendum) — a card's history button summons the dog's full
   ledger as a bottom sheet with the keyboard drawer's slide (same glass, same curve), instead of
   navigating away. Taller than the drawer (a reading surface, 88dvh) and ABOVE it (z 338–340 vs
   the drawer's 320 — opening one closes the other), with a tap-to-close backdrop the drawer never
   needed. The sheet's BODY is the scroller — the shell's lazy month frames chain inside it — so
   only the grip zone is drag chrome (touch-action:none there alone; the body keeps touch scroll). */
.acct-sheet-backdrop {
  position: fixed; inset: 0; z-index: 338;
  background: rgba(12, 20, 42, 0.35);
  opacity: 0; pointer-events: none;
  transition: opacity 0.28s ease;
}
.acct-sheet-backdrop.is-on { opacity: 1; pointer-events: auto; }
.acct-history-sheet {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 340;
  max-width: 480px; margin: 0 auto;
  height: 88dvh;
  display: flex; flex-direction: column;
  background: var(--color-glass-strong);
  backdrop-filter: blur(24px) saturate(1.5);
  -webkit-backdrop-filter: blur(24px) saturate(1.5);
  border: 1px solid var(--color-glass-subtle); border-bottom: none;
  border-radius: 20px 20px 0 0;
  box-shadow: 0 -10px 36px rgba(12, 20, 42, 0.22);
  transform: translateY(110%);
  transition: transform 0.28s cubic-bezier(0, 0, 0.2, 1); /* the drawer's keyboard-like slide */
}
.acct-history-sheet.is-up { transform: none; }
.acct-sheet-grip { padding: 8px 0 6px; flex-shrink: 0; touch-action: none; cursor: grab; }
.acct-sheet-body {
  flex: 1; min-height: 0;
  overflow-y: auto; -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain; /* the sheet's scroll never chains to the locked page */
  padding: 0 16px calc(16px + env(safe-area-inset-bottom));
}
