*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

:root {
  --orange: #FF6B35;
  --teal: #2EC4B6;
  --purple: #9B5DE5;
  --yellow: #F7C948;
  --green: #4CAF50;
  --red: #E53935;
  --bg: #F8F5F1;
  --surface: #FFFFFF;
  --text: #2D2A26;
  --muted: #7A756E;
  --border: #E8E3DC;
  --radius: 12px;
  --shadow: 0 2px 12px rgba(0,0,0,0.08);
  --shadow-lg: 0 8px 32px rgba(0,0,0,0.12);
}

body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: var(--bg) url("assets/bg-texture.png") repeat top left; background-size: 480px; color: var(--text); min-height: 100vh; }

a { color: var(--orange); text-decoration: none; }
a:hover { text-decoration: underline; }

button { cursor: pointer; border: none; font-family: inherit; font-size: 14px; transition: all 0.15s; }
input, select, textarea { font-family: inherit; font-size: 14px; border: 1.5px solid var(--border); border-radius: 8px; padding: 10px 14px; width: 100%; background: var(--surface); color: var(--text); transition: border-color 0.15s; }
input:focus, select:focus, textarea:focus { outline: none; border-color: var(--orange); }
textarea { resize: vertical; min-height: 80px; }
label { display: block; font-size: 13px; font-weight: 600; color: var(--muted); margin-bottom: 6px; }

/* ── Layout ── */
/* `overflow-x: clip` (not `hidden`) kills the stubborn few-px horizontal pan on
   mobile: `hidden` on the root gets propagated to the viewport and can still be
   defeated by a flex item's min-content width or a fixed child, whereas `clip`
   truly clamps the box and leaves nothing to pan. Width is pinned to the visual
   viewport so nothing can establish a wider scroll area. */
html, body { overflow-x: clip; max-width: 100%; width: 100%; }
#app { min-height: 100vh; display: flex; flex-direction: column; overflow-x: clip; max-width: 100%; width: 100%; }

/* Flex items default to min-width:auto, which lets wide/nowrap content push the
   view past the screen; min-width:0 + clip keeps every screen inside the phone. */
.view { display: none; flex-direction: column; min-height: 100vh; min-width: 0; max-width: 100%; overflow-x: clip; }
.view.active { display: flex; }

/* ── Topbar ── */
.topbar { background: var(--surface); border-bottom: 1px solid var(--border); padding: 0 20px; height: 60px; display: flex; align-items: center; justify-content: space-between; position: sticky; top: 0; z-index: 100; box-shadow: 0 1px 4px rgba(0,0,0,0.06); }
.topbar .logo { font-size: 22px; font-weight: 800; color: var(--orange); letter-spacing: -0.5px; display: flex; align-items: center; gap: 8px; }
.topbar .logo span { font-size: 20px; }
.topbar .logo-img { height: 30px; width: auto; display: block; }
.topbar-right { display: flex; align-items: center; gap: 12px; }
.avatar-sm { width: 36px; height: 36px; border-radius: 50%; background: var(--orange); color: white; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 14px; cursor: pointer; }
.badge { background: var(--orange); color: white; border-radius: 20px; padding: 2px 8px; font-size: 11px; font-weight: 700; }

/* ── Bottom Nav ── */
.bottom-nav { background: var(--surface); border-top: 1px solid var(--border); padding: 8px 0 max(8px, env(safe-area-inset-bottom)); display: flex; justify-content: space-around; position: sticky; bottom: 0; z-index: 100; }
.nav-item { display: flex; flex-direction: column; align-items: center; gap: 3px; padding: 6px 16px; border-radius: 12px; color: var(--muted); font-size: 11px; font-weight: 600; cursor: pointer; border: none; background: none; transition: color 0.15s; }
.nav-item .icon { font-size: 22px; }
.nav-item .nav-icon-img { width: 24px; height: 24px; object-fit: contain; display: block; }
.nav-item.active { color: var(--orange); }

/* ── Main content ── */
.main { flex: 1; overflow-y: auto; overflow-x: clip; padding: 20px; max-width: 600px; width: 100%; margin: 0 auto; min-width: 0; }

/* ── Cards ── */
.card { background: var(--surface); border-radius: var(--radius); box-shadow: var(--shadow); padding: 20px; margin-bottom: 16px; }
.card-title { font-size: 16px; font-weight: 700; margin-bottom: 14px; display: flex; align-items: center; justify-content: space-between; }
.card-title span { color: var(--muted); font-size: 13px; font-weight: 500; }
.card-title span.badge { color: #fff; font-size: 11px; font-weight: 700; }

/* ── Buttons ── */
.btn { padding: 11px 20px; border-radius: 10px; font-weight: 700; display: inline-flex; align-items: center; gap: 7px; justify-content: center; }
.btn-primary { background: var(--orange); color: white; }
.btn-primary:hover { background: #e55a24; }
.btn-secondary { background: var(--bg); color: var(--text); border: 1.5px solid var(--border); }
.btn-secondary:hover { background: var(--border); }
.btn-teal { background: var(--teal); color: white; }
.btn-teal:hover { background: #26a99d; }
.btn-purple { background: var(--purple); color: white; }
.btn-sm { padding: 6px 14px; font-size: 12px; border-radius: 8px; }
/* Destructive / cautionary actions (admin portal). Outlined rather than solid so
   a row of them doesn't read as three equally-loud primary buttons. */
.btn-danger { background: var(--surface); color: var(--red); border: 1.5px solid rgba(229,57,53,0.45); }
.btn-danger:hover:not(:disabled) { background: var(--red); color: #fff; border-color: var(--red); }
.btn-warn { background: var(--surface); color: #B26A00; border: 1.5px solid rgba(178,106,0,0.35); }
.btn-warn:hover:not(:disabled) { background: #B26A00; color: #fff; border-color: #B26A00; }
.btn-icon { background: none; padding: 6px; border-radius: 8px; font-size: 18px; }
.btn-icon:hover { background: var(--bg); }
.btn-full { width: 100%; }
.btn:disabled { opacity: 0.55; cursor: not-allowed; }

/* ── Auth view ── */
#view-login, #view-register { background: linear-gradient(160deg, #FFF5F0 0%, #F0F9F8 100%); justify-content: center; align-items: center; }

/* Landing: hero banner alongside the sign-in box (stacks on small screens). */
.landing { display: flex; flex: 1; width: 100%; min-height: 100vh; align-items: stretch; }
.landing .auth-box { flex-shrink: 0; align-self: center; margin: 20px auto; }
.landing-hero {
  flex: 1.1; position: relative; display: flex; align-items: flex-end;
  padding: 48px; color: var(--text);
  background: linear-gradient(180deg, rgba(255,245,240,0) 35%, rgba(255,245,240,0.92) 100%), url("assets/hero.png") center / cover no-repeat #FFF5F0;
}
.landing-hero-copy { max-width: 420px; }
.landing-hero-logo { height: 40px; width: auto; display: block; margin-bottom: 16px; }
.landing-hero-copy h2 { font-size: 30px; font-weight: 900; line-height: 1.15; letter-spacing: -0.5px; }
.landing-hero-copy p { font-size: 16px; color: var(--muted); margin-top: 10px; line-height: 1.5; }

.auth-box { background: var(--surface); border-radius: 20px; box-shadow: var(--shadow-lg); padding: 36px 32px; width: 100%; max-width: 380px; margin: 20px; }

/* Feature/benefit blocks below the landing fold, each with a brand illustration. */
.landing-features { width: 100%; padding: 56px 24px 72px; text-align: center; background: linear-gradient(180deg, rgba(255,245,240,0.92) 0%, var(--bg) 100%); }
.features-heading { font-size: 28px; font-weight: 900; letter-spacing: -0.5px; }
.features-sub { color: var(--muted); margin-top: 8px; font-size: 16px; }
.features-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; max-width: 1000px; margin: 40px auto 0; }
.feature-card { background: var(--surface); border: 1px solid var(--border); border-radius: 18px; box-shadow: var(--shadow); padding: 24px 22px 28px; text-align: center; }
.feature-img { width: 100%; max-width: 200px; height: auto; aspect-ratio: 1 / 1; object-fit: contain; margin: 0 auto 12px; display: block; }
.feature-card h3 { font-size: 18px; font-weight: 800; letter-spacing: -0.3px; }
.feature-card p { color: var(--muted); font-size: 14px; line-height: 1.55; margin-top: 8px; }
@media (max-width: 860px) {
  .landing-features { padding: 40px 20px 56px; }
  .features-heading { font-size: 24px; }
  .features-grid { grid-template-columns: 1fr; gap: 18px; max-width: 420px; }
}

@media (max-width: 860px) {
  .landing { flex-direction: column; }
  .landing-hero {
    flex: none; min-height: 220px; padding: 24px 24px 44px;
    align-items: center; justify-content: center; text-align: center;
  }
  .landing-hero-logo { margin: 0 auto 10px; }
  .landing-hero-copy h2 { font-size: 22px; }
  .landing-hero-copy p { font-size: 14px; }
  .landing .auth-box { margin: -28px auto 20px; position: relative; z-index: 1; }
}
.auth-logo { text-align: center; margin-bottom: 28px; }
.auth-logo h1 { font-size: 32px; font-weight: 900; color: var(--orange); }
.auth-logo-img { height: 52px; width: auto; display: block; margin: 0 auto 4px; }
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }
.auth-logo p { color: var(--muted); margin-top: 4px; }
.form-group { margin-bottom: 16px; }
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.auth-footer { text-align: center; margin-top: 16px; font-size: 13px; color: var(--muted); }
/* Short explanatory line above an auth form (forgot / reset password). */
.auth-note { font-size: 13px; line-height: 1.45; color: var(--muted); margin-bottom: 14px; }
.auth-hint { font-size: 12px; color: var(--muted); margin: -4px 0 4px; line-height: 1.4; }
.auth-hint.is-bad { color: var(--red); }
.auth-hint.is-good { color: var(--green); }

/* Password strength meter (reset-password form). */
.pw-meter { display: flex; align-items: center; gap: 10px; margin-top: 8px; }
.pw-meter-track { flex: 1; height: 5px; border-radius: 3px; background: var(--border); overflow: hidden; }
.pw-meter-fill { height: 100%; width: 0; border-radius: 3px; background: var(--red); transition: width 0.2s, background-color 0.2s; }
.pw-meter-label { font-size: 12px; font-weight: 600; color: var(--muted); min-width: 54px; text-align: right; }

/* ── Dashboard ── */
.section-title { font-size: 18px; font-weight: 800; margin-bottom: 12px; display: flex; align-items: center; gap: 8px; }
.greeting { font-size: 24px; font-weight: 900; margin-bottom: 4px; }
.greeting-sub { color: var(--muted); font-size: 14px; margin-bottom: 20px; }
.dash-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.dash-head-copy { min-width: 0; }
.dash-customize { flex-shrink: 0; gap: 6px; }

.dash-sec-title { display: flex; align-items: center; gap: 8px; min-width: 0; }
.dash-done-badge { font-size: 11px; font-weight: 700; color: var(--teal); background: color-mix(in srgb, var(--teal) 14%, transparent); border-radius: 999px; padding: 2px 8px; white-space: nowrap; }
.dash-done-badge:empty { display: none; }

/* Homepage blocks — each is a draggable top-level card/section. The grip lives
   in a left gutter so it never collides with the card title or its "See all"
   link, and works the same for a plain card or the Organize/Family accordions. */
.home-block { position: relative; margin-bottom: 16px; }
/* The drag "dots" (grip) only appear in Customize mode, so only reserve room
   for them then — otherwise the homepage sits flush with no left gutter. */
#dash-sections.dash-editing .home-block { padding-left: 30px; }
/* Reserve a matching right gutter in Customize mode so the Hide/Show pill sits
   beside the block instead of landing on the card's "View all" link or the
   accordion's + toggle. Collapsed restore rows lay the pill out inline, so they
   keep their own padding. */
#dash-sections.dash-editing .home-block:not(.home-block--collapsed) { padding-right: 62px; }
.home-block > .card, .home-block .accordion, .home-block-body > .accordion { margin-bottom: 0; }
.home-grip {
  position: absolute; left: 0; top: 0; width: 30px; padding: 16px 4px 0;
  display: flex; align-items: flex-start; justify-content: center;
  background: none; border: none; color: var(--muted); cursor: grab;
  touch-action: none; opacity: 0.85; transition: opacity 0.15s;
}
.home-block:hover .home-grip, .home-grip:focus-visible { opacity: 1; }
/* Per-block Hide/Show pill, shown only in Customize mode. */
.home-hide-btn {
  position: absolute; right: 8px; top: 16px; z-index: 2;
  font-size: 11px; font-weight: 700; line-height: 1; padding: 5px 9px;
  border: 1.5px solid var(--border); border-radius: 999px;
  background: var(--surface); color: var(--muted); cursor: pointer;
}
.home-hide-btn:hover { color: var(--text); border-color: var(--muted); }
/* A hidden block, collapsed to a slim restore row in Customize mode. */
.home-block--collapsed {
  display: flex; align-items: center; gap: 8px; min-height: 44px;
  border: 1.5px dashed var(--border); border-radius: 14px; padding: 8px 12px 8px 30px;
  color: var(--muted); opacity: 0.75;
}
.home-block--collapsed .home-collapsed-label {
  flex: 1; min-width: 0; display: flex; align-items: center; gap: 6px;
  font-weight: 700; font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.home-block--collapsed .home-hide-btn { position: static; }
.home-block--collapsed .home-grip { padding-top: 10px; }
.home-grip:active { cursor: grabbing; }
.home-grip .ui-ic { width: 20px; height: 20px; }
.home-block.is-dragging { box-shadow: 0 10px 28px rgba(0,0,0,0.18); border-radius: var(--radius); opacity: 0.97; }
.home-block.is-dragging .home-grip { cursor: grabbing; opacity: 0.9; }
.reorder-placeholder { border: 2px dashed var(--border); border-radius: var(--radius); margin-bottom: 16px; background: color-mix(in srgb, var(--orange) 5%, transparent); }

/* Homepage customizer — each member picks their own sections and order */
.dash-editor-hint { font-size: 13px; color: var(--muted); margin: -6px 0 14px; line-height: 1.45; }
.dash-editor-grip { display: flex; align-items: center; padding: 2px; background: none; border: none; color: var(--muted); cursor: grab; touch-action: none; }
.dash-editor-grip:active { cursor: grabbing; }
.dash-editor-row.is-dragging { box-shadow: 0 8px 20px rgba(0,0,0,0.16); background: var(--surface); }
.dash-editor-list .reorder-placeholder { margin-bottom: 0; border-radius: 14px; }
.dash-editor-list { display: flex; flex-direction: column; gap: 8px; margin-bottom: 16px; }
.dash-editor-row { display: flex; align-items: center; gap: 10px; border: 1.5px solid var(--border); border-radius: 14px; padding: 10px 12px; }
.dash-editor-row.is-off { opacity: 0.55; }
.dash-editor-ic { display: flex; color: var(--orange); }
.dash-editor-name { flex: 1; min-width: 0; font-weight: 700; font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dash-editor-moves { display: flex; gap: 2px; }
.dash-editor-moves .btn-icon:disabled { opacity: 0.3; cursor: default; }
.dash-editor-toggle { display: flex; align-items: center; gap: 6px; margin: 0; font-size: 12px; font-weight: 600; color: var(--muted); cursor: pointer; }
.dash-editor-toggle input { width: 16px; height: 16px; accent-color: var(--orange); }

/* Homepage section previews */
.dash-reward-ic { flex-shrink: 0; color: var(--purple); display: flex; }
.dash-more { font-size: 13px; color: var(--muted); margin: 10px 0 0; }

/* Stats row */
.stats-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-bottom: 20px; }
.stat-card { background: var(--surface); border-radius: var(--radius); padding: 16px 12px; text-align: center; box-shadow: var(--shadow); }
.stat-card .stat-val { font-size: 28px; font-weight: 900; }
.stat-card .stat-lbl { font-size: 11px; color: var(--muted); font-weight: 600; margin-top: 2px; }
.stat-orange .stat-val { color: var(--orange); }
.stat-teal .stat-val { color: var(--teal); }
.stat-purple .stat-val { color: var(--purple); }

/* ── Task items ── */
.task-list { display: flex; flex-direction: column; gap: 10px; }
.task-item { background: var(--surface); border-radius: var(--radius); padding: 14px 16px; display: flex; align-items: center; flex-wrap: wrap; gap: 12px; box-shadow: var(--shadow); border-left: 4px solid transparent; transition: transform 0.1s; }
.task-item:active { transform: scale(0.99); }
.task-item.done { opacity: 0.6; border-left-color: var(--green); }
.task-item.overdue { border-left-color: var(--red); }
.task-item.pending-approval { border-left-color: var(--yellow); }
.task-item.expanded { border-left-color: var(--teal); }
.task-check { width: 26px; height: 26px; border-radius: 50%; border: 2.5px solid var(--border); background: white; display: flex; align-items: center; justify-content: center; cursor: pointer; flex-shrink: 0; font-size: 14px; transition: all 0.15s; }
.task-check.checked { background: var(--green); border-color: var(--green); color: white; }
.task-info { flex: 1; min-width: 0; cursor: pointer; }
.task-name { font-weight: 600; font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.task-meta { font-size: 12px; color: var(--muted); margin-top: 2px; display: flex; gap: 8px; flex-wrap: wrap; }
.task-points { font-size: 12px; font-weight: 700; color: var(--orange); white-space: nowrap; }
/* Inline "Assign a day" control on the To-do tab. cursor:default cancels the
   .task-info row cursor so the picker area doesn't read as the expand toggle. */
.todo-day { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 10px; margin-top: 8px; cursor: default; }
.todo-day-text { font-size: 12px; font-weight: 600; color: var(--muted); }
.todo-day-actions { display: flex; align-items: center; gap: 8px; }
.todo-day-input { width: auto; padding: 5px 10px; font-size: 12px; border-radius: 8px; }
.todo-day-input:focus { border-color: var(--teal); outline: none; }
.todo-day-cta { font-size: 12px; font-weight: 600; color: var(--teal); }
.todo-day-clear { white-space: nowrap; }
/* Expandable task details (tap a task to reveal). */
.task-detail { flex-basis: 100%; width: 100%; border-top: 1px solid var(--border); margin-top: 4px; padding-top: 10px; }
.task-detail-desc { font-size: 13px; line-height: 1.45; color: var(--text, #333); white-space: pre-wrap; }
.task-detail-desc.muted { color: var(--muted); font-style: italic; }
.task-detail-facts { display: grid; grid-template-columns: 1fr 1fr; gap: 6px 16px; margin-top: 10px; }
.task-detail-facts > div { display: flex; justify-content: space-between; gap: 8px; font-size: 12px; }
.task-detail-facts span { color: var(--muted); }
.task-detail-facts b { font-weight: 600; text-align: right; }
/* Locked check circle: task has an unfinished checklist. */
.task-check.locked { border-style: dashed; background: var(--bg); font-size: 12px; cursor: pointer; }
/* Checklist inside the expanded detail. */
.task-checklist { margin: 4px 0 2px; }
.task-checklist-head { font-size: 12px; font-weight: 700; color: var(--muted); text-transform: uppercase; letter-spacing: .03em; margin-bottom: 6px; display: flex; align-items: center; gap: 8px; }
.cl-progress { font-weight: 700; color: var(--orange); background: var(--bg); border-radius: 20px; padding: 1px 8px; letter-spacing: 0; }
.cl-progress.done { color: var(--green); }
.cl-item { display: flex; align-items: flex-start; gap: 10px; padding: 7px 0; font-size: 14px; cursor: pointer; border-bottom: 1px solid var(--border); }
.cl-item:last-of-type { border-bottom: 0; }
.cl-item input { width: 20px; height: 20px; margin-top: 1px; flex-shrink: 0; accent-color: var(--green); cursor: pointer; }
.cl-item.done span { text-decoration: line-through; color: var(--muted); }
.cl-hint { font-size: 12px; color: var(--muted); margin-top: 6px; font-style: italic; }
/* Checklist editor (add-task / rooms / templates). */
.cl-edit-row { display: flex; gap: 8px; margin-bottom: 6px; }
.cl-edit-input { flex: 1; min-width: 0; }
.cl-edit-del { flex-shrink: 0; width: 38px; border: 1.5px solid var(--border); background: var(--surface); border-radius: 10px; color: var(--muted); font-size: 14px; cursor: pointer; }
.cl-edit-del:hover { color: var(--red); border-color: var(--red); }
.btn-ghost { background: var(--bg); color: var(--text, #333); border: 1.5px dashed var(--border); }
/* Time-of-day sections in the task list. */
.tod-group + .tod-group { margin-top: 18px; }
.tod-head { font-size: 12px; font-weight: 800; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); margin: 0 2px 8px; }
/* Bills & Subscriptions. */
.topbar-title { font-weight: 800; font-size: 16px; }
.parent-tool-btn { margin-top: 14px; text-align: left; justify-content: flex-start; }

/* ── Accordion / collapsible bar (FAQ-style) ── */
.accordion { margin-bottom: 16px; }
.accordion-bar {
  width: 100%;
  background: var(--surface);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 16px 18px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  cursor: pointer;
  text-align: left;
}
.accordion-title { font-size: 16px; font-weight: 700; display: flex; align-items: center; gap: 8px; }
.accordion-title .ui-ic { color: var(--orange); }
.accordion-plus {
  font-size: 22px;
  font-weight: 700;
  line-height: 1;
  color: var(--orange);
  width: 24px;
  text-align: center;
  flex-shrink: 0;
}
.accordion-bar.is-open { border-bottom-left-radius: 0; border-bottom-right-radius: 0; }
.accordion-body {
  background: var(--surface);
  border-radius: 0 0 var(--radius) var(--radius);
  box-shadow: var(--shadow);
  padding: 6px 18px 16px;
  margin-top: -2px;
}
.accordion-body .parent-tool-btn:first-child { margin-top: 8px; }
.bills-summary { display: flex; justify-content: space-around; text-align: center; padding: 16px; }
.bills-summary { gap: 8px; }
.bills-summary > div { min-width: 0; }
.bills-summary .bs-val { font-size: clamp(17px, 5.5vw, 22px); font-weight: 900; color: var(--orange); white-space: nowrap; }
.bills-summary .bs-val.warn { color: var(--red); }
.bills-summary .bs-lbl { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: .03em; margin-top: 2px; }
.bill-row { display: flex; align-items: center; gap: 10px; padding: 11px 0; border-bottom: 1px solid var(--border); }
.bill-row:last-child { border-bottom: 0; }
.bill-row.is-paid { opacity: 0.62; }
.bill-ic { font-size: 22px; width: 32px; text-align: center; flex-shrink: 0; }
.bill-info { flex: 1; min-width: 0; cursor: pointer; }
.bill-name { font-weight: 700; font-size: 14px; }
.bill-meta { font-size: 12px; color: var(--muted); margin-top: 1px; }
.bill-due.soon { color: var(--red); font-weight: 700; }
.bill-due.paid { color: var(--green); font-weight: 700; }
.bill-paid-toggle { flex-shrink: 0; display: flex; align-items: center; gap: 5px; font-size: 12px; color: var(--muted); cursor: pointer; }
.bill-paid-toggle input { width: 18px; height: 18px; accent-color: var(--green); }
/* Shopping list. */
.shop-add { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 14px; }
.shop-add input, .shop-add .btn { box-sizing: border-box; }
.shop-add #shop-add-name { flex: 1 1 100%; min-width: 0; }
.shop-add .shop-qty { flex: 0 0 88px; min-width: 0; text-align: center; }
.shop-add .btn { flex: 1 1 auto; }
.shop-add .shop-store-select { flex: 1 1 130px; min-width: 0; box-sizing: border-box; }
.shop-new-store #shop-new-store-name { flex: 1 1 100%; min-width: 0; }
.shop-store-tag { font-size: 12px; font-weight: 600; color: var(--purple); background: var(--bg); border-radius: 20px; padding: 1px 9px; flex-shrink: 0; }
.shop-cat { font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); margin: 16px 2px 6px; }
.shop-cat:first-child { margin-top: 0; }
.shop-cat-done { display: flex; align-items: center; justify-content: space-between; color: var(--green); }
.shop-cat-done .btn { padding: 2px 10px; font-size: 12px; }
.shop-item { display: flex; align-items: center; gap: 10px; padding: 9px 0; border-bottom: 1px solid var(--border); }
.shop-item:last-child { border-bottom: 0; }
.shop-check { width: 22px; height: 22px; accent-color: var(--green); flex-shrink: 0; }
.shop-body { flex: 1; min-width: 0; cursor: pointer; display: flex; align-items: baseline; gap: 8px; }
.shop-name { font-weight: 600; font-size: 15px; }
.shop-qty-tag { font-size: 12px; color: var(--muted); background: var(--bg); border-radius: 20px; padding: 1px 9px; flex-shrink: 0; }
.shop-item.done .shop-name { text-decoration: line-through; color: var(--muted); }
.shop-del { flex-shrink: 0; border: 0; background: transparent; color: var(--muted); font-size: 16px; cursor: pointer; padding: 4px 6px; line-height: 1; }
.shop-del:hover { color: var(--red); }
/* Store integrations (Walmart / Instacart). */
.shop-stores { display: flex; gap: 8px; margin-bottom: 16px; }
.store-btn { flex: 1; min-width: 0; white-space: nowrap; display: inline-flex; align-items: center; justify-content: center; gap: 7px; font-weight: 700; font-size: 13px; padding: 10px 8px; border-radius: 12px; border: 1.5px solid var(--border); background: var(--surface); color: var(--text); cursor: pointer; }
.store-btn .store-ic { font-size: 16px; }
.store-walmart { border-color: #0071dc; color: #0071dc; }
.store-walmart:hover { background: #0071dc; color: #fff; }
.store-instacart { border-color: #0aad0a; color: #0aad0a; }
.store-instacart:hover:not(.is-soon) { background: #0aad0a; color: #fff; }
.store-btn.is-soon { opacity: 0.6; cursor: default; border-color: var(--border); color: var(--muted); }
.soon-badge { font-size: 9px; font-weight: 800; text-transform: uppercase; letter-spacing: .04em; background: var(--muted); color: #fff; border-radius: 20px; padding: 1px 6px; }
.store-note { font-size: 13px; color: var(--muted); margin-bottom: 12px; line-height: 1.4; }
.store-links { display: flex; flex-direction: column; gap: 6px; max-height: 46vh; overflow-y: auto; }
.store-link { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 10px 12px; border: 1.5px solid var(--border); border-radius: 10px; text-decoration: none; color: var(--text); font-weight: 600; font-size: 14px; }
.store-link:hover { border-color: #0071dc; }
.store-link-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.store-link-go { flex-shrink: 0; font-size: 12px; font-weight: 700; color: #0071dc; }
@media (max-width: 360px) { .store-btn { font-size: 12px; padding: 10px 6px; gap: 5px; } }
/* Meal planner. */
.meal-weekbar { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 12px; }
.meal-range { font-weight: 800; font-size: 15px; text-align: center; flex: 1; }
.meal-day { background: var(--surface); border: 1.5px solid var(--border); border-radius: 14px; padding: 10px 12px; margin-bottom: 10px; }
.meal-day.is-today { border-color: var(--orange); box-shadow: 0 0 0 1px var(--orange); }
.meal-day-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 4px; }
.meal-day-name { font-weight: 700; font-size: 14px; }
.meal-today-tag { font-size: 10px; font-weight: 800; text-transform: uppercase; letter-spacing: .04em; color: #fff; background: var(--orange); border-radius: 20px; padding: 1px 7px; margin-left: 6px; vertical-align: middle; }
.meal-add { flex-shrink: 0; width: 28px; height: 28px; border-radius: 50%; border: 1.5px solid var(--border); background: var(--bg); color: var(--muted); font-size: 18px; line-height: 1; cursor: pointer; }
.meal-add:hover { border-color: var(--orange); color: var(--orange); }
.meal-row { display: flex; align-items: center; gap: 9px; padding: 8px 0; border-top: 1px solid var(--border); cursor: pointer; }
.meal-slot-ic { font-size: 18px; flex-shrink: 0; }
.meal-title { flex: 1; min-width: 0; font-weight: 600; font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.meal-ing-count { flex-shrink: 0; font-size: 11px; color: var(--muted); background: var(--bg); border-radius: 20px; padding: 1px 8px; }
.meal-empty { font-size: 13px; color: var(--muted); padding: 4px 0; }
/* Ingredient editor rows (meal modal). */
.ing-label { display: flex; justify-content: space-between; align-items: center; }
.ing-row { display: flex; gap: 6px; margin-bottom: 6px; align-items: center; }
/* Proportional + shrinkable so the 4-up row never overflows on a phone. */
.ing-row .ing-name { flex: 2 1 0; min-width: 0; }
.ing-row .ing-qty { flex: 0 0 3.1rem; min-width: 0; }
.ing-row .ing-cat { flex: 1.3 1 0; min-width: 0; font-size: 12px; }
.ing-row input, .ing-row select { padding: 9px 9px; font-size: 13px; }
.ing-row .ing-del { flex: 0 0 auto; border: 0; background: transparent; color: var(--muted); font-size: 15px; cursor: pointer; padding: 4px; }
.ing-row .ing-del:hover { color: var(--red); }
/* Reward unlocks (screen time etc.). */
.reward-card.reward-unlock { border-color: var(--teal); background: linear-gradient(160deg, #F0F9F8, var(--surface)); }
.reward-card.reward-unlock .reward-cost { color: var(--teal); font-weight: 700; }
.unlock-row { display: flex; align-items: center; gap: 10px; padding: 10px 0; border-bottom: 1px solid var(--border); }
.unlock-row:last-child { border-bottom: 0; }
.unlock-ic { font-size: 20px; flex-shrink: 0; }
.unlock-name { flex: 1; min-width: 0; font-weight: 600; font-size: 14px; display: flex; flex-direction: column; }
.unlock-cond { font-weight: 400; font-size: 11px; color: var(--muted); }
.unlock-state { flex-shrink: 0; font-size: 12px; font-weight: 700; color: var(--muted); }
.unlock-row.on { }
.unlock-row.on .unlock-state { color: var(--green); }
/* Chore library picker. */
.lib-cat { font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); margin: 14px 2px 6px; }
.lib-cat:first-child { margin-top: 0; }
.lib-item { display: flex; align-items: center; gap: 10px; width: 100%; text-align: left; background: var(--surface); border: 1.5px solid var(--border); border-radius: 12px; padding: 10px 12px; margin-bottom: 6px; cursor: pointer; }
.lib-item:hover { border-color: var(--orange); }
.lib-ic { font-size: 20px; flex-shrink: 0; }
.lib-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.lib-name { font-weight: 600; font-size: 14px; }
.lib-meta { font-size: 12px; color: var(--muted); margin-top: 1px; }
.lib-use { flex-shrink: 0; font-size: 12px; font-weight: 700; color: var(--orange); background: var(--bg); border-radius: 20px; padding: 4px 12px; }
/* Rooms manager. */
.room-row { display: flex; align-items: center; gap: 10px; padding: 10px 0; border-bottom: 1px solid var(--border); }
.room-row:last-child { border-bottom: 0; }
.room-icon { font-size: 22px; width: 34px; text-align: center; flex-shrink: 0; }
.room-info { flex: 1; min-width: 0; }
.room-name { font-weight: 700; font-size: 14px; }
.room-meta { font-size: 12px; color: var(--muted); margin-top: 1px; }
/* Split-generate rows: step label + assignee + day. */
.rg-split-row { display: grid; grid-template-columns: 1fr; gap: 6px; padding: 10px 0; border-bottom: 1px solid var(--border); }
.rg-split-step { font-weight: 600; font-size: 13px; }
.rg-split-row select { width: 100%; }
@media (min-width: 420px) {
  .rg-split-row { grid-template-columns: 1.3fr 1fr 1fr; align-items: center; }
  .rg-split-step { grid-column: 1 / 2; }
}
.chip { background: var(--bg); border-radius: 20px; padding: 2px 8px; font-size: 11px; font-weight: 600; color: var(--muted); }
.chip-green { background: #E8F5E9; color: var(--green); }
.chip-red { background: #FFEBEE; color: var(--red); }
.chip-yellow { background: #FFFDE7; color: #F59E0B; }
.chip-orange { background: #FFF3EE; color: var(--orange); }

/* ── Member chips ── */
.member-list { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 16px; }
.member-chip { display: flex; align-items: center; gap: 6px; padding: 6px 12px; border-radius: 20px; background: var(--bg); border: 1.5px solid var(--border); font-size: 13px; font-weight: 600; cursor: pointer; transition: all 0.15s; }
.member-chip.selected { background: var(--orange); color: white; border-color: var(--orange); }
.member-chip .avatar-xs { width: 22px; height: 22px; border-radius: 50%; background: var(--teal); color: white; display: flex; align-items: center; justify-content: center; font-size: 10px; font-weight: 700; }

/* ── Points / Reward cards ── */
.points-bar { background: var(--bg); border-radius: 20px; height: 10px; overflow: hidden; margin: 8px 0; }
.points-bar-fill { height: 100%; border-radius: 20px; background: linear-gradient(90deg, var(--orange), var(--yellow)); transition: width 0.5s; }
.reward-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.reward-card { background: var(--surface); border-radius: var(--radius); padding: 16px; text-align: center; box-shadow: var(--shadow); border: 1.5px solid var(--border); }
.reward-card .reward-icon { font-size: 32px; margin-bottom: 6px; }
.reward-card .reward-name { font-weight: 700; font-size: 13px; }
.reward-card .reward-cost { color: var(--orange); font-weight: 700; font-size: 12px; margin: 4px 0; }
.reward-card .btn { font-size: 12px; padding: 7px 12px; }

/* ── Family member view ── */
.member-row { display: flex; align-items: center; gap: 12px; padding: 12px 0; border-bottom: 1px solid var(--border); }
.member-row:last-child { border-bottom: none; }
.avatar-md { width: 44px; height: 44px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 16px; color: white; flex-shrink: 0; }
.avatar-lg { width: 68px; height: 68px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 800; font-size: 24px; color: white; flex-shrink: 0; }
.member-info { flex: 1; min-width: 0; }
.member-name { font-weight: 700; }
.member-role { font-size: 12px; color: var(--muted); }
.member-points { font-weight: 700; color: var(--orange); font-size: 14px; }
.progress-track { background: var(--bg); border-radius: 20px; height: 10px; overflow: hidden; margin: -8px 0 16px; }
.progress-fill { background: linear-gradient(90deg, var(--teal), var(--orange)); height: 100%; border-radius: 20px; transition: width 0.3s; }

/* ── Approval queue ── */
.approval-row { display: flex; align-items: center; gap: 12px; padding: 12px 0; border-bottom: 1px solid var(--border); }
.approval-row:last-child { border-bottom: none; }
.approval-actions { display: flex; gap: 8px; flex-shrink: 0; }

/* ── Modal ── */
.modal-backdrop { position: fixed; inset: 0; background: rgba(0,0,0,0.45); z-index: 200; display: flex; align-items: flex-end; justify-content: center; opacity: 0; pointer-events: none; transition: opacity 0.2s; }
.modal-backdrop.open { opacity: 1; pointer-events: all; }
.modal { background: var(--surface); border-radius: 24px 24px 0 0; padding: 24px 24px max(24px,env(safe-area-inset-bottom)); width: 100%; max-width: 600px; max-height: 90dvh; overflow-y: auto; overflow-x: hidden; overscroll-behavior: contain; transform: translateY(40px); transition: transform 0.25s; }
/* Keep every control inside the sheet on narrow phones. */
.modal input, .modal select, .modal textarea { max-width: 100%; box-sizing: border-box; }
.modal .form-row { min-width: 0; }
.modal .form-row > .form-group { min-width: 0; }
/* Smart-add proposals: let long titles wrap and controls shrink instead of overflowing the viewport. */
#at-smart-results .task-item { flex-wrap: wrap; }
#at-smart-results .task-name { white-space: normal; overflow: visible; text-overflow: clip; }
#at-smart-results .form-row { grid-template-columns: minmax(0,1fr) auto; }
#at-smart-results select, #at-smart-results input { min-width: 0; }
.modal-backdrop.open .modal { transform: translateY(0); }
.modal-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px; }
.modal-title { font-size: 18px; font-weight: 800; }
.modal-close { background: var(--bg); border: none; border-radius: 50%; width: 32px; height: 32px; font-size: 18px; display: flex; align-items: center; justify-content: center; cursor: pointer; }

/* ── Toast ── */
#toast { position: fixed; bottom: 80px; left: 50%; transform: translateX(-50%) translateY(20px); background: var(--text); color: white; padding: 10px 20px; border-radius: 20px; font-size: 13px; font-weight: 600; opacity: 0; transition: all 0.3s; z-index: 300; white-space: nowrap; pointer-events: none; }
#toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }

/* ── Streaks ── */
.streak-box { display: flex; align-items: center; gap: 10px; background: linear-gradient(135deg, #FFF5F0, #FFF9E6); border-radius: var(--radius); padding: 14px 16px; margin-bottom: 10px; }
.streak-flame { font-size: 32px; }
.streak-info .streak-num { font-size: 22px; font-weight: 900; color: var(--orange); }
.streak-info .streak-lbl { font-size: 12px; color: var(--muted); }

/* ── Loading ── */
.spinner { width: 36px; height: 36px; border: 3px solid var(--border); border-top-color: var(--orange); border-radius: 50%; animation: spin 0.7s linear infinite; margin: 40px auto; }
@keyframes spin { to { transform: rotate(360deg); } }

/* ── Empty state ── */
.empty { text-align: center; padding: 40px 20px; color: var(--muted); }
.empty .empty-icon { font-size: 48px; margin-bottom: 12px; }
.empty h3 { font-size: 16px; font-weight: 700; color: var(--text); margin-bottom: 6px; }

/* ── Tabs ── */
.tabs { display: flex; gap: 4px; background: var(--bg); padding: 4px; border-radius: 12px; margin-bottom: 16px; }
.tab { flex: 1; padding: 8px; text-align: center; border-radius: 9px; font-size: 13px; font-weight: 600; cursor: pointer; color: var(--muted); border: none; background: none; transition: all 0.15s; }
.tab.active { background: var(--surface); color: var(--text); box-shadow: var(--shadow); }

/* ── Responsive ── */
@media (max-width: 400px) {
  .form-row { grid-template-columns: 1fr; }
  .reward-grid { grid-template-columns: 1fr; }
  .stats-row { grid-template-columns: repeat(3, 1fr); }
}

/* ════════════════════════ GuudHome brand lockup ════════════════════════ */
.brand-lockup { display: flex; align-items: center; gap: 8px; }
.brand-lockup-mark { height: 30px; width: 30px; border-radius: 8px; display: block; box-shadow: 0 1px 3px rgba(0,0,0,0.12); }
.brand-lockup-name { font-size: 20px; font-weight: 800; letter-spacing: -0.4px; color: var(--text); }
.brand-lockup-name b { color: var(--orange); font-weight: 800; }

/* A little rooftop over the H of "Home" so the wordmark reads as a house.
   Everything is in em so it scales with whichever lockup it sits in (topbar,
   landing nav, sign-in). It's drawn inside the H's own line box — the cap of a
   capital letter sits ~0.23em below the top of the box — so nothing clips and
   the letter keeps its normal width in the flow. */
.brand-roof { position: relative; display: inline-block; }
.brand-roof::before {
  content: "";
  position: absolute;
  top: -0.01em; left: 50%;
  width: 1.15em; height: 0.21em;
  transform: translateX(-50%);
  background: currentColor;
  clip-path: polygon(50% 0, 100% 100%, 0 100%);
  pointer-events: none;
}

/* The lockup in an in-app topbar is the way back to the Home screen. */
.brand-home { cursor: pointer; user-select: none; }
.brand-home:focus-visible { outline: 2px solid var(--orange); outline-offset: 3px; border-radius: 8px; }

.auth-logo-mark { height: 60px; width: 60px; border-radius: 15px; display: block; margin: 0 auto 10px; box-shadow: var(--shadow); }
.auth-brand-name { font-size: 26px; font-weight: 900; letter-spacing: -0.5px; color: var(--text); }
.auth-brand-name b { color: var(--orange); }

/* SVG icons in nav / icon buttons */
.nav-item .nav-ic { width: 24px; height: 24px; display: block; }
.btn-icon svg { width: 20px; height: 20px; display: block; }
.btn-icon { color: var(--text); }
.btn-ghost { background: transparent; color: var(--text); border: 1.5px solid var(--border); }
.btn-ghost:hover { background: var(--bg); text-decoration: none; }
.btn-lg { padding: 14px 26px; font-size: 16px; border-radius: 12px; }

/* ════════════════════════ Funnel / Landing page ════════════════════════ */
/* The landing view scrolls normally — it is #view-home, kept apart from the
   centered sign-in card at #view-login. */
#view-home { background: var(--bg); justify-content: flex-start; align-items: stretch; }
.funnel { width: 100%; display: flex; flex-direction: column; }
.funnel section { scroll-margin-top: 76px; }

/* Sticky marketing nav */
.lp-nav { position: sticky; top: 0; z-index: 50; display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 12px 24px; background: rgba(255,255,255,0.86); backdrop-filter: saturate(180%) blur(12px); border-bottom: 1px solid var(--border); }
.lp-brand { display: flex; align-items: center; gap: 9px; text-decoration: none; }
.lp-brand:hover { text-decoration: none; }
.lp-brand-mark { height: 34px; width: 34px; border-radius: 9px; box-shadow: 0 1px 4px rgba(0,0,0,0.14); }
.lp-brand-name { font-size: 21px; font-weight: 800; letter-spacing: -0.4px; color: var(--text); }
.lp-brand-name b { color: var(--orange); }
.lp-nav-links { display: flex; gap: 26px; }
.lp-nav-links a { color: var(--muted); font-weight: 600; font-size: 14px; }
.lp-nav-links a:hover { color: var(--text); text-decoration: none; }
.lp-nav-cta { display: flex; align-items: center; gap: 8px; }

/* Hero */
.lp-hero { display: flex; align-items: center; gap: 48px; max-width: 1140px; width: 100%; margin: 0 auto; padding: 64px 32px 48px; }
.lp-hero-copy { flex: 1.05; }
.lp-eyebrow { display: inline-block; background: #FFF3EE; color: var(--orange); font-weight: 700; font-size: 13px; padding: 6px 12px; border-radius: 20px; margin-bottom: 18px; }
.lp-hero-copy h1 { font-size: 46px; line-height: 1.08; font-weight: 900; letter-spacing: -1px; color: var(--text); }
.lp-hero-copy > p { font-size: 18px; color: var(--muted); line-height: 1.55; margin-top: 18px; max-width: 520px; }
.lp-hero-actions { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 28px; }
.lp-hero-trust { display: flex; align-items: center; gap: 10px; margin-top: 22px; font-size: 13px; color: var(--muted); }
.lp-stars { color: #F5A623; letter-spacing: 2px; font-size: 15px; }
.lp-hero-art { flex: 1; }
.lp-hero-art img { width: 100%; height: auto; border-radius: 22px; box-shadow: var(--shadow-lg); display: block; }

/* Trust strip */
.lp-trust { display: flex; flex-wrap: wrap; justify-content: center; gap: 16px 48px; padding: 22px 24px; background: var(--surface); border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); }
.lp-trust-item { text-align: center; }
.lp-trust-item b { display: block; font-size: 20px; font-weight: 900; color: var(--text); }
.lp-trust-item span { font-size: 12.5px; color: var(--muted); }

/* Generic section */
.lp-section { max-width: 1100px; width: 100%; margin: 0 auto; padding: 72px 32px; }
.lp-section-head { text-align: center; max-width: 620px; margin: 0 auto 44px; }
.lp-section-head h2 { font-size: 32px; font-weight: 900; letter-spacing: -0.6px; }
.lp-section-head p { color: var(--muted); font-size: 17px; margin-top: 10px; }

/* Features */
.lp-features { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; }
.lp-feature { background: var(--surface); border: 1px solid var(--border); border-radius: 18px; padding: 26px 20px; box-shadow: var(--shadow); }
.lp-feat-ic { width: 52px; height: 52px; border-radius: 14px; display: flex; align-items: center; justify-content: center; margin-bottom: 16px; }
.lp-feat-ic svg { width: 26px; height: 26px; color: #fff; }
.ic-orange { background: linear-gradient(135deg, #FF8A5B, #FF6B35); }
.ic-teal { background: linear-gradient(135deg, #47D6C8, #2EC4B6); }
.ic-purple { background: linear-gradient(135deg, #B07BEE, #9B5DE5); }
.ic-green { background: linear-gradient(135deg, #6FCF7B, #4CAF50); }
.lp-feature h3 { font-size: 17px; font-weight: 800; letter-spacing: -0.2px; }
.lp-feature p { color: var(--muted); font-size: 14px; line-height: 1.55; margin-top: 8px; }

/* How it works */
.lp-how { background: linear-gradient(180deg, #FFF9F6, var(--bg)); max-width: none; }
.lp-steps { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; max-width: 1000px; margin: 0 auto; }
.lp-step { text-align: center; padding: 12px; }
.lp-step-num { width: 46px; height: 46px; border-radius: 50%; background: var(--orange); color: #fff; font-weight: 900; font-size: 20px; display: flex; align-items: center; justify-content: center; margin: 0 auto 16px; box-shadow: 0 4px 12px rgba(255,107,53,0.35); }
.lp-step h3 { font-size: 18px; font-weight: 800; }
.lp-step p { color: var(--muted); font-size: 14.5px; line-height: 1.55; margin-top: 8px; }

/* Deep-dive alternating blocks */
.lp-dives { display: flex; flex-direction: column; gap: 56px; }
.lp-dive { display: flex; align-items: center; gap: 44px; }
.lp-dive.reverse { flex-direction: row-reverse; }
.lp-dive img { width: 42%; max-width: 380px; border-radius: 20px; box-shadow: var(--shadow); display: block; }
.lp-dive-copy { flex: 1; }
.lp-dive-copy h3 { font-size: 26px; font-weight: 900; letter-spacing: -0.4px; }
.lp-dive-copy p { color: var(--muted); font-size: 16px; line-height: 1.6; margin-top: 12px; }

/* Testimonials */
.lp-testimonials { background: var(--surface); max-width: none; border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); }
.lp-quotes { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; max-width: 1080px; margin: 0 auto; }
.lp-quote { background: var(--bg); border: 1px solid var(--border); border-radius: 16px; padding: 24px; }
.lp-quote blockquote { font-size: 15px; line-height: 1.6; color: var(--text); margin: 10px 0; }
.lp-quote figcaption { font-size: 13px; color: var(--muted); font-weight: 600; }

/* Pricing */
.lp-plans { display: grid; grid-template-columns: repeat(2, minmax(0, 340px)); gap: 22px; justify-content: center; }
.lp-plan { position: relative; background: var(--surface); border: 1.5px solid var(--border); border-radius: 20px; padding: 30px 26px; box-shadow: var(--shadow); }
.lp-plan.featured { border-color: var(--orange); box-shadow: 0 10px 34px rgba(255,107,53,0.18); }
.lp-plan-badge { position: absolute; top: -12px; left: 50%; transform: translateX(-50%); background: var(--orange); color: #fff; font-size: 11px; font-weight: 800; letter-spacing: 0.3px; padding: 5px 14px; border-radius: 20px; text-transform: uppercase; }
.lp-plan-name { font-size: 15px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.6px; color: var(--muted); }
.lp-plan-price { margin: 10px 0 4px; }
.lp-plan-price .amt { font-size: 40px; font-weight: 900; letter-spacing: -1px; }
.lp-plan-price .per { color: var(--muted); font-size: 15px; font-weight: 600; }
.lp-plan-tag { color: var(--muted); font-size: 14px; margin-bottom: 18px; }
.lp-plan-list { list-style: none; display: flex; flex-direction: column; gap: 11px; margin-bottom: 22px; }
.lp-plan-list li { position: relative; padding-left: 26px; font-size: 14.5px; color: var(--text); }
.lp-plan-list li::before { content: "✓"; position: absolute; left: 0; top: -1px; color: var(--green); font-weight: 900; }
.lp-pricing-note { text-align: center; color: var(--muted); font-size: 12.5px; margin-top: 20px; }

/* FAQ */
.lp-faq-list { max-width: 720px; margin: 0 auto; display: flex; flex-direction: column; gap: 12px; }
.lp-faq-item { background: var(--surface); border: 1px solid var(--border); border-radius: 14px; padding: 4px 20px; }
.lp-faq-item summary { cursor: pointer; font-weight: 700; font-size: 15.5px; padding: 16px 0; list-style: none; display: flex; justify-content: space-between; align-items: center; }
.lp-faq-item summary::-webkit-details-marker { display: none; }
.lp-faq-item summary::after { content: "+"; color: var(--orange); font-size: 22px; font-weight: 400; }
.lp-faq-item[open] summary::after { content: "–"; }
.lp-faq-item p { color: var(--muted); font-size: 14.5px; line-height: 1.6; padding: 0 0 16px; }

/* Final CTA */
.lp-cta-final { text-align: center; padding: 76px 32px; background: linear-gradient(135deg, var(--orange), #FF8A5B); color: #fff; }
.lp-cta-final h2 { font-size: 34px; font-weight: 900; letter-spacing: -0.6px; }
.lp-cta-final p { font-size: 17px; margin: 12px 0 26px; opacity: 0.95; }
.lp-cta-final .btn-primary { background: #fff; color: var(--orange); }
.lp-cta-final .btn-primary:hover { background: #fff5ef; }

/* Footer */
.lp-footer { text-align: center; padding: 46px 24px 40px; background: var(--text); color: #cfcac3; }
.lp-foot-brand { display: inline-flex; align-items: center; gap: 9px; }
.lp-foot-brand .brand-lockup-mark, .lp-foot-brand .lp-brand-mark { height: 30px; width: 30px; border-radius: 8px; }
.lp-footer .lp-brand-name { color: #fff; }
.lp-footer .lp-brand-name b { color: var(--orange); }
.lp-foot-tag { font-size: 14px; margin-top: 8px; }
.lp-foot-links { display: flex; justify-content: center; gap: 22px; margin: 18px 0; }
.lp-foot-links a { color: #cfcac3; font-size: 14px; }
.lp-foot-links a:hover { color: #fff; text-decoration: none; }
.lp-foot-legal { font-size: 12.5px; color: #9a948c; }
.lp-foot-legal a { color: #cfcac3; }

/* ── Funnel responsive ── */
@media (max-width: 900px) {
  .lp-nav-links { display: none; }
  .lp-hero { flex-direction: column; text-align: center; gap: 28px; padding: 40px 24px 32px; }
  .lp-hero-copy h1 { font-size: 34px; }
  .lp-hero-copy > p { margin-left: auto; margin-right: auto; }
  .lp-hero-actions, .lp-hero-trust { justify-content: center; }
  .lp-features { grid-template-columns: repeat(2, 1fr); }
  .lp-steps { grid-template-columns: 1fr; gap: 20px; }
  .lp-dive, .lp-dive.reverse { flex-direction: column; text-align: center; gap: 20px; }
  .lp-dive img { width: 100%; max-width: 420px; }
  .lp-quotes { grid-template-columns: 1fr; }
  .lp-plans { grid-template-columns: 1fr; }
  .lp-section { padding: 52px 22px; }
  .lp-section-head h2 { font-size: 26px; }
}
@media (max-width: 520px) {
  .lp-features { grid-template-columns: 1fr; }
  .lp-hero-copy h1 { font-size: 29px; }
  .lp-nav { padding: 10px 16px; }
  .lp-nav-cta .btn { padding: 8px 12px; }
}

/* ── Inline UI icons (section headers, empty states) ── */
.ui-ic { width: 1.15em; height: 1.15em; flex: 0 0 auto; vertical-align: -0.16em; }
.section-title .ui-ic, .card-title .ui-ic { color: var(--orange); }
.empty-svg { width: 46px; height: 46px; color: var(--muted); display: block; margin: 0 auto; }
.empty .empty-icon { line-height: 1; }
.streak-flame { display: flex; align-items: center; }
.streak-flame .ui-ic { width: 30px; height: 30px; color: var(--orange); }
.member-edit-hint .ui-ic { width: 16px; height: 16px; vertical-align: -0.2em; }
.tab .ui-ic { width: 15px; height: 15px; vertical-align: -0.15em; }

/* ── Icon library (pickable icons for tasks, categories, rewards) ── */
/* Sizes are shared by the library's SVGs and by legacy emoji values, so a
   family that saved an emoji before the library shipped still lines up. */
.icon-md { width: 22px; height: 22px; flex: 0 0 auto; }
.icon-lg { width: 28px; height: 28px; flex: 0 0 auto; }
.icon-xl { width: 36px; height: 36px; flex: 0 0 auto; }
.icon-pick { width: 24px; height: 24px; }
span.icon-emoji { display: inline-flex; align-items: center; justify-content: center; line-height: 1; }
span.icon-md { font-size: 18px; }
span.icon-lg { font-size: 24px; }
span.icon-xl { font-size: 32px; }
span.icon-pick { font-size: 20px; }

/* The icon on a task row — tinted with its category colour when it has one. */
.task-ic { display: flex; align-items: center; justify-content: center; color: var(--orange); flex-shrink: 0; }
.task-item.done .task-ic { color: var(--muted); }

/* Picker: a trigger showing the current icon, opening a searchable grid. */
.icon-picker { position: relative; }
.ip-trigger { display: flex; align-items: center; gap: 10px; width: 100%; padding: 8px 12px; background: var(--surface); border: 1.5px solid var(--border); border-radius: 8px; color: var(--text); text-align: left; }
.ip-trigger:hover { border-color: var(--orange); }
.ip-current { display: flex; align-items: center; justify-content: center; width: 34px; height: 34px; border-radius: 8px; background: var(--bg); color: var(--orange); flex-shrink: 0; }
.ip-label { font-size: 13px; color: var(--muted); font-weight: 600; }
.ip-panel { margin-top: 8px; padding: 10px; border: 1.5px solid var(--border); border-radius: 10px; background: var(--surface); box-shadow: var(--shadow); }
.ip-search { margin-bottom: 8px; }
.ip-grid { max-height: 210px; overflow-y: auto; }
.ip-group-head { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px; color: var(--muted); margin: 6px 0 4px; }
.ip-group-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(44px, 1fr)); gap: 6px; }
.ip-opt { display: flex; align-items: center; justify-content: center; height: 42px; border-radius: 10px; background: var(--bg); color: var(--text); border: 1.5px solid transparent; }
.ip-opt:hover { border-color: var(--orange); }
.ip-opt.selected { border-color: var(--orange); background: rgba(255,107,53,0.12); color: var(--orange); }
.ip-clear { margin-top: 8px; }

/* Reward cards / unlock rows now carry library icons rather than an emoji. */
.reward-card .reward-icon { display: flex; align-items: center; justify-content: center; color: var(--orange); }
.reward-card.reward-unlock .reward-icon { color: var(--purple); }
.unlock-ic { display: inline-flex; align-items: center; color: var(--purple); }

/* ── Chore categories (Family view) ── */
.cat-row { display: flex; align-items: center; gap: 12px; padding: 10px 0; border-top: 1px solid var(--border); }
.cat-row:first-child { border-top: none; }
.cat-icon { display: flex; align-items: center; justify-content: center; width: 34px; flex-shrink: 0; color: var(--orange); }
.cat-info { flex: 1; min-width: 0; }
.cat-name { font-weight: 700; font-size: 15px; }
.cat-meta { font-size: 12px; color: var(--muted); }
.cat-swatches { display: flex; flex-wrap: wrap; gap: 8px; }
.cat-swatch { width: 30px; height: 30px; border-radius: 50%; border: 3px solid transparent; box-shadow: inset 0 0 0 1px rgba(0,0,0,0.06); }
.cat-swatch.selected { border-color: var(--text); }

/* Category filter chips above the task board (colours come from the category). */
.cat-chip .cat-chip-ic { display: flex; align-items: center; color: var(--orange); }
.cat-chip.selected .cat-chip-ic { color: inherit; }
.cat-chip .ui-ic { width: 16px; height: 16px; }
/* The category label on a task row — tinted inline, muted grey when it isn't. */
.task-meta .cat-tag { display: inline-flex; align-items: center; }
.task-item.done .task-meta .cat-tag { opacity: 0.6; }

/* "Goal: <name>" on a task that feeds a goal — a button, since tapping it opens
   that goal's detail rather than expanding the task. */
.task-meta .goal-tag { display: inline-flex; align-items: center; gap: 4px; border: 1px solid transparent;
  color: var(--purple); background: rgba(155, 93, 229, 0.12); font: inherit; font-size: 11px; font-weight: 600;
  cursor: pointer; max-width: 100%; }
.task-meta .goal-tag .ui-ic { width: 13px; height: 13px; flex-shrink: 0; }
.task-meta .goal-tag .icon-emoji { font-size: 12px; line-height: 1; }
.task-meta .goal-tag:hover { border-color: var(--purple); }
.task-item.done .task-meta .goal-tag { opacity: 0.6; }
/* The goal's daily habit, pinned above the rest of the day. The status accents
   still win — overdue/expanded say more in the moment than "this is a goal". */
.task-item.goal-pinned { border-left-color: var(--purple); }
.task-item.goal-pinned.done { border-left-color: var(--green); }
.task-item.goal-pinned.overdue { border-left-color: var(--red); }
.task-item.goal-pinned.pending-approval { border-left-color: var(--yellow); }
.task-item.goal-pinned.expanded { border-left-color: var(--teal); }

/* Re-file a task from its expanded detail panel. */
.task-cat-edit { display: flex; align-items: center; gap: 10px; margin-top: 10px; }
.task-cat-edit label { font-size: 12px; font-weight: 700; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; }
.task-cat-edit select { flex: 1; }

/* ── Kid Requests ("Mom, Mom, Mom" inbox) ── */
.kidreq-card { padding: 16px; }
.kidreq-head { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.kidreq-text { font-style: italic; color: var(--text); margin: 0 0 10px; line-height: 1.4; }
.kidreq-meta { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; }
.kidreq-meta .chip { display: inline-flex; align-items: center; gap: 4px; }
.kidreq-meta .chip .ui-ic { width: 13px; height: 13px; }
.kidreq-converted { font-size: 13px; font-weight: 600; color: var(--green); display: flex; align-items: center; gap: 6px; margin: 0 0 4px; }
.kidreq-converted .ui-ic { width: 15px; height: 15px; }
.kidreq-actions { display: flex; flex-wrap: wrap; gap: 8px; }
.kidreq-daypick { margin-top: 10px; padding: 12px; border: 1px solid var(--border); border-radius: 12px; background: var(--surface-2, var(--bg)); display: flex; flex-direction: column; gap: 10px; }
.kidreq-daypick-title { font-size: 13px; font-weight: 700; color: var(--text); }
.kidreq-daypick-quick { display: flex; flex-wrap: wrap; gap: 8px; }
.kidreq-daypick-date { display: flex; align-items: center; gap: 8px; font-size: 12px; font-weight: 600; color: var(--muted); }
.kidreq-daypick-date .todo-day-input { width: auto; padding: 5px 10px; font-size: 12px; border-radius: 8px; }
.btn.is-soon { opacity: 0.65; }
#kidreq-badge.badge { margin-left: 6px; }

/* ── Homepage "Mom, Mom, Mom" panel (grouped by kid) ── */
.mom-group { padding: 12px 0; border-top: 1px solid var(--border); }
.card-title + .mom-group { padding-top: 0; border-top: none; }
.mom-group-head { display: flex; align-items: center; gap: 12px; margin-bottom: 8px; }
.mom-group-list { display: flex; flex-direction: column; gap: 8px; margin-left: 48px; }
.mom-request-row { display: flex; align-items: baseline; gap: 8px; cursor: pointer; }
.mom-request-row .kidreq-text { margin: 0; font-size: 14px; }
.mom-request-row .chip { flex-shrink: 0; }

/* ── Family Calendar ── */
.cal-toolbar { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; }
.cal-nav { display: flex; align-items: center; gap: 2px; }
.cal-nav .btn-icon { font-size: 20px; font-weight: 700; }
.cal-range { flex: 1; text-align: center; font-weight: 800; font-size: 14px; }
.cal-toolbar-actions { display: flex; align-items: center; gap: 6px; flex-shrink: 0; }
.cal-add-wrap { position: relative; }
.cal-add-menu { position: absolute; top: calc(100% + 6px); right: 0; z-index: 60; min-width: 240px; padding: 6px; background: var(--surface); border: 1px solid var(--border); border-radius: 12px; box-shadow: var(--shadow-lg); display: flex; flex-direction: column; gap: 2px; }
.cal-add-menu-item { display: flex; align-items: center; gap: 10px; width: 100%; padding: 10px 12px; background: none; border: 0; border-radius: 8px; font-size: 14px; font-weight: 600; color: var(--text); text-align: left; cursor: pointer; }
.cal-add-menu-item:hover { background: var(--bg); }
.cal-add-menu-ic { font-size: 16px; flex-shrink: 0; }

/* Work-schedule screenshot import */
.import-hint { font-size: 14px; line-height: 1.5; color: var(--muted); margin-bottom: 16px; }
.import-drop { display: block; border: 2px dashed var(--border); border-radius: 14px; padding: 24px 16px; text-align: center; cursor: pointer; background: var(--bg); transition: border-color 0.15s, background 0.15s; }
.import-drop:hover { border-color: var(--orange); background: var(--surface); }
.import-drop-icon { font-size: 40px; line-height: 1; }
.import-drop-cta { font-weight: 700; font-size: 14px; margin-top: 10px; color: var(--text); }
.import-drop-sub { font-size: 12px; color: var(--muted); margin-top: 4px; }
.import-preview { max-width: 100%; max-height: 240px; border-radius: 10px; border: 1px solid var(--border); }
.import-file-name { font-size: 13px; font-weight: 600; margin-top: 10px; word-break: break-all; }
.import-error { background: rgba(229,57,53,0.08); border: 1px solid rgba(229,57,53,0.25); color: var(--red); font-size: 13px; line-height: 1.45; border-radius: 10px; padding: 10px 12px; margin: 12px 0; }
.import-shift { border: 1.5px solid var(--border); border-radius: 12px; padding: 10px 12px; margin-bottom: 10px; background: var(--surface); }
.import-shift-top { display: flex; align-items: flex-end; gap: 8px; }
.import-shift-top .import-field-date { flex: 1; margin: 0; }
.import-shift-grid { display: flex; align-items: flex-end; gap: 8px; margin-top: 8px; }
.import-shift-grid .import-field { flex: 1; margin: 0; }
.import-field { display: block; margin: 0; }
.import-field > span { display: block; font-size: 11px; font-weight: 700; color: var(--muted); margin-bottom: 4px; text-transform: uppercase; letter-spacing: 0.02em; }
.import-field input { width: 100%; }
.import-field-label { margin-top: 8px; }
.import-shift-del { flex-shrink: 0; width: 38px; height: 42px; border-radius: 8px; border: 1.5px solid var(--border); background: var(--bg); color: var(--muted); font-size: 15px; cursor: pointer; transition: color 0.15s, border-color 0.15s; }
.import-shift-del:hover { color: var(--red); border-color: var(--red); }
.import-add-shift { margin-bottom: 14px; }
.import-review-actions { display: flex; gap: 10px; }
.import-review-actions .btn { flex: 1; }

.cal-day-card { background: var(--surface); border: 1.5px solid var(--border); border-radius: 14px; padding: 10px 12px; margin-bottom: 10px; }
.cal-day-card.is-today { border-color: var(--orange); box-shadow: 0 0 0 1px var(--orange); }
.cal-day-card-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 4px; }
.cal-day-card-name { font-weight: 700; font-size: 14px; }
.cal-day-add { flex-shrink: 0; width: 28px; height: 28px; border-radius: 50%; border: 1.5px solid var(--border); background: var(--bg); color: var(--muted); font-size: 18px; line-height: 1; cursor: pointer; }
.cal-day-add:hover { border-color: var(--orange); color: var(--orange); }
.cal-day-card-empty { font-size: 13px; color: var(--muted); padding: 4px 0; }

.cal-day-agenda .cal-entry-row:first-child { border-top: none; }
.cal-entry-row { display: flex; align-items: center; gap: 10px; padding: 9px 0; border-top: 1px solid var(--border); cursor: pointer; }
.cal-entry-row.cal-entry-readonly { cursor: default; opacity: 0.75; }
.cal-entry-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--cal-color, var(--muted)); flex-shrink: 0; }
.cal-entry-ic { color: var(--cal-color, var(--orange)); flex-shrink: 0; display: flex; }
.cal-entry-ic .ui-ic { width: 18px; height: 18px; }
.cal-entry-main { flex: 1; min-width: 0; }
.cal-entry-title { font-weight: 600; font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cal-entry-meta { font-size: 12px; color: var(--muted); margin-top: 1px; }
.cal-entry-row .chip { flex-shrink: 0; }

.cal-month-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 3px; }
.cal-month-headcell { text-align: center; font-size: 10px; font-weight: 700; color: var(--muted); padding: 4px 0; text-transform: uppercase; letter-spacing: .03em; }
.cal-month-cell { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; min-height: 64px; padding: 4px; cursor: pointer; display: flex; flex-direction: column; gap: 2px; }
.cal-month-cell:hover { border-color: var(--orange); }
.cal-month-cell.is-outside { background: var(--bg); opacity: 0.55; }
.cal-month-cell.is-today .cal-month-daynum { background: var(--orange); color: #fff; }
.cal-month-daynum { font-size: 11px; font-weight: 700; width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; border-radius: 50%; }
.cal-month-entries { display: flex; flex-direction: column; gap: 2px; overflow: hidden; }
.cal-month-pill { font-size: 10px; font-weight: 600; color: #fff; background: var(--cal-color, var(--muted)); border-radius: 5px; padding: 1px 4px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: pointer; }
.cal-month-pill.cal-entry-readonly { cursor: default; opacity: 0.8; }
.cal-month-more { font-size: 10px; font-weight: 700; color: var(--muted); }

/* Kid-friendly agenda: big tappable rows, no grid to parse. */
.cal-agenda-day { margin-bottom: 20px; }
.cal-agenda-day-head { font-weight: 800; font-size: 15px; margin-bottom: 8px; }
.cal-agenda-entry { display: flex; align-items: center; gap: 12px; background: var(--surface); border: 1.5px solid var(--border); border-radius: 16px; padding: 12px; margin-bottom: 8px; cursor: pointer; }
.cal-agenda-entry.cal-entry-readonly { cursor: default; opacity: 0.8; }
.cal-agenda-ic { flex-shrink: 0; width: 40px; height: 40px; border-radius: 50%; background: var(--cal-color, var(--orange)); color: #fff; display: flex; align-items: center; justify-content: center; }
.cal-agenda-ic .ui-ic { width: 20px; height: 20px; }
.cal-agenda-main { flex: 1; min-width: 0; }
.cal-agenda-title { font-weight: 700; font-size: 15px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cal-agenda-meta { font-size: 13px; color: var(--muted); margin-top: 2px; }
.cal-agenda-entry .chip { flex-shrink: 0; }

/* ── Time-block day view ── */
.day-summary { display: flex; align-items: center; justify-content: space-between; gap: 10px; font-size: 13px; color: var(--muted); margin: 2px 2px 14px; }
.day-summary-who { font-weight: 800; color: var(--text); font-size: 15px; }

/* Hour bands: the time label runs down the left, tasks sit to its right. */
.day-band { display: flex; gap: 12px; padding: 10px 0; border-top: 1px dashed var(--border); }
.day-band-time { flex-shrink: 0; width: 72px; font-size: 12px; font-weight: 800; color: var(--muted); text-transform: uppercase; letter-spacing: .03em; padding-top: 12px; }
.day-band .task-list { flex: 1; min-width: 0; }
.day-band.is-now .day-band-time { color: var(--orange); }
.day-band.is-now { border-top-color: var(--orange); }
.day-block-time { font-weight: 700; color: var(--orange); }

/* Bookends: the routines that open and close the day, visually set apart from
   the hour bands between them. */
.day-bookend { border-radius: 16px; padding: 14px; margin-bottom: 16px; border: 1.5px solid var(--border); }
.day-bookend + .day-band { border-top: none; }
.day-bookend-morning { background: linear-gradient(160deg, #FFF6EC 0%, var(--surface) 70%); border-color: #F7D9B8; }
.day-bookend-evening { background: linear-gradient(160deg, #F2EEFF 0%, var(--surface) 70%); border-color: #DCD2F5; margin-top: 16px; margin-bottom: 0; }
.day-bookend-head { display: flex; align-items: center; gap: 8px; font-size: 14px; font-weight: 800; margin-bottom: 10px; }
.day-bookend-morning .day-bookend-head .ui-ic { color: var(--yellow); }
.day-bookend-evening .day-bookend-head .ui-ic { color: var(--purple); }
.day-bookend-count { margin-left: auto; font-size: 12px; font-weight: 700; color: var(--muted); background: var(--bg); border-radius: 20px; padding: 2px 9px; }

@media (max-width: 400px) {
  .cal-month-cell { min-height: 52px; }
  .cal-range { font-size: 12px; }
  .day-band { flex-direction: column; gap: 4px; }
  .day-band-time { width: auto; padding-top: 0; }
}

/* ── Shopping lists: picker, budget totals, per-store breakdown ── */
.shop-listbar { display: flex; gap: 8px; align-items: center; margin-bottom: 12px; }
.shop-listbar select { flex: 1; min-width: 0; font-weight: 700; }
.shop-list-settings { flex-shrink: 0; border: 1.5px solid var(--border); }
.shop-budget { padding: 16px; margin-bottom: 14px; }
.shop-budget-note { font-size: 12.5px; color: var(--muted); margin-top: 6px; text-align: center; }
.shop-budget-note.warn { color: var(--red); font-weight: 700; }
.shop-budget .points-bar-fill.over { background: linear-gradient(90deg, #E53935, #FF8A5B); }
.shop-list-archive { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--muted); margin-bottom: 14px; }
.shop-list-archive input { width: 17px; height: 17px; accent-color: var(--orange); }
.shop-add .shop-price { flex: 0 0 72px; min-width: 0; text-align: center; }
.shop-price-tag { font-size: 12px; font-weight: 700; color: var(--green); flex-shrink: 0; }
.shop-bystore { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; }
.shop-store-pill { font-size: 12px; color: var(--muted); background: var(--bg); border: 1px solid var(--border); border-radius: 20px; padding: 3px 10px; }
.shop-store-pill b { color: var(--text); }
.dash-budget { font-size: 12.5px; font-weight: 700; color: var(--muted); margin: -4px 0 10px; }
.dash-budget.warn { color: var(--red); }

/* ── Goals ── */
.goal-bar { display: flex; gap: 8px; margin-bottom: 14px; }
.goal-bar select { flex: 1; min-width: 0; }
.goal-bar .btn { flex-shrink: 0; }
.goal-card { display: flex; gap: 12px; width: 100%; text-align: left; background: var(--surface); border: 1.5px solid var(--border); border-radius: var(--radius); padding: 14px 16px; margin-bottom: 10px; cursor: pointer; box-shadow: var(--shadow); }
.goal-card:hover { border-color: var(--orange); }
.goal-card.is-done { opacity: 0.7; }
.goal-ic { flex-shrink: 0; color: var(--orange); display: flex; align-items: flex-start; }
.goal-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.goal-name { font-weight: 700; font-size: 15px; }
.goal-meta { font-size: 12px; color: var(--muted); display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.goal-prog { font-size: 12px; font-weight: 700; color: var(--orange); }
.goal-card .points-bar { margin: 2px 0; display: block; }
.goal-card .points-bar-fill { display: block; }
.goal-smart { border: 1.5px dashed var(--border); border-radius: 14px; padding: 14px 14px 2px; margin-bottom: 16px; }
.goal-smart-head { font-size: 12px; font-weight: 800; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); margin-bottom: 10px; }
.goal-hint { font-size: 12px; color: var(--muted); margin: -8px 0 14px; }
/* The daily habit the goal creates — its own step under the SMART box. */
.goal-daily { border: 1.5px dashed var(--border); border-radius: 14px; padding: 14px; margin-bottom: 16px; background: rgba(247,201,72,0.10); }
.goal-daily > label { display: block; font-size: 13px; font-weight: 700; margin-bottom: 4px; }
.goal-daily input { width: 100%; box-sizing: border-box; }
.goal-draft-note { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; font-size: 12px; font-weight: 600; color: var(--text); background: rgba(46,196,182,0.10); border: 1.5px solid rgba(46,196,182,0.35); border-radius: 12px; padding: 10px 12px; margin-bottom: 14px; }
.goal-draft-note .btn { flex: 0 0 auto; }
.goal-drafts-head { display: flex; align-items: center; gap: 8px; font-size: 12px; font-weight: 800; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); margin: 22px 0 4px; }
.goal-drafts-count { display: inline-flex; align-items: center; justify-content: center; min-width: 20px; height: 20px; padding: 0 6px; border-radius: 10px; background: rgba(247,201,72,0.25); color: var(--text); font-size: 11px; letter-spacing: 0; }
.goal-drafts-hint { font-size: 12px; color: var(--muted); margin: 0 0 10px; }
.draft-row { display: flex; align-items: stretch; gap: 8px; margin-bottom: 10px; }
.draft-row .goal-card { margin-bottom: 0; border-style: dashed; background: var(--surface); }
.draft-row .goal-card:hover { border-color: var(--orange); }
.draft-del { flex: 0 0 auto; width: 40px; border: 1.5px dashed var(--border); border-radius: var(--radius); background: var(--surface); color: var(--muted); font-size: 15px; cursor: pointer; }
.draft-del:hover { color: var(--red, #E53935); border-color: var(--red, #E53935); }
.goal-detail-head { display: flex; gap: 12px; align-items: center; margin-bottom: 12px; }
.goal-detail-owner { font-weight: 700; font-size: 14px; }
.goal-detail-when { font-size: 12px; color: var(--muted); display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-top: 2px; }
.goal-prog-line { font-size: 13px; font-weight: 700; color: var(--orange); margin-bottom: 14px; }
.goal-smart-list { display: flex; flex-direction: column; gap: 8px; margin-bottom: 16px; }
.goal-smart-row { display: flex; gap: 10px; font-size: 13px; line-height: 1.45; }
.goal-smart-row b { flex: 0 0 88px; color: var(--muted); font-size: 12px; text-transform: uppercase; letter-spacing: .03em; padding-top: 1px; }
.goal-checkin { border-top: 1px solid var(--border); padding-top: 14px; margin-bottom: 14px; }
.goal-checkin > label { display: block; font-size: 13px; font-weight: 700; margin-bottom: 8px; }
.goal-checkin-row { display: flex; gap: 8px; margin-bottom: 8px; }
.goal-checkin-row input { flex: 1; min-width: 0; }
.goal-checkin-row .btn { flex-shrink: 0; }
.goal-help { border-top: 1px solid var(--border); padding-top: 14px; margin-bottom: 14px; }
.goal-help > label { display: flex; align-items: center; gap: 6px; font-size: 13px; font-weight: 700; margin-bottom: 4px; }
.goal-help-row { display: flex; flex-wrap: wrap; gap: 8px; }
.goal-help-row input { flex: 1 1 180px; min-width: 0; box-sizing: border-box; }
.goal-help-row .btn { flex: 0 0 auto; box-sizing: border-box; }
/* Achievable = the "what do I need?" checklist, in the form and in the detail
   view (where the Ask-for-help box lives inside it). */
.goal-needs { display: flex; flex-direction: column; gap: 6px; margin-bottom: 10px; }
.goal-need-row { display: flex; align-items: center; gap: 8px; }
.goal-need-row input[type=checkbox].goal-need-check { width: 18px; height: 18px; flex: 0 0 auto; margin: 0; }
.goal-need-text { flex: 1 1 auto; min-width: 0; }
.goal-need-label { flex: 1 1 auto; min-width: 0; font-size: 14px; line-height: 1.35; }
.goal-need-row.is-done .goal-need-label { text-decoration: line-through; color: var(--muted); }
.goal-need-del { flex: 0 0 auto; width: 30px; height: 30px; border: 0; border-radius: 8px; background: none; color: var(--muted); font-size: 20px; line-height: 1; cursor: pointer; }
.goal-need-del:hover { background: var(--border); color: var(--red); }
.goal-need-ask { flex: 0 0 auto; border: 1.5px solid var(--border); background: none; border-radius: 999px; padding: 3px 10px; font-size: 12px; font-weight: 700; color: var(--muted); cursor: pointer; }
.goal-need-ask:hover { border-color: var(--orange); color: var(--orange); }
.goal-need-add { display: flex; gap: 8px; margin-bottom: 14px; }
.goal-need-add input { flex: 1 1 auto; min-width: 0; }
.goal-need-add .btn { flex: 0 0 auto; }
.goal-needs-box { border: 1.5px dashed var(--border); border-radius: 14px; padding: 14px; margin-bottom: 16px; }
.goal-needs-head { display: flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 800; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); margin-bottom: 10px; }
.goal-needs-box .goal-help { margin-bottom: 0; }
.goal-smart .goal-help { margin-bottom: 0; }
.goal-needs-box .goal-help:first-child { border-top: 0; padding-top: 0; }
.goal-log { border-top: 1px solid var(--border); padding-top: 12px; }
.goal-log-head { font-size: 12px; font-weight: 800; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); margin-bottom: 8px; }
.goal-log-row { display: flex; justify-content: space-between; gap: 10px; font-size: 13px; padding: 6px 0; border-bottom: 1px solid var(--border); }
.goal-log-row:last-child { border-bottom: 0; }
.dash-goal-list { display: flex; flex-direction: column; gap: 12px; }
.dash-goal { display: flex; flex-direction: column; gap: 4px; width: 100%; text-align: left; background: none; border: 0; padding: 0; cursor: pointer; }
.dash-goal-top { display: flex; justify-content: space-between; gap: 8px; align-items: baseline; }
.dash-goal-name { font-weight: 700; font-size: 14px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dash-goal-pct { font-size: 12px; font-weight: 800; color: var(--orange); flex-shrink: 0; }
.dash-goal .points-bar { display: block; margin: 0; }
.dash-goal .points-bar-fill { display: block; }
.dash-goal-meta { font-size: 12px; color: var(--muted); display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }

/* Unfinished goals, right in the homepage goal card — same idea as the drafts
   list on the Goals page, squeezed down to one line each. */
.dash-drafts { margin-top: 14px; padding-top: 12px; border-top: 1px dashed var(--border); }
.dash-drafts-head { display: flex; align-items: center; gap: 8px; font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); margin-bottom: 8px; }
.dash-draft-row { display: flex; align-items: stretch; gap: 6px; margin-bottom: 8px; }
.dash-draft-row:last-child { margin-bottom: 0; }
.dash-draft { flex: 1 1 auto; display: flex; align-items: center; gap: 8px; min-width: 0; text-align: left; padding: 8px 10px; border: 1.5px dashed var(--border); border-radius: var(--radius); background: var(--surface); cursor: pointer; }
.dash-draft:hover { border-color: var(--orange); }
.dash-draft-ic { display: flex; align-items: center; color: var(--orange); flex: 0 0 auto; }
.dash-draft-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 700; font-size: 14px; }
.dash-draft-row .draft-del { width: 34px; font-size: 13px; }

/* ── Make a Request (kid dashboard) ── */
.quick-request-card { margin-bottom: 16px; }
.quick-request-title { font-weight: 700; font-size: 15px; margin-bottom: 10px; }
.quick-request-card .dash-req-form { margin-bottom: 0; }
.dash-req-form { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 14px; }
.dash-req-form input { flex: 1 1 180px; min-width: 0; box-sizing: border-box; }
.dash-req-form .btn { flex: 0 0 auto; box-sizing: border-box; }
.dash-req-list { display: flex; flex-direction: column; gap: 10px; }
.dash-req-item { border: 1.5px solid var(--border); border-radius: 10px; padding: 10px 12px; background: var(--surface); }
.dash-req-top { display: flex; justify-content: space-between; align-items: flex-start; gap: 8px; }
.dash-req-text { font-weight: 600; font-size: 14px; min-width: 0; overflow-wrap: anywhere; }
.dash-req-top .chip { flex-shrink: 0; }
.dash-req-when { font-size: 12px; color: var(--muted); margin-top: 3px; }
.dash-req-note { font-size: 13px; color: var(--text); margin: 6px 0 0; font-style: italic; }

/* ── Routine templates ── */
.routine-search { display: flex; gap: 8px; margin-bottom: 14px; }
.routine-search input { flex: 1; min-width: 0; }
.routine-card { display: flex; gap: 12px; width: 100%; text-align: left; background: var(--surface); border: 1.5px solid var(--border); border-radius: var(--radius); padding: 14px 16px; margin-bottom: 10px; cursor: pointer; box-shadow: var(--shadow); }
.routine-card:hover { border-color: var(--orange); }
.routine-ic { flex-shrink: 0; color: var(--teal); display: flex; align-items: flex-start; }
.routine-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.routine-name { font-weight: 700; font-size: 15px; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.routine-meta { font-size: 12px; color: var(--muted); }
.routine-desc { font-size: 13px; color: var(--text); line-height: 1.4; }
.routine-desc-full { font-size: 14px; line-height: 1.5; margin-bottom: 8px; }
.routine-tasks { margin: 12px 0 16px; }
.routine-tasks .task-item { box-shadow: none; border: 1px solid var(--border); }
.routine-apply { border-top: 1px solid var(--border); padding-top: 14px; }
.routine-note { font-size: 12.5px; color: var(--muted); margin-top: 8px; line-height: 1.45; }
.routine-public { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--muted); margin-bottom: 16px; }
.routine-public input { width: 17px; height: 17px; accent-color: var(--orange); }
.routine-picker { max-height: 42vh; overflow-y: auto; border: 1.5px solid var(--border); border-radius: 12px; padding: 4px 10px; }
.routine-pick { display: flex; align-items: center; gap: 10px; padding: 9px 0; border-bottom: 1px solid var(--border); font-size: 14px; cursor: pointer; margin: 0; }
.routine-pick:last-child { border-bottom: 0; }
.routine-pick input { width: 18px; height: 18px; accent-color: var(--green); flex-shrink: 0; }
.routine-pick > span:first-of-type { flex: 1; min-width: 0; }
.routine-pick-meta { font-size: 12px; color: var(--muted); flex-shrink: 0; }

/* ── Text alerts (Settings modal) ──────────────────────────────────────── */
/* One row per SMS switch. The switch shows its own state without being clicked,
   which is what a preference that saves instantly needs — there is no Save
   button to confirm it landed. */
.settings-alerts { display: flex; flex-direction: column; gap: 12px; }
.settings-alert { display: flex; align-items: flex-start; gap: 12px; }
.set-switch { position: relative; flex-shrink: 0; width: 46px; height: 26px; margin-top: 1px; padding: 0; border: 0; border-radius: 999px; background: var(--border); cursor: pointer; transition: background .15s ease; }
.set-switch.is-on { background: var(--orange); }
.set-switch:disabled { opacity: .5; cursor: progress; }
.set-switch:focus-visible { outline: 2px solid var(--orange); outline-offset: 2px; }
.set-switch-knob { position: absolute; top: 3px; left: 3px; width: 20px; height: 20px; border-radius: 50%; background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.25); transition: transform .15s ease; }
.set-switch.is-on .set-switch-knob { transform: translateX(20px); }
.settings-alert-text { min-width: 0; }
.settings-alert-label { font-size: 14px; font-weight: 700; line-height: 1.35; }
.settings-alert-sub { font-size: 12.5px; color: var(--muted); margin-top: 2px; line-height: 1.45; }
.settings-alerts-note { font-size: 13px; color: var(--muted); margin: 0; }
/* The switches are on but there's nowhere to text — worth more than muted grey,
   since nothing else on the screen says the alerts can't arrive. */
.settings-alerts-hint { font-size: 12.5px; line-height: 1.5; margin: 2px 0 0; padding: 10px 12px; border-radius: 10px; border: 1px solid var(--orange); background: rgba(255,107,53,0.06); }

/* ── Membership / Premium (Settings modal) ─────────────────────────────── */
.settings-membership { border: 1.5px solid var(--border); border-radius: 12px; padding: 14px 16px; }
.settings-membership.is-premium { border-color: var(--orange); background: rgba(255,107,53,0.05); }
.pill-premium { display: inline-block; vertical-align: middle; margin-left: 6px; padding: 2px 9px; border-radius: 999px; font-size: 11px; font-weight: 700; letter-spacing: .02em; color: #fff; background: var(--orange); }
.form-status { font-size: 13px; color: var(--muted); min-height: 0; }
.form-status.ok { color: var(--green); }
.form-status.error { color: #C62828; }

/* ══════════════════ Platform Admin portal ══════════════════ */
/* Full-viewport shell: persistent grouped left sidebar (desktop) + off-canvas
   drawer (<1024px). Rendered into #view-admin by app.js. Uses the app's design
   tokens throughout so it reads as one system. */
#view-admin { background: var(--bg); padding: 0; }
.admin { display: flex; align-items: stretch; width: 100%; height: 100vh; overflow: hidden; }

/* Sidebar */
.admin-sidebar { width: 248px; flex-shrink: 0; height: 100vh; background: var(--surface); border-right: 1px solid var(--border); display: flex; flex-direction: column; }
.admin-brand { display: flex; align-items: baseline; gap: 8px; padding: 20px 20px 16px; border-bottom: 1px solid var(--border); }
.admin-brand-logo { font-size: 20px; font-weight: 800; color: var(--orange); letter-spacing: -0.5px; }
.admin-nav { flex: 1; overflow-y: auto; overscroll-behavior: contain; padding: 12px 12px 16px; }
/* The nav's scrollbar belongs to the panel, not to the OS: a thin bar, a thumb
   just off the sidebar's own surface, a transparent track and no arrow buttons.
   Firefox takes scrollbar-width/-color; everything else takes the -webkit- rules,
   so both ship. Hover lifts the thumb's contrast to show it is grabbable. */
/* Both are shipped, but the standard pair is gated to engines that have no
   ::-webkit-scrollbar (i.e. Firefox): where a browser supports both, the standard
   properties win and the -webkit- rules are dropped — which on Chromium hands the
   nav the platform bar complete with arrow buttons. Gating keeps Firefox on
   scrollbar-width/-color and everything else on the pseudo-elements below. */
@supports (scrollbar-width: thin) and (not selector(::-webkit-scrollbar)) {
  .admin-nav { scrollbar-width: thin; scrollbar-color: var(--border) transparent; }
  .admin-nav:hover { scrollbar-color: color-mix(in srgb, var(--muted) 45%, var(--border)) transparent; }
}
.admin-nav::-webkit-scrollbar { width: 8px; }
.admin-nav::-webkit-scrollbar-track { background: transparent; }
.admin-nav::-webkit-scrollbar-thumb { background: var(--border); border-radius: 999px; border: 2px solid transparent; background-clip: content-box; }
.admin-nav::-webkit-scrollbar-thumb:hover { background: color-mix(in srgb, var(--muted) 45%, var(--border)); background-clip: content-box; }
.admin-nav::-webkit-scrollbar-button { display: none; width: 0; height: 0; }
.admin-nav::-webkit-scrollbar-corner { background: transparent; }
.admin-nav-group { margin-bottom: 14px; }
.admin-nav-label { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .07em; color: var(--muted); padding: 6px 10px 4px; }
.admin-nav-item { width: 100%; display: flex; align-items: center; gap: 10px; padding: 9px 10px; border-radius: 9px; font-size: 14px; font-weight: 600; color: var(--text); background: none; text-align: left; }
.admin-nav-item:hover { background: var(--bg); }
.admin-nav-item.active { background: rgba(255,107,53,0.12); color: var(--orange); }
.admin-nav-item.active .admin-nav-ic { color: var(--orange); }
.admin-nav-ic { display: inline-flex; align-items: center; justify-content: center; width: 18px; height: 18px; color: var(--muted); flex-shrink: 0; }
.admin-nav-ic svg { width: 18px; height: 18px; }
.admin-nav-dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; opacity: .5; }
.admin-nav-text { flex: 1; min-width: 0; }
.admin-nav-soon { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); background: var(--bg); padding: 2px 6px; border-radius: 999px; }
/* "n/a" is a different statement from "soon" — the section is never coming, so
   it is drawn quieter still (outline, no fill) rather than looking like a
   pending one. */
.admin-nav-na { background: transparent; border: 1px solid var(--border); opacity: .85; }
/* The unread count on a nav entry (Bugs). Red rather than the sidebar's orange
   because it is not an active-state — it is a thing that has gone wrong and is
   still waiting — and it is only ever rendered when the count is non-zero. */
.admin-nav-badge { flex-shrink: 0; min-width: 20px; text-align: center; font-size: 10.5px; font-weight: 800; line-height: 1.6; color: #fff; background: var(--red); padding: 1px 6px; border-radius: 999px; }
/* Automations and Other Services are sections like the rest, but they route on
   the shared panel module's own hash — so they are <a>s in a list of <button>s
   and need the sidebar's link underline turned off to match them. */
a.admin-nav-item:hover { text-decoration: none; color: var(--text); }
a.admin-nav-item.active:hover { color: var(--orange); }
.admin-exit { display: flex; align-items: center; gap: 8px; padding: 14px 20px; border-top: 1px solid var(--border); font-size: 13px; font-weight: 600; color: var(--muted); }
.admin-exit:hover { color: var(--text); text-decoration: none; background: var(--bg); }
.admin-exit svg { width: 16px; height: 16px; }

/* Body + topbar */
.admin-body { flex: 1; min-width: 0; height: 100vh; display: flex; flex-direction: column; }
.admin-topbar { height: 60px; flex-shrink: 0; display: flex; align-items: center; gap: 12px; padding: 0 24px; background: var(--surface); border-bottom: 1px solid var(--border); }
.admin-hamburger { display: none; background: none; padding: 6px; border-radius: 8px; color: var(--text); }
.admin-hamburger svg { width: 24px; height: 24px; display: block; }
.admin-topbar-title { font-size: 16px; font-weight: 800; }
.admin-main { flex: 1; overflow-y: auto; padding: 24px; }
.admin-scrim { display: none; }

/* Overview sits above the groups — it is the portal's home, not a member of any
   one group, so it gets its own block of space rather than a group label. */
.admin-nav-home { margin-bottom: 10px; }

/* Section heading + search */
.admin-section-head { margin-bottom: 18px; }
.admin-h1 { font-size: 22px; font-weight: 800; letter-spacing: -0.3px; }
.admin-sub { color: var(--muted); font-size: 13px; margin-top: 2px; }
/* Heading with its section-level actions (export, etc.) pinned to the right;
   wraps under the title rather than squeezing it on a narrow screen. */
.admin-section-head-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.admin-head-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
.admin-head-actions .btn { display: inline-flex; align-items: center; gap: 6px; }
.admin-head-actions .btn .ui-ic { width: 16px; height: 16px; }
.admin-search { position: relative; display: flex; align-items: center; max-width: 460px; margin-bottom: 16px; }
.admin-search-ic { position: absolute; left: 12px; display: inline-flex; color: var(--muted); pointer-events: none; }
.admin-search-ic svg { width: 18px; height: 18px; }
.admin-search input { width: 100%; padding: 11px 40px; border: 1.5px solid var(--border); border-radius: 10px; font-size: 14px; background: var(--surface); color: var(--text); }
.admin-search input:focus { outline: none; border-color: var(--orange); box-shadow: 0 0 0 3px rgba(255,107,53,0.15); }
.admin-search-clear { position: absolute; right: 8px; background: none; color: var(--muted); padding: 6px; border-radius: 8px; font-size: 13px; line-height: 1; }
.admin-search-clear:hover { background: var(--bg); color: var(--text); }

/* List filter selects (Users: status / plan / access). They sit above the search
   box, where the Families section puts its plan chips, so the controls that
   narrow a list are always in the same place. */
.admin-filters { display: flex; align-items: center; gap: 10px 16px; flex-wrap: wrap; margin: 0 0 14px; }
.admin-filter { display: inline-flex; align-items: center; gap: 8px; margin: 0; }
.admin-filter-lbl { font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); font-weight: 700; white-space: nowrap; }
.admin-filter select { width: auto; min-width: 150px; padding: 9px 12px; font-size: 13px; font-weight: 600; margin: 0; }

/* Users table */
.admin-tablewrap { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow); }
.admin-table { width: 100%; border-collapse: collapse; font-size: 14px; }
.admin-table thead th { text-align: left; font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); font-weight: 700; padding: 12px 16px; border-bottom: 1px solid var(--border); background: var(--bg); }
.admin-table td { padding: 14px 16px; border-bottom: 1px solid var(--border); vertical-align: middle; }
.admin-table tbody tr:last-child td { border-bottom: none; }
.admin-table tbody tr:hover { background: var(--bg); }
.admin-uc { display: flex; align-items: center; gap: 12px; min-width: 0; }
.admin-uc .avatar-sm { cursor: default; flex-shrink: 0; }
.admin-uc-info { min-width: 0; }
.admin-uc-name { font-weight: 700; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.admin-uc-email { color: var(--muted); font-size: 12.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 260px; }
.admin-fams { display: flex; flex-wrap: wrap; gap: 6px; }
.admin-fam { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 600; background: var(--bg); border: 1px solid var(--border); border-radius: 999px; padding: 3px 10px; }
.admin-fam-plan { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; }
.admin-fam-plan.premium { color: var(--purple); }
.admin-fam-plan.free { color: var(--muted); }
.admin-pill { display: inline-flex; align-items: center; font-size: 11.5px; font-weight: 700; padding: 4px 10px; border-radius: 999px; text-transform: capitalize; }
.admin-pill.active { background: rgba(76,175,80,0.14); color: #2E7D32; }
.admin-pill.disabled { background: rgba(122,117,110,0.16); color: var(--muted); }
.admin-pill.banned { background: rgba(229,57,53,0.14); color: var(--red); }
.admin-pill.plan-premium { background: rgba(156,39,176,0.13); color: var(--purple); }
.admin-pill.plan-free { background: rgba(122,117,110,0.16); color: var(--muted); }
.admin-badge-super { font-size: 10px; font-weight: 800; text-transform: uppercase; letter-spacing: .04em; color: var(--orange); background: rgba(255,107,53,0.12); padding: 2px 7px; border-radius: 999px; }
.admin-muted { color: var(--muted); }

/* Admin → Families: a count and the one line that qualifies it. The cell wrapper
   keeps the two lines together as ONE flex item in the mobile card layout, where
   a bare pair of children would be spread apart by the row's space-between. */
.afm-cell { min-width: 0; }
.afm-n { font-weight: 700; font-size: 15px; }
.afm-sub { font-size: 12px; margin-top: 2px; }

/* Admin → Families → one household. The screen borrows the Analytics day view's
   furniture wholesale — same back link, same stat cards, same titled blocks —
   because it is the same shape of screen (one row of a list, opened) and an
   admin should recognise it rather than learn it twice. Only what is genuinely
   different is below. */
#admin-famd-body.is-loading { opacity: .55; transition: opacity .15s; pointer-events: none; }
.afd-h1 { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.afd-stats { margin-bottom: 4px; }
/* The facts sit in a panel like every other block on the screen, rather than in
   the slide-over's bare two-column grid. */
.afd-facts-wrap { padding: 18px 20px; }
.afd-facts { grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 16px 20px; }
.afd-facts code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .92em; background: var(--bg); border: 1px solid var(--border); border-radius: 5px; padding: 0 5px; }
.afd-table td { vertical-align: top; }
/* A member who was removed from the household is kept — their completions are
   still in the history below — but must not read as someone currently in it. */
.afd-inactive { opacity: .68; }
/* The person cell is the route into their account on Users, so it says so. */
.afd-person { cursor: pointer; border-radius: 10px; margin: -4px -6px; padding: 4px 6px; }
.afd-person:hover { background: var(--bg); }
.afd-person:hover .admin-uc-name { color: var(--orange); }
.afd-more { margin-top: 18px; text-align: center; }
.afd-more .admin-note { margin-top: 8px; }

/* What the last Edit did, kept at the top of the household rather than in a
   toast: `warnings` is where "the PayPal charges have not stopped" is said, and
   that has to still be readable when the admin looks up from the dialog. */
.afd-changed { background: var(--surface); border: 1px solid var(--border); border-left: 3px solid var(--green); border-radius: var(--radius); padding: 14px 16px; margin-bottom: 16px; font-size: 13.5px; line-height: 1.55; color: var(--muted); }
.afd-changed.is-warn { border-left-color: var(--red); }
.afd-changed strong { color: var(--text); }
.afd-changed-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--text); }
.afd-changed-close { border: none; background: none; color: var(--muted); font-size: 14px; line-height: 1; cursor: pointer; padding: 2px 4px; border-radius: 6px; }
.afd-changed-close:hover { color: var(--text); background: var(--bg); }
.afd-changed .admin-confirm-list { color: var(--muted); }
.afd-changed .admin-note-warn { margin-top: 10px; font-weight: 600; }

/* The Edit dialog's "here is what this plan does" panel, under the picker. It
   follows the selection, so it is always about the change on screen. */
.afd-consequence { margin-top: 14px; padding: 12px 14px; border: 1px solid var(--border); border-radius: 10px; background: var(--bg); font-size: 13px; line-height: 1.55; color: var(--muted); }
.afd-consequence p + p { margin-top: 7px; }
.afd-consequence strong { color: var(--text); }
.afd-consequence.is-on { border-color: rgba(67,160,71,0.4); }
.afd-consequence.is-warn { border-color: rgba(229,57,53,0.4); }
.afd-consequence.is-quiet { opacity: .85; }

/* Pagination */
.admin-pager { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 16px; flex-wrap: wrap; }
.admin-pager-info { font-size: 13px; color: var(--muted); }
.admin-pager-btns { display: flex; align-items: center; gap: 8px; }

/* Empty / error / placeholder states */
.admin-empty { text-align: center; padding: 48px 24px; color: var(--muted); background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); }
.admin-empty-ic { width: 44px; height: 44px; margin: 0 auto 12px; color: var(--border); }
.admin-empty-ic svg { width: 44px; height: 44px; }
.admin-empty-title { font-weight: 800; font-size: 16px; color: var(--text); }
.admin-empty-sub { font-size: 13.5px; margin: 6px auto 0; max-width: 380px; line-height: 1.5; }
.admin-empty .btn { margin-top: 16px; }
/* Two ways out of a filtered-to-nothing screen (clear the source, clear the
   search) sit side by side rather than butted together. */
.admin-empty-actions { display: flex; justify-content: center; align-items: center; gap: 10px; flex-wrap: wrap; }
/* "This domain doesn't apply to GuudHome" — an explanation rather than a
   one-line empty state, so it gets room for two paragraphs and a hand-off to
   the section that DOES hold what the admin came for. */
.admin-section-head.is-na { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.admin-na-chip { font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); background: var(--bg); border: 1px solid var(--border); padding: 3px 8px; border-radius: 999px; }
.admin-empty-na .admin-empty-sub { max-width: 560px; text-align: left; }
.admin-empty-na .admin-empty-p { margin: 10px 0 0; }
.admin-empty-na .admin-empty-actions { margin-top: 20px; }
.admin-empty-na .admin-empty-actions .btn { margin-top: 0; }

/* ── Overview (the portal's home) ─────────────────────────────────────────────
   A needs-attention row above a grid of linked tiles. Source order is priority
   order — needs-attention first — so the mobile single-column stack falls out of
   the markup rather than needing its own rules. */

.admin-asof { font-size: 12.5px; color: var(--muted); white-space: nowrap; }

/* Needs attention. Amber when something waits, green when nothing does — a blank
   space would read as broken rather than calm. */
.admin-attn { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; background: var(--surface); border: 1px solid var(--border); border-left: 4px solid var(--yellow); border-radius: var(--radius); box-shadow: var(--shadow); padding: 16px 18px; margin-bottom: 18px; }
.admin-attn.is-clear { border-left-color: var(--green); }
.admin-attn.is-unknown { border-left-color: var(--border); }
.admin-attn-main { display: flex; align-items: flex-start; gap: 12px; min-width: 0; flex: 1; }
.admin-attn-text { min-width: 0; flex: 1; }
.admin-attn-ic { display: inline-flex; flex-shrink: 0; color: var(--yellow); margin-top: 1px; }
.admin-attn-ic svg { width: 22px; height: 22px; }
.admin-attn.is-clear .admin-attn-ic { color: var(--green); }
.admin-attn.is-unknown .admin-attn-ic { color: var(--muted); }
.admin-attn-title { font-size: 15.5px; font-weight: 800; letter-spacing: -0.2px; }
.admin-attn-sub { font-size: 12.5px; color: var(--muted); line-height: 1.5; margin-top: 3px; max-width: 62ch; }
.admin-attn-delta { flex-shrink: 0; margin-left: auto; align-self: center; display: inline-flex; align-items: center; gap: 6px; }
/* Shown only when the headline total mixes the approval queues with the failed
   payments: the chip compares the APPROVALS, and unlabelled it would read as a
   comparison of the total it sits beside. */
.admin-attn-delta-l { font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); }
.admin-attn-items { display: flex; flex-wrap: wrap; gap: 8px; flex-shrink: 0; }
.admin-attn-item { display: inline-flex; align-items: baseline; gap: 8px; background: var(--bg); border: 1px solid var(--border); border-radius: 999px; padding: 7px 14px; color: var(--text); font-weight: 600; font-size: 13px; }
.admin-attn-item:hover { border-color: var(--orange); color: var(--orange); }
.admin-attn-item:focus-visible { outline: 2px solid var(--orange); outline-offset: 2px; }
.admin-attn-n { font-size: 16px; font-weight: 800; }

/* Tiles. Each is a button, because the portal navigates in-page. */
.admin-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 14px; }
.admin-tile { display: flex; flex-direction: column; align-items: stretch; text-align: left; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow); padding: 16px 18px 14px; color: var(--text); font-family: inherit; }
.admin-tile:hover { border-color: var(--orange); transform: translateY(-1px); box-shadow: var(--shadow-lg); }
.admin-tile:focus-visible { outline: 2px solid var(--orange); outline-offset: 2px; }
.admin-tile-head { display: flex; align-items: center; gap: 8px; }
.admin-tile-ic { display: inline-flex; color: var(--orange); flex-shrink: 0; }
.admin-tile-ic svg { width: 18px; height: 18px; }
.admin-tile-label { flex: 1; min-width: 0; font-size: 12px; font-weight: 800; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); }
.admin-tile-go { display: inline-flex; color: var(--border); flex-shrink: 0; }
.admin-tile-go svg { width: 16px; height: 16px; }
.admin-tile:hover .admin-tile-go { color: var(--orange); }
.admin-tile-value { font-size: 30px; font-weight: 800; letter-spacing: -1px; line-height: 1.15; margin-top: 10px; }
.admin-tile-caption { font-size: 12.5px; color: var(--muted); }
.admin-tile-delta { margin-top: 8px; }
.admin-tile-rows { display: flex; flex-direction: column; gap: 6px; margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border); }
.admin-tile-row { display: flex; align-items: baseline; gap: 8px; font-size: 13px; }
.admin-tile-row-l { color: var(--muted); flex: 1; min-width: 0; }
.admin-tile-row-v { font-weight: 700; }
/* A source heading inside a tile's rows — the Comms tile counts email and SMS
   from two different calls, and a flat list of ten lines hides which is which.
   Deliberately quiet: it labels the group, it isn't a number. */
.admin-tile-sub { font-size: 10.5px; font-weight: 800; text-transform: uppercase; letter-spacing: .07em; color: var(--muted); opacity: .8; }
.admin-tile-sub + .admin-tile-row { margin-top: -1px; }
.admin-tile-row + .admin-tile-sub { margin-top: 5px; padding-top: 7px; border-top: 1px solid var(--border); }
.admin-tile-note { font-size: 11.5px; color: var(--muted); line-height: 1.45; margin-top: 10px; }
/* The Work tile's recently-completed lines. Backlog items are whole sentences,
   so each one is clamped to two lines — the tile is a pulse, and the full text
   is one click away in the Backlog section (title= carries it meanwhile). */
.admin-tile-list { display: flex; flex-direction: column; gap: 7px; margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border); }
.admin-tile-list-h { font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); }
.admin-tile-list-i { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; font-size: 12.5px; line-height: 1.4; padding-left: 12px; position: relative; }
.admin-tile-list-i::before { content: "✓"; position: absolute; left: 0; top: 0; color: var(--green); font-weight: 800; font-size: 11px; }
.admin-tile-list-i code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .92em; background: var(--bg); padding: 0 3px; border-radius: 4px; }
.admin-tile-list-none { font-size: 12.5px; color: var(--muted); }

/* The recent-activity feed, under the tiles. Each row is a button into the
   section that owns that event, so the feed drills in the same way the tiles do.
   The type badge, status pill and their colours are the Activity section's own
   (.ac-type / .admin-pill.ac-*), so one event looks the same on both screens. */
.admin-feed { margin-top: 22px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow); padding: 16px 18px 8px; }
.admin-feed-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.admin-feed-title { font-size: 15.5px; font-weight: 800; letter-spacing: -0.2px; }
.admin-feed-sub { font-size: 12.5px; color: var(--muted); line-height: 1.5; margin-top: 3px; }
.admin-feed-none { font-size: 12.5px; color: var(--muted); line-height: 1.5; padding: 14px 0 12px; max-width: 70ch; }
.admin-feed-list { display: flex; flex-direction: column; margin-top: 8px; }
.admin-feed-row { display: flex; align-items: center; gap: 12px; text-align: left; width: 100%; background: none; color: var(--text); font-family: inherit; padding: 11px 8px; margin: 0 -8px; border-radius: 10px; border-top: 1px solid var(--border); }
.admin-feed-row:first-child { border-top: none; }
.admin-feed-row:hover { background: var(--bg); }
.admin-feed-row:focus-visible { outline: 2px solid var(--orange); outline-offset: -2px; }
.admin-feed-main { display: flex; flex-direction: column; gap: 4px; min-width: 0; flex: 1; }
.admin-feed-top { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.admin-feed-ev { font-size: 13.5px; font-weight: 700; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 46ch; }
.admin-feed-who { display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; font-size: 12.5px; color: var(--muted); }
.admin-feed-actor { font-weight: 600; color: var(--text); }
.admin-feed-dot { color: var(--border); }
.admin-feed-when { font-size: 12.5px; color: var(--muted); white-space: nowrap; flex-shrink: 0; }
.admin-feed-go { display: inline-flex; color: var(--border); flex-shrink: 0; }
.admin-feed-go svg { width: 16px; height: 16px; }
.admin-feed-row:hover .admin-feed-go { color: var(--orange); }

/* The comparison chip. Colour is good/bad, not up/down. */
.admin-delta { display: inline-flex; align-items: baseline; gap: 5px; font-size: 12px; font-weight: 700; padding: 3px 8px; border-radius: 999px; background: var(--bg); color: var(--muted); white-space: nowrap; }
.admin-delta.good { background: rgba(76,175,80,0.14); color: #2E7D32; }
.admin-delta.bad { background: rgba(229,57,53,0.12); color: var(--red); }
.admin-delta-b { font-weight: 600; opacity: .85; }
.admin-note-warn { color: var(--red); margin-top: 14px; }

/* Access denied (whole view) */
.admin-denied { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 24px; }
.admin-denied-card { max-width: 420px; text-align: center; background: var(--surface); border: 1px solid var(--border); border-radius: 16px; box-shadow: var(--shadow-lg); padding: 40px 28px; }
.admin-denied-card .admin-empty-ic { color: var(--muted); }
/* The same card serves the authorisation gate: the holding screen shown while
   we ask whether this account may see the portal, and the "couldn't check"
   retry when that question goes unanswered. */
.admin-denied-card .spinner { margin: 0 auto; }
.admin-gate-actions { display: flex; gap: 10px; justify-content: center; margin-top: 18px; }

/* ── Row actions + the one-account slide-over panel ───────────────────────── */
.admin-row { cursor: pointer; }
.admin-th-actions, .admin-td-actions { text-align: right; white-space: nowrap; width: 1%; }

/* Slide-over: scrim + right-hand panel, over the whole portal (sidebar included). */
.admin-panel-wrap { position: fixed; inset: 0; z-index: 80; display: flex; justify-content: flex-end; }
.admin-panel-scrim { position: absolute; inset: 0; background: rgba(0,0,0,0.38); }
.admin-panel {
  position: relative; width: 460px; max-width: 100%; height: 100%;
  background: var(--surface); border-left: 1px solid var(--border); box-shadow: var(--shadow-lg);
  display: flex; flex-direction: column;
}
/* Entrance only — the panel re-renders after every action, and a wrap that kept
   these rules would replay the slide-in each time. */
.admin-panel-wrap.is-entering .admin-panel-scrim { animation: admin-fade .16s ease; }
.admin-panel-wrap.is-entering .admin-panel { animation: admin-slide .2s ease; }
@keyframes admin-fade { from { opacity: 0 } to { opacity: 1 } }
@keyframes admin-slide { from { transform: translateX(20px); opacity: .6 } to { transform: none; opacity: 1 } }
.admin-panel-head { flex-shrink: 0; display: flex; align-items: center; gap: 12px; padding: 16px 18px; border-bottom: 1px solid var(--border); }
.admin-panel-head-main { display: flex; align-items: center; gap: 12px; min-width: 0; flex: 1; }
.admin-panel-head-main .avatar-sm { cursor: default; flex-shrink: 0; }
.admin-panel-head .admin-uc-email { max-width: 300px; }
.admin-panel-close { background: none; color: var(--muted); padding: 6px; border-radius: 8px; flex-shrink: 0; }
.admin-panel-close:hover { background: var(--bg); color: var(--text); }
.admin-panel-close svg { width: 18px; height: 18px; display: block; }
.admin-panel-body { flex: 1; overflow-y: auto; padding: 4px 18px 28px; }
.admin-panel-sec { padding: 18px 0; border-bottom: 1px solid var(--border); }
.admin-panel-sec:last-child { border-bottom: none; }
.admin-panel-h3 { font-size: 12px; font-weight: 800; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); margin-bottom: 12px; }
.admin-panel-actions { display: flex; flex-wrap: wrap; gap: 8px; }
.admin-note { font-size: 12.5px; color: var(--muted); line-height: 1.5; margin-top: 10px; }

/* An on/off switch inside the account panel (Staff access). A state you can read
   without clicking anything, which a pair of buttons can't give you. */
.admin-toggle { display: flex; align-items: flex-start; gap: 12px; }
.admin-switch { position: relative; flex-shrink: 0; width: 46px; height: 26px; margin-top: 1px; padding: 0; border-radius: 999px; background: var(--border); transition: background .15s ease; }
.admin-switch.is-on { background: var(--orange); }
.admin-switch:disabled { opacity: .5; cursor: not-allowed; }
.admin-switch:focus-visible { outline: 2px solid var(--orange); outline-offset: 2px; }
.admin-switch-knob { position: absolute; top: 3px; left: 3px; width: 20px; height: 20px; border-radius: 50%; background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.25); transition: transform .15s ease; }
.admin-switch.is-on .admin-switch-knob { transform: translateX(20px); }
.admin-toggle-label { font-size: 14px; font-weight: 700; }
.admin-toggle-sub { font-size: 12.5px; color: var(--muted); margin-top: 2px; }

/* Fact list (label / value pairs) */
.admin-facts { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 16px; }
.admin-fact { min-width: 0; }
.admin-fact dt { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); }
.admin-fact dd { font-size: 13.5px; font-weight: 600; margin-top: 3px; overflow-wrap: anywhere; }
.admin-tag { display: inline-block; font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); background: var(--bg); border: 1px solid var(--border); padding: 1px 6px; border-radius: 999px; vertical-align: 1px; }

/* One family, with its plan + role selects */
.admin-fam-card { border: 1px solid var(--border); border-radius: 10px; padding: 12px 14px; margin-bottom: 10px; background: var(--bg); }
.admin-fam-card:last-child { margin-bottom: 0; }
.admin-fam-card-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
.admin-fam-card-name { font-weight: 700; font-size: 14px; }
.admin-fam-card-meta { font-size: 12px; color: var(--muted); margin-top: 2px; }
.admin-fam-card-fields { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 12px; }
.admin-field span { display: block; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); margin-bottom: 5px; }
.admin-field select { padding: 8px 10px; font-size: 13px; font-weight: 600; }
.admin-field select:disabled { opacity: .6; cursor: not-allowed; }

/* Confirm dialog for destructive actions */
.admin-confirm { position: fixed; inset: 0; z-index: 90; display: flex; align-items: center; justify-content: center; padding: 20px; background: rgba(0,0,0,0.45); animation: admin-fade .14s ease; }
.admin-confirm-card { width: 100%; max-width: 420px; background: var(--surface); border-radius: 16px; box-shadow: var(--shadow-lg); padding: 22px 22px 18px; }
.admin-confirm-title { font-size: 17px; font-weight: 800; letter-spacing: -0.2px; }
.admin-confirm-body { font-size: 13.5px; color: var(--muted); line-height: 1.55; margin-top: 8px; }
.admin-confirm-body p + p { margin-top: 8px; }
.admin-confirm-body strong { color: var(--text); }
.admin-confirm-btns { display: flex; justify-content: flex-end; gap: 8px; margin-top: 18px; }
/* The "here is exactly what this removes" list a destructive confirm carries. */
.admin-confirm-list { margin: 8px 0 0; padding-left: 18px; }
.admin-confirm-list li { margin-top: 5px; }
/* Optional free-text reason on an audited action (impersonate, delete). */
.admin-reason { display: block; margin-top: 14px; }
.admin-reason span { display: block; font-size: 12px; font-weight: 700; color: var(--muted); margin-bottom: 5px; }
.admin-reason input { width: 100%; padding: 9px 11px; border: 1.5px solid var(--border); border-radius: 10px; font-size: 13px; background: var(--surface); color: var(--text); }
.admin-reason input:focus { outline: none; border-color: var(--orange); }

/* ── Form dialog (Users → New staff account) ───────────────────────────────
   Same card as the confirm dialog, but it holds a form the admin can be sent
   back to: a 409 on the email has to land next to the email box with every
   other field still filled in, so this dialog survives its own errors. */
.admin-form-card { max-width: 460px; max-height: calc(100vh - 40px); overflow-y: auto; }
.admin-form-fields { display: grid; gap: 12px; margin-top: 16px; }
.admin-form-field { display: block; }
.admin-form-field > span { display: block; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); margin-bottom: 5px; }
.admin-form-field input { width: 100%; padding: 10px 12px; border: 1.5px solid var(--border); border-radius: 10px; font-size: 14px; background: var(--surface); color: var(--text); }
.admin-form-field input:focus { outline: none; border-color: var(--orange); }
.admin-form-field input:disabled { opacity: .6; cursor: not-allowed; }
.admin-form-field input.is-invalid { border-color: var(--red); }
/* Same box as the inputs above it, for the one field that is a list rather than
   a value (the chore library's checklist, entered a step to a line). Resizable
   only downwards, so dragging it can't push the dialog wider than the scrim. */
.admin-form-field textarea { width: 100%; padding: 10px 12px; border: 1.5px solid var(--border); border-radius: 10px; font-size: 14px; line-height: 1.5; background: var(--surface); color: var(--text); font-family: inherit; resize: vertical; }
.admin-form-field textarea:focus { outline: none; border-color: var(--orange); }
.admin-form-field textarea:disabled { opacity: .6; cursor: not-allowed; }
.admin-form-field textarea.is-invalid { border-color: var(--red); }
/* A picker in a form dialog (Families → Edit → Plan) matches its text boxes —
   the shared `select` rules are sized for the slide-over panels. */
.admin-form-field select { width: 100%; padding: 10px 12px; border: 1.5px solid var(--border); border-radius: 10px; font-size: 14px; font-weight: 600; background: var(--surface); color: var(--text); }
.admin-form-field select:focus { outline: none; border-color: var(--orange); }
.admin-form-field select:disabled { opacity: .6; cursor: not-allowed; }
/* The per-field message. Hidden by the `hidden` attribute, so it takes no room
   until there is something to say. */
.admin-form-fielderr { display: block; font-style: normal; font-size: 12px; font-weight: 600; color: var(--red); margin-top: 5px; }
/* The whole-form message, for an error that belongs to no single field. */
.admin-form-err { font-size: 13px; font-weight: 600; color: var(--red); background: rgba(229,57,53,0.08); border: 1.5px solid rgba(229,57,53,0.3); border-radius: 10px; padding: 9px 12px; margin-top: 14px; }
.admin-form-card [hidden] { display: none; }

.admin-form-check { display: flex; align-items: flex-start; gap: 10px; margin-top: 16px; padding: 12px 14px; border: 1px solid var(--border); border-radius: 10px; background: var(--bg); cursor: pointer; }
.admin-form-check input { width: 17px; height: 17px; margin: 1px 0 0; flex: none; accent-color: var(--orange); cursor: pointer; }
.admin-form-check input:disabled { cursor: not-allowed; }
.admin-form-check-text { display: block; }
.admin-form-check-label { display: block; font-size: 13.5px; font-weight: 700; }
.admin-form-check-sub { display: block; font-size: 12.5px; color: var(--muted); line-height: 1.5; margin-top: 3px; }

/* ── Admin → Settings (the admin's own account) ────────────────────────────
   A narrow single column rather than the section-wide tables everywhere else:
   this screen is two short forms about one person, and stretching them to a
   1600px content pane would put the labels a monitor's width from the values. */
.admin-set-grid { display: grid; gap: 16px; max-width: 560px; }
.admin-set-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow); padding: 18px 20px 20px; }
.admin-set-card [hidden] { display: none; }
.admin-set-title { display: flex; align-items: center; gap: 8px; font-size: 16px; font-weight: 800; }
.admin-set-title svg { width: 18px; height: 18px; color: var(--orange); flex: none; }
.admin-set-sub { font-size: 12.5px; color: var(--muted); line-height: 1.5; margin-top: 4px; }
.admin-set-rows { margin: 14px 0 0; }
.admin-set-row { display: flex; align-items: baseline; gap: 12px; padding: 9px 0; border-bottom: 1px solid var(--border); }
.admin-set-row:last-child { border-bottom: none; }
.admin-set-row dt { flex: none; width: 130px; font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); }
.admin-set-row dd { margin: 0; min-width: 0; font-size: 14px; font-weight: 600; word-break: break-word; }
.admin-set-email { font-weight: 800; }
.admin-set-none { color: var(--muted); font-weight: 600; }
/* What staff access actually means, said once where the account is shown. */
.admin-set-note { display: flex; align-items: flex-start; gap: 9px; margin-top: 14px; padding: 11px 13px; border: 1px solid var(--border); border-radius: 10px; background: var(--bg); font-size: 12.5px; color: var(--muted); line-height: 1.5; }
.admin-set-note svg { width: 16px; height: 16px; flex: none; margin-top: 1px; color: var(--orange); }
/* The read failed but a cached account is on screen — say it is unconfirmed
   rather than letting stale details pass as fresh. */
.admin-set-stale { margin-top: 12px; font-size: 12.5px; font-weight: 600; color: var(--red); line-height: 1.5; }
.admin-set-link { background: none; padding: 0; font: inherit; color: var(--orange); text-decoration: underline; }
/* Outcome banner, shared by both cards (same language as the email test card). */
.admin-set-out { border-radius: 10px; padding: 12px 14px; margin-top: 14px; border-left: 4px solid var(--border); background: var(--bg); }
.admin-set-out.is-good { border-left-color: var(--green); }
.admin-set-out.is-bad { border-left-color: var(--red); }
.admin-set-out-title { font-size: 13.5px; font-weight: 800; }
.admin-set-out-sub { font-size: 12.5px; color: var(--muted); line-height: 1.5; margin-top: 3px; }
.admin-set-btns { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-top: 16px; }
.admin-set-hint { font-size: 12px; color: var(--muted); flex: 1 1 220px; line-height: 1.45; }
.admin-set-btns .btn { flex: none; }

/* Support access + danger zone (Users → one account) */
.admin-panel-danger .admin-panel-h3 { color: var(--red); }
.admin-imp-ready { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 10px; padding: 10px 12px; border: 1.5px solid rgba(255,107,53,0.35); background: rgba(255,107,53,0.07); border-radius: 12px; }
.admin-imp-exp { font-size: 12px; font-weight: 700; color: var(--muted); }

/* ── Impersonation banner (the tab holding a support session) ──────────────
   Deliberately loud and in the page flow: an admin acting inside someone
   else's account must never have to wonder whose account it is. */
.imp-banner { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; padding: 10px 20px; background: #B26A00; color: #fff; }
.imp-banner[hidden] { display: none; }
.imp-banner-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.imp-banner-text strong { font-size: 14px; font-weight: 800; }
.imp-banner-text span { font-size: 12px; opacity: .92; }
.imp-banner .btn { background: rgba(255,255,255,0.16); color: #fff; border: 1.5px solid rgba(255,255,255,0.55); flex-shrink: 0; }
.imp-banner .btn:hover { background: #fff; color: #B26A00; }

/* ── Admin → Email ──────────────────────────────────────────────
   The send log plus the test-send door. `failed` and `dropped` are different
   events and are coloured differently on purpose: red is "the gateway said no",
   amber is "this server was never given credentials to ask it". */
.em-filters { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin: 0 0 14px; }
.em-chips { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.em-chip { display: inline-flex; align-items: center; gap: 6px; padding: 7px 13px; border-radius: 999px; border: 1.5px solid var(--border); background: var(--surface); font-size: 13px; font-weight: 700; color: var(--muted); white-space: nowrap; }
.em-chip:hover { border-color: var(--orange); color: var(--text); }
.em-chip.is-on { border-color: var(--orange); background: rgba(255,107,53,0.1); color: var(--orange); }
.em-chip:focus-visible { outline: 2px solid var(--orange); outline-offset: 2px; }
.em-chip-n { font-size: 11.5px; font-weight: 800; color: inherit; opacity: .75; }
.em-filter-right { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.em-select { width: auto; min-width: 168px; padding: 9px 12px; font-size: 13px; font-weight: 600; }
.em-search { flex: 0 1 300px; min-width: 220px; margin: 0; }

.admin-pill.em-sent { background: rgba(76,175,80,0.14); color: #2E7D32; }
.admin-pill.em-failed { background: rgba(229,57,53,0.14); color: var(--red); }
.admin-pill.em-dropped { background: rgba(247,201,72,0.24); color: #8A6D0B; }
.em-to { font-weight: 700; font-size: 13.5px; overflow-wrap: anywhere; }
.em-to-sub { font-size: 12px; margin-top: 2px; }
.em-subject { font-size: 13.5px; overflow-wrap: anywhere; }
.em-template { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; background: var(--bg); border: 1px solid var(--border); border-radius: 5px; padding: 2px 6px; color: var(--muted); }
.em-note { font-size: 12px; color: var(--muted); line-height: 1.45; margin-top: 5px; max-width: 34ch; }
/* A disabled button doesn't receive hover in every browser, so the wrapper is
   what carries the "why not" tooltip on a row that can't be re-sent. */
.em-resend-wrap { display: inline-block; }

/* The test-send form. Sits above the log because its result lands in the log —
   send, then read the row it wrote, in reading order. */
.em-test { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow); padding: 18px; margin: 0 0 16px; }
.em-test-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: 14px; }
.em-test-title { font-size: 15.5px; font-weight: 800; letter-spacing: -0.2px; }
.em-test-sub { font-size: 12.5px; color: var(--muted); line-height: 1.5; margin-top: 3px; max-width: 68ch; }
.em-test-fields { display: grid; grid-template-columns: 1fr 1fr; gap: 0 14px; }
.em-opt { font-weight: 600; text-transform: none; letter-spacing: 0; opacity: .8; }
.em-test-html { min-height: 96px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12.5px; line-height: 1.6; }
.em-test-btns { display: flex; align-items: center; justify-content: flex-end; gap: 12px; flex-wrap: wrap; }
.em-test-hint { flex: 1; min-width: 200px; font-size: 12px; color: var(--muted); line-height: 1.45; }
.em-test-out { border-radius: 10px; padding: 12px 14px; margin: 0 0 14px; border-left: 4px solid var(--border); background: var(--bg); }
.em-test-out.is-good { border-left-color: var(--green); }
.em-test-out.is-bad { border-left-color: var(--red); }
.em-test-out-title { font-size: 13.5px; font-weight: 800; }
.em-test-out-sub { font-size: 12.5px; color: var(--muted); line-height: 1.5; margin-top: 3px; }
.em-test-meta { font-size: 12px; color: var(--muted); margin-top: 6px; }
.em-test-meta code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11.5px; background: var(--surface); border: 1px solid var(--border); border-radius: 5px; padding: 1px 4px; overflow-wrap: anywhere; }
.em-test-out.is-warn { border-left-color: var(--yellow); }

/* The Email section's two halves (send log / templates). A switch INSIDE one
   section, not portal navigation — the left panel stays the section IA. */
.em-views { display: flex; gap: 4px; border-bottom: 1.5px solid var(--border); margin: 0 0 16px; }
.em-view { padding: 9px 14px; border: 0; background: none; font-size: 13.5px; font-weight: 700; color: var(--muted); border-bottom: 2.5px solid transparent; margin-bottom: -1.5px; }
.em-view:hover { color: var(--text); }
.em-view.is-on { color: var(--orange); border-bottom-color: var(--orange); }
.em-view:focus-visible { outline: 2px solid var(--orange); outline-offset: -2px; border-radius: 6px 6px 0 0; }
.em-viewhead { display: flex; align-items: flex-start; justify-content: space-between; gap: 14px; flex-wrap: wrap; margin: 0 0 16px; }
.em-viewhead .admin-sub { margin: 0; max-width: 78ch; }

/* ── Admin → Email → Templates ──────────────────────────────────
   The editable copy behind every send. The one thing the styling has to carry
   is WHICH copy goes out — hence three distinct pills rather than an
   on/off switch, and a preview pane that is visibly a rendering of a draft. */
.admin-pill.et-live { background: rgba(76,175,80,0.14); color: #2E7D32; text-transform: none; }
.admin-pill.et-builtin { background: rgba(122,117,110,0.16); color: var(--muted); text-transform: none; }
.admin-pill.et-platform { background: rgba(247,201,72,0.24); color: #8A6D0B; text-transform: none; }

.et-name { font-weight: 700; font-size: 13.5px; }
.et-key { margin-top: 3px; }
.et-key code, .et-editor-sub code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11.5px; color: var(--muted); background: var(--bg); border: 1px solid var(--border); border-radius: 5px; padding: 1px 5px; }
.et-list tbody tr { cursor: pointer; }
.et-foot { font-size: 12.5px; color: var(--muted); line-height: 1.5; margin: 12px 2px 0; max-width: 80ch; }

.et-editorhead { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin: 0 0 14px; }
.et-editorhead .btn { display: inline-flex; align-items: center; gap: 6px; flex: none; }
.et-editorhead-main { min-width: 0; }
.et-editor-title { font-size: 17px; font-weight: 800; letter-spacing: -0.2px; }
.et-editor-sub { font-size: 12.5px; color: var(--muted); margin-top: 4px; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.et-banner { background: var(--surface); border: 1px solid var(--border); border-left: 4px solid var(--border); border-radius: var(--radius); padding: 12px 16px; margin: 0 0 14px; font-size: 12.5px; color: var(--muted); line-height: 1.55; max-width: 92ch; }
.et-banner.is-warn { border-left-color: var(--yellow); }

.et-editor { display: grid; grid-template-columns: minmax(0,1fr) minmax(0,1fr); gap: 18px; align-items: start; }
.et-col { min-width: 0; }
.et-form { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow); padding: 18px; }
.et-side { display: flex; flex-direction: column; gap: 16px; }
.et-html { min-height: 340px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12.5px; line-height: 1.6; resize: vertical; }
.et-hint { font-size: 11.5px; color: var(--muted); margin-top: 5px; }

.et-vars { background: var(--bg); border: 1px solid var(--border); border-radius: 10px; padding: 11px 13px; margin: 0 0 16px; }
.et-vars-title { font-size: 10.5px; font-weight: 800; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); margin-bottom: 7px; }
.et-vars-row { display: flex; flex-wrap: wrap; gap: 5px; }
.et-var { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11.5px; font-weight: 600; background: var(--surface); border: 1px solid var(--border); border-radius: 6px; padding: 2px 6px; }
.et-var.is-req { border-color: rgba(76,175,80,0.55); color: #2E7D32; }
.et-var.is-unknown { border-color: rgba(247,201,72,0.7); color: #8A6D0B; }
.et-vars-note { font-size: 11.5px; color: var(--muted); line-height: 1.5; margin-top: 8px; }
.et-vars-note.is-bad { color: var(--red); font-weight: 600; }
.et-vars-note code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; }

.et-toggle { display: flex; align-items: flex-start; gap: 10px; margin: 0 0 16px; cursor: pointer; }
.et-toggle input { width: 17px; height: 17px; margin-top: 2px; flex: none; accent-color: var(--orange); }
.et-toggle-label { display: block; font-size: 13.5px; font-weight: 700; }
.et-toggle-sub { display: block; font-size: 12px; color: var(--muted); line-height: 1.5; margin-top: 2px; max-width: 56ch; }
.et-btns { display: flex; align-items: center; justify-content: flex-end; gap: 8px; flex-wrap: wrap; }

.et-pane { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow); padding: 16px; }
.et-pane-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
.et-pane-title { font-size: 14.5px; font-weight: 800; letter-spacing: -0.2px; }
.et-pane-sub { font-size: 12px; color: var(--muted); line-height: 1.5; margin-top: 3px; max-width: 56ch; }
.et-stale { background: rgba(247,201,72,0.18); color: #8A6D0B; border-radius: 8px; padding: 8px 11px; font-size: 12px; font-weight: 600; line-height: 1.45; margin: 0 0 12px; }
.et-subjline { font-size: 13.5px; font-weight: 700; line-height: 1.45; margin: 0 0 10px; overflow-wrap: anywhere; }
.et-subjlabel { font-size: 10.5px; font-weight: 800; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); margin-right: 6px; }
.et-frame { width: 100%; height: 420px; border: 1px solid var(--border); border-radius: 10px; background: #fff; display: block; }
.et-testrow { display: flex; align-items: flex-start; gap: 8px; }
.et-testrow .form-group { flex: 1; min-width: 0; margin: 0; }
.et-testpane .em-test-out { margin: 12px 0 0; }

@media (max-width: 1180px) {
  .et-editor { grid-template-columns: 1fr; }
  .et-html { min-height: 220px; }
  .et-frame { height: 340px; }
  .st-body { min-height: 120px; }
}

/* ── Admin → SMS ────────────────────────────────────────────────
   The inbound line log. Filters reuse the Email screen's chips/select/search
   (.em-*) and the roll-up reuses the Revenue screen's stat cards (.rv-stat*), so
   a counter and a filter look the same wherever they are read. What is specific
   here is the exchange: a text in and our reply out, side by side as two
   bubbles, so a row reads as the conversation it was. */
.sm-stats { margin: 0 0 18px; }
.sm-summary-off { background: var(--surface); border: 1px solid var(--border); border-left: 4px solid var(--yellow); border-radius: var(--radius); box-shadow: var(--shadow); padding: 12px 16px; margin: 0 0 16px; font-size: 12.5px; color: var(--muted); line-height: 1.5; }

.sm-table td { vertical-align: top; }
.sm-when { font-size: 13.5px; white-space: nowrap; }
.sm-sub { font-size: 11.5px; margin-top: 2px; }
.sm-num-v { font-size: 13.5px; font-weight: 700; white-space: nowrap; font-variant-numeric: tabular-nums; }
.sm-who { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; min-width: 0; }
.sm-who-top { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }

/* Two voices, two tints: theirs on the page background, ours in the brand
   colour — the same distinction a phone makes, and the fastest way to read a
   column of exchanges without checking a direction label on every line. */
.sm-msg { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; min-width: 0; max-width: 44ch; }
.sm-bubble { font-size: 13.5px; line-height: 1.5; overflow-wrap: anywhere; white-space: pre-wrap; background: var(--bg); border: 1px solid var(--border); border-radius: 12px 12px 12px 4px; padding: 8px 11px; }
.sm-msg-out .sm-bubble { background: rgba(255,107,53,0.09); border-color: rgba(255,107,53,0.28); border-radius: 12px 12px 4px 12px; }
.sm-status { align-self: flex-start; }
.sm-none { font-size: 12.5px; font-style: italic; }
.sm-err { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11.5px; color: var(--red); line-height: 1.45; margin-top: 6px; max-width: 34ch; overflow-wrap: anywhere; }

/* Outcome colour carries the meaning, so thirteen enum words don't have to be
   learned: it did the thing, it was refused on purpose, our side broke, it ended
   unremarkably. */
.admin-pill.sm-good { background: rgba(76,175,80,0.14); color: #2E7D32; }
.admin-pill.sm-warn { background: rgba(247,201,72,0.24); color: #8A6D0B; }
.admin-pill.sm-bad { background: rgba(229,57,53,0.14); color: var(--red); }
.admin-pill.sm-neutral { background: var(--bg); color: var(--muted); }
/* The reply leg's own status. `dropped` is amber, never red: we chose not to
   answer a number nobody here owns, which is a correct outcome on a line shared
   across products — not a delivery failure. */
.admin-pill.sm-sent { background: rgba(76,175,80,0.14); color: #2E7D32; }
.admin-pill.sm-dropped { background: rgba(247,201,72,0.24); color: #8A6D0B; }
.admin-pill.sm-failed { background: rgba(229,57,53,0.14); color: var(--red); }
.admin-pill.sm-delivered { background: var(--bg); color: var(--muted); }

/* ── Admin → SMS → Templates ────────────────────────────────────
   The editable copy behind every text. The editor chrome is the Email
   templates one (.et-*) rather than a second set of the same rules; what is
   specific here is that a text is METERED — so the character count is a first
   class thing that changes colour at the segment boundary — and that it is read
   on a handset, so the preview is the log's own bubble rather than an iframe. */
.st-body { min-height: 150px; font-size: 13.5px; line-height: 1.6; resize: vertical; }
.st-count { font-weight: 700; font-variant-numeric: tabular-nums; }
/* Past 160 characters the text costs another message. Amber, not red: it is a
   thing to know you are doing, not a thing you may not do. */
.st-count.is-multi { color: #8A6D0B; }
.st-seg { font-size: 11.5px; font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; padding: 2px 7px; }
.st-seg.is-multi { background: rgba(247,201,72,0.22); border-color: rgba(247,201,72,0.5); color: #8A6D0B; }

/* Two lines of the copy in the list — enough to recognise the text, not enough
   for one long template to own the table. */
.st-excerpt { font-size: 13px; line-height: 1.5; overflow-wrap: anywhere; display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; max-width: 46ch; }
.st-where { font-size: 12px; line-height: 1.45; display: block; max-width: 30ch; }

/* The preview: our own outgoing bubble, tinted like the reply leg in the log,
   on a narrow column so the line breaks fall roughly where a phone puts them. */
.st-phone { background: var(--bg); border: 1px solid var(--border); border-radius: 12px; padding: 14px; display: flex; }
.st-bubble { font-size: 14px; line-height: 1.5; white-space: pre-wrap; overflow-wrap: anywhere; background: rgba(255,107,53,0.09); border: 1px solid rgba(255,107,53,0.28); border-radius: 12px 12px 4px 12px; padding: 9px 12px; max-width: 34ch; margin-left: auto; }
.st-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 10px; font-size: 11.5px; }

/* ── Admin → Notifications ──────────────────────────────────────────────
   The in-app channel's log — the third comms screen, beside the email send log
   and the SMS line log. It reuses the catalogues' furniture wholesale
   (.em-chips for the read filter, .admin-filters/.admin-search for the rest,
   .admin-table/.admin-pager for the shape, .ct-item/.ct-title/.ct-sub for the
   multi-line cells, .ct-fam-btn for the recipient and household filters, and
   Activity's own .ac-scope bar for the recipient scope) so it reads as one of
   the lists rather than as a console. Only what belongs to a NOTIFICATION is
   new: the message body, and whether anybody ever opened it. */
.nt-chips { margin: 0 0 12px; }
.nt-table td { vertical-align: top; }
.nt-item { max-width: 420px; }
.nt-top { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.nt-type { color: var(--text); white-space: nowrap; }
.nt-title { margin-top: 3px; }
/* Two lines of the message — enough to recognise what was said, not enough for
   one long notification to own the table. The full text is on the title
   attribute, and the export carries it whole. */
.nt-body { font-size: 12.5px; line-height: 1.5; margin-top: 3px; overflow-wrap: anywhere; display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
/* Unread is the finding on this screen — it is the answer to "did they ever
   actually see it" — so it carries the amber every other section uses for
   "still waiting on a human", and read is the same quiet green as everywhere
   else for "nothing to do here". */
.admin-pill.nt-unread { background: rgba(247,201,72,0.24); color: #8A6D0B; text-transform: none; }
.admin-pill.nt-read { background: rgba(76,175,80,0.10); color: #2E7D32; text-transform: none; }
.admin-pill.nt-priority { background: rgba(156,39,176,0.12); color: var(--purple); text-transform: none; }
.nt-read-cell { gap: 4px; }
/* An unread row leans on its left edge rather than on a tint: a whole table of
   coloured rows would make the pill it duplicates unreadable, and most of this
   log is unread most of the time. */
.nt-table tr.nt-row-unread td:first-child { box-shadow: inset 3px 0 0 rgba(247,201,72,0.75); }
/* The caveat under the table. It keeps its own space rather than being tucked
   against the pager, because it changes what a row count means. */
.nt-scope-note { margin-top: 14px; max-width: 78ch; }

/* ── Admin → Activity ───────────────────────────────────────────
   The cross-tenant event stream. Type chips get their own row above the
   date/search row: they are a multi-select, so they grow with the vocabulary
   and would otherwise squeeze the date inputs to nothing. */
.ac-chips { margin: 0 0 10px; }
.ac-filters { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin: 0 0 14px; }
.ac-range { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.ac-range-dates { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ac-range-lbl { font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); font-weight: 700; margin: 0; }
.ac-date { width: auto; min-width: 150px; padding: 8px 11px; font-size: 13px; font-weight: 600; margin: 0; }
.ac-presets { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.ac-preset { font-size: 12px; font-weight: 700; color: var(--muted); background: none; border: none; padding: 6px 8px; border-radius: 8px; text-decoration: underline; text-underline-offset: 3px; white-space: nowrap; }
.ac-preset:hover { color: var(--orange); background: var(--bg); }
.ac-preset:focus-visible { outline: 2px solid var(--orange); outline-offset: 2px; }
.ac-preset-clear { text-decoration: none; border: 1px solid var(--border); }

/* The scope bar — "you are looking at ONE account/household". It sits above the
   chips because it outranks them: the chips narrow what happened, this narrows
   whose. Tinted rather than plain so a scoped stream can never be mistaken for
   the whole platform at a glance, with the way out on the same line. */
.ac-scope { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin: 0 0 12px; padding: 11px 14px; border: 1px solid rgba(255,107,53,0.35); background: rgba(255,107,53,0.07); border-radius: 12px; }
.ac-scope-ic { display: inline-flex; color: var(--orange); flex-shrink: 0; }
.ac-scope-ic svg { width: 20px; height: 20px; }
.ac-scope-text { flex: 1 1 240px; min-width: 0; }
.ac-scope-title { font-size: 13.5px; font-weight: 700; overflow-wrap: anywhere; }
.ac-scope-sub { font-size: 12px; color: var(--muted); margin-top: 2px; }
.ac-scope .btn { flex-shrink: 0; }
@media (max-width: 560px) { .ac-scope .btn { width: 100%; } }

/* One event per row: when it happened, what it was, who did it, whose house. */
.ac-table td { vertical-align: top; }
.ac-when { font-size: 13.5px; white-space: nowrap; }
.ac-sub { font-size: 11.5px; margin-top: 2px; }
.ac-ev { min-width: 0; }
.ac-ev-top { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.ac-ev-title { font-size: 13.5px; margin-top: 4px; overflow-wrap: anywhere; }
.ac-type { color: var(--text); }
.ac-type-signup { color: var(--purple); border-color: rgba(156,39,176,0.35); }
.ac-type-task_completion { color: #2E7D32; border-color: rgba(76,175,80,0.4); }
.ac-type-reward_redemption { color: var(--orange); border-color: rgba(255,107,53,0.4); }
.ac-type-kid_request, .ac-type-approval_request { color: #8A6D0B; border-color: rgba(247,201,72,0.55); }
/* Status vocabularies differ per event type, so the colour carries the meaning:
   settled, still waiting on a parent, refused. */
.admin-pill.ac-ok { background: rgba(76,175,80,0.14); color: #2E7D32; }
.admin-pill.ac-wait { background: rgba(247,201,72,0.24); color: #8A6D0B; }
.admin-pill.ac-no { background: rgba(229,57,53,0.14); color: var(--red); }
/* Signed, because the stream mixes earning and spending the same currency. */
.ac-pts { font-size: 12px; font-weight: 800; color: #2E7D32; }
.ac-pts.is-spend { color: var(--orange); }
.ac-fam { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; min-width: 0; }

/* ── Admin → Chores / Rewards / Kid requests (the pending queue) ─
   One screen for three sections, so the styles are shared too. The age column
   is the point of the screen and is coloured by how long the wait has run:
   normal, two days old, a week old. Type tags reuse the Activity stream's
   colours (.ac-type-*) so one kind of event looks the same on both screens. */
.ap-chips { margin: 0 0 10px; }
.ap-filters { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin: 0 0 14px; }
.ap-sort { display: inline-flex; align-items: center; gap: 8px; margin: 0; }
.ap-sort-lbl { font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); font-weight: 700; }
.ap-sort select { width: auto; min-width: 190px; padding: 9px 12px; font-size: 13px; font-weight: 600; margin: 0; }

.ap-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin: 0 0 12px; font-size: 13.5px; }
.ap-head-n { font-weight: 700; }
.ap-head-oldest { color: var(--muted); }
.ap-head-oldest strong { color: var(--text); }

.ap-table td { vertical-align: top; }
.ap-when { white-space: nowrap; }
.ap-age { font-size: 14px; font-weight: 800; }
.ap-age.is-old { color: #8A6D0B; }
.ap-age.is-stale { color: var(--red); }
.ap-sub { font-size: 11.5px; margin-top: 2px; }
.ap-item { display: flex; flex-direction: column; align-items: flex-start; gap: 3px; min-width: 0; max-width: 380px; }
.ap-item-top { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.ap-item-title { font-size: 13.5px; font-weight: 600; overflow-wrap: anywhere; }
.ap-item-detail { font-style: italic; overflow-wrap: anywhere; }
/* Stacked, not inline: Approve and Deny side by side in a narrow column is how
   a child's chore gets rejected when it was meant to be approved. */
.ap-acts { display: flex; flex-direction: column; align-items: stretch; gap: 6px; min-width: 128px; }
.ap-acts .btn { white-space: nowrap; }
/* The row whose decision is in flight reads as busy, so a click on a
   neighbouring row is an obvious mistake rather than a race. */
.ap-table tr.is-busy { opacity: .55; }

/* ── Admin → Chores → Catalogue ─────────────────────────────────────────
   The queue's sibling view: what every household has DEFINED, rather than what
   is waiting on a parent. Reuses the generic list furniture — .em-views for the
   tabs, .admin-filters, .admin-search, .admin-table, .admin-pager — so it reads
   as the same kind of screen as Users, Families and Subscribers. Only the two
   things that belong to a chore are new: the points cell (a figure plus its
   bonus) and the pills for the two statuses the account palette has no word
   for. */
.ct-head-bits { color: var(--muted); }
.ct-table td { vertical-align: top; }
.ct-item { display: flex; flex-direction: column; align-items: flex-start; gap: 3px; min-width: 0; max-width: 340px; }
.ct-title { font-size: 13.5px; font-weight: 600; overflow-wrap: anywhere; }
.ct-sub { font-size: 11.5px; margin-top: 2px; overflow-wrap: anywhere; }
.ct-tag, .ct-tag-family { white-space: nowrap; }
.ct-fam { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; min-width: 0; }
/* The household name is a filter, not a link off the page — it narrows this
   same list, which is the only way to reach a household the select's first
   page does not offer. */
.ct-fam-btn { border: 0; background: none; padding: 0; font: inherit; font-weight: 700; font-size: 13.5px; color: var(--text); text-align: left; overflow-wrap: anywhere; }
.ct-fam-btn:hover { color: var(--orange); text-decoration: underline; }
.ct-fam-btn:focus-visible { outline: 2px solid var(--orange); outline-offset: 2px; border-radius: 4px; }
.ct-cat { display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap; font-size: 13px; }
.ct-cat-ic { width: 16px; height: 16px; flex: none; }
.ct-cat-off { font-size: 11.5px; }
.ct-when { white-space: nowrap; }
.ct-th-pts, .ct-td-pts { text-align: right; white-space: nowrap; }
.ct-pts { font-size: 14px; font-weight: 800; }
.ct-bonus { font-size: 11.5px; font-weight: 700; color: var(--purple); margin-left: 4px; }
.admin-pill.ct-paused { background: rgba(247,201,72,0.24); color: #8A6D0B; }
.admin-pill.ct-completed { background: rgba(76,175,80,0.10); color: #2E7D32; }
/* Stacked, not inline: Change points and Archive side by side in a narrow
   column is how a household's chore gets pulled when it was meant to be
   repriced. Same rule the approval queue's Approve/Deny pair follows. */
.ct-acts { display: flex; flex-direction: column; align-items: stretch; gap: 6px; min-width: 132px; }
.ct-acts .btn { white-space: nowrap; }
.ct-acts .btn:disabled { opacity: .5; cursor: not-allowed; }
/* The row whose edit is in flight reads as busy, so a click on a neighbouring
   row is an obvious mistake rather than a race. */
.ct-table tr.is-busy { opacity: .55; }

/* ── Admin → Rewards → Catalogue ────────────────────────────────────────
   The redemption queue's sibling view: what every household has PUT IN ITS
   STORE, rather than what a child has already paid for and is waiting on.
   Reuses the chore catalogue's furniture wholesale — .em-views for the tabs,
   .ct-item/.ct-title/.ct-sub for the multi-line cells, .ct-fam-btn for the
   household filter — so the two halves of the portal's own domain read as one
   kind of screen. Only what belongs to a reward is new: its icon beside the
   name, the unlock cell, and the "this can never be earned" flag. */
.rc-table td { vertical-align: top; }
.rc-head { margin-bottom: 12px; }
.rc-item { display: flex; align-items: flex-start; gap: 9px; min-width: 0; max-width: 340px; }
.rc-ic { flex: none; display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 30px; border-radius: 9px; background: rgba(156,39,176,0.10); color: var(--purple); }
.rc-ic .ui-ic { width: 17px; height: 17px; }
.rc-unlock { white-space: nowrap; }
/* A chore-gated reward whose chore has been deleted can never be earned by
   anybody, which is a defect in that household's store rather than a detail —
   so it is coloured like one instead of sitting muted under the label. */
.rc-broken { font-size: 11.5px; font-weight: 700; color: #C62828; margin-top: 2px; white-space: normal; }
.rc-th-num, .rc-td-num { text-align: right; white-space: nowrap; }
.rc-pts { font-size: 14px; font-weight: 800; }
/* A cost carried by a reward nobody buys with points: shown, because it is
   stored, but never with the weight of a real price. */
.rc-pts-muted { font-weight: 700; color: var(--muted); }

/* ── Admin → Kid requests → Catalogue ───────────────────────────────────
   The waiting queue's sibling view: everything the kids have EVER asked,
   answered or not, rather than only the asks nobody has dealt with. Reuses the
   other two catalogues' furniture wholesale — .em-views for the tabs,
   .ct-item/.ct-title/.ct-sub for the multi-line cells, .ct-fam-btn for the
   household filter, .rc-broken for a pointer that leads nowhere — so all three
   halves of the portal's own domain read as one kind of screen. Only what
   belongs to a request is new: the status the family app already has words for,
   and the two ways the AI classifier can leave a row incomplete. */
.kr-table td { vertical-align: top; }
.kr-head { margin-bottom: 12px; }
/* Answered, but nothing was made of it — neither the green of a conversion nor
   the red of a dismissal, because it is neither. */
.admin-pill.kr-reviewed { background: rgba(122,117,110,0.16); color: var(--muted); }
.kr-kind, .kr-link { min-width: 0; }
/* Asks the classifier never managed to read. They reached the parent as raw
   text with no kind, title or date pulled out of them — a failure that is
   invisible on every other screen, so it is said out loud above the table and
   marked on the row itself. */
.kr-unclassified { font-size: 11.5px; font-weight: 700; color: #8A6D0B; }
.kr-unread { font-style: italic; }
/* The per-row status control. It sits in the actions column the other
   catalogues put their row buttons in, and it is sized like them rather than
   like the filter selects above the table: those narrow a list, this one
   changes what a family sees in their own app. */
.kr-acts { min-width: 214px; }
.kr-acts .admin-rowsel { width: 100%; margin: 0; padding: 8px 10px; font-size: 12.5px; font-weight: 600; }
.kr-acts .admin-rowsel:disabled { opacity: .5; cursor: not-allowed; }
/* A row whose own retriage is in flight. Same fade the other action tables use,
   so "this one is working" reads identically wherever an admin meets it. */
.kr-table tr.is-busy { opacity: .55; }

/* ── Admin → Calendar ───────────────────────────────────────────────────
   Every household's shared calendar, cross-tenant. Reuses the catalogues'
   furniture wholesale — .admin-filters, .admin-search, .admin-table,
   .admin-pager for the shape, .ac-range/.ac-date/.ac-preset for the date range
   (the same control the activity stream uses), .ct-item/.ct-title/.ct-sub for
   the multi-line cells, .ct-fam-btn for the household filter, .rc-broken for a
   row whose end is before its start. Only what belongs to a diary is new: the
   when-cell, the colour swatch the family itself sees, and the badge that says
   a row came from Google rather than from the household. */
.cal-table td { vertical-align: top; }
/* A row whose own DELETE is in flight: dimmed with its buttons disabled, so a
   second click is an obvious mistake rather than a race. Same treatment as
   every other catalogue's row actions (.ct-table, .gc-table). */
.cal-table tr.is-busy { opacity: .55; }
.cal-head { margin-bottom: 12px; }
/* The when-cell is the row's subject, so it is the one column allowed to be
   heavier than the title beside it. */
.cal-when { min-width: 0; }
.cal-when-main { font-size: 13.5px; font-weight: 600; }
.cal-when-main .admin-tag { margin-left: 4px; }
.cal-item { display: flex; align-items: flex-start; gap: 9px; min-width: 0; max-width: 340px; }
/* The same colour the household sees on its own calendar: a custom hex if the
   entry carries one, otherwise the assignee's avatar colour, otherwise neutral
   for a whole-family entry (entryColor()). It is the one thing that lets an
   admin and a parent talk about the same row. */
.cal-dot { flex: none; width: 10px; height: 10px; border-radius: 50%; margin-top: 5px; }
.cal-repeat { margin-top: 2px; }
/* Google-imported rows. The import is ONE-WAY, so this is a warning as much as
   a label — anything the household edits here is overwritten on the next sync. */
.admin-pill.cal-google { background: rgba(66,133,244,0.12); color: #1A56C4; }
.cal-src-local { font-size: 12.5px; white-space: nowrap; }
.cal-imported { align-self: center; }
/* The scope note under the table (derived_entries_excluded). It is the sentence
   that stops an empty page reading as an empty calendar, so it keeps its own
   space rather than being tucked against the pager. */
.cal-scope-note { margin-top: 14px; max-width: 76ch; }

/* ── Admin → Shopping ───────────────────────────────────────────────────
   Every household's grocery lists, cross-tenant. Reuses the catalogues'
   furniture wholesale — .admin-filters, .admin-search, .admin-table,
   .admin-pager for the shape, .ct-item/.ct-title/.ct-sub for the multi-line
   cells, .ct-fam-btn for the household and store filters. Only what belongs to
   a shopping list is new: the fill bar that says how much of it is in the cart,
   the money cells, and the flags that change what a row MEANS (the default list
   every household is given, one they archived, one nobody ever filled). */
.sh-table td { vertical-align: top; }
/* A row whose own DELETE is in flight: dimmed with its buttons disabled, so a
   second click is an obvious mistake rather than a race. Same treatment as
   every other catalogue's row actions (.ct-table, .cal-table, .gc-table). */
.sh-table tr.is-busy { opacity: .55; }
.sh-head { margin-bottom: 12px; }
.sh-head-tags { display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap; }
/* Over budget is the section's one finding worth a colour: a household spending
   past the limit it set itself is the row an owner would act on. */
.admin-tag.sh-over { background: rgba(229,57,53,0.12); border-color: rgba(229,57,53,0.28); color: #C62828; }
.admin-tag.sh-empty-tag { background: rgba(247,201,72,0.20); border-color: rgba(247,201,72,0.45); color: #8A6D0B; }
.admin-tag.sh-archived { background: var(--bg); color: var(--muted); }
.sh-item { display: flex; align-items: flex-start; gap: 9px; min-width: 0; max-width: 320px; }
/* The emoji the family picked for the list, at the size it wears on their own
   screen — it is how a parent finds their list on a call. */
.sh-ic { flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; border-radius: 9px; background: rgba(255,138,91,0.12); font-size: 16px; line-height: 1; }
.sh-flags { display: flex; align-items: center; gap: 5px; flex-wrap: wrap; }
.sh-stores { gap: 5px; }
.sh-store-btn { font-size: 13px; }
.sh-stops { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.sh-more { font-size: 11.5px; font-weight: 700; }
.sh-nostore { font-size: 12.5px; }
/* The three numeric columns line up on the right, because they are read down
   the column rather than across the row. */
.sh-th-num, .sh-td-num { text-align: right; }
.sh-items { display: flex; flex-direction: column; align-items: flex-end; gap: 4px; min-width: 96px; }
.sh-items-n { font-size: 14px; font-weight: 800; white-space: nowrap; }
.sh-of { font-weight: 600; color: var(--muted); }
.sh-bar { width: 100%; max-width: 110px; height: 5px; border-radius: 999px; background: var(--bg); overflow: hidden; }
.sh-bar-fill { display: block; height: 100%; border-radius: 999px; background: linear-gradient(90deg, var(--orange), var(--yellow)); }
/* A finished list gets the green, so "everything is in the cart" reads without
   having to compare the two numbers above it. */
.sh-bar-fill.done { background: #4CAF50; }
.sh-empty, .sh-unpriced, .sh-nobudget { font-size: 12.5px; }
.sh-money { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; }
.sh-money-n { font-size: 14px; font-weight: 800; white-space: nowrap; }
.sh-money-n.over { color: #C62828; }
.sh-over-n { font-size: 11.5px; font-weight: 700; color: #C62828; white-space: nowrap; }
/* How much of the estimate is a real price. It is deliberately not muted into
   invisibility — it is the line that stops the money column reading as
   authoritative when it is a guess over two priced items. */
.sh-priced { font-style: italic; }
/* The unfiled-items note under the table. It is the sentence that stops the
   totals reading as all of the money, so it keeps its own space rather than
   being tucked against the pager. */
.sh-scope-note { margin-top: 14px; max-width: 76ch; }

/* ── Admin → Goals ──────────────────────────────────────────────────────
   Every household's SMART goals, cross-tenant. Reuses the catalogues'
   furniture wholesale — .admin-filters, .admin-search, .admin-table,
   .admin-pager for the shape, .ct-item/.ct-title/.ct-sub for the multi-line
   cells, .ct-fam-btn for the household and owner filters. Only what belongs to
   a goal is new: the progress bar, and the small vocabulary of states that
   change what the row MEANS (past its date, due this week, behind its own
   pace, achieved, archived). */
.gl-table td { vertical-align: top; }
/* A row whose own DELETE is in flight: dimmed with its buttons disabled, so a
   second click is an obvious mistake rather than a race. Same treatment as
   every other catalogue's row actions (.ct-table, .cal-table, .ml-table). */
.gl-table tr.is-busy { opacity: .55; }
.gl-head { margin-bottom: 12px; }
.gl-head-tags { display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap; }
/* The section's colour vocabulary, and it is deliberately small. Red is only
   ever "past its date"; amber is "coming up, nobody has done anything";
   green is achieved or on pace. Everything else stays neutral, so the two
   colours that mean act-on-this keep their meaning. */
.admin-tag.gl-overdue { background: rgba(229,57,53,0.12); border-color: rgba(229,57,53,0.28); color: #C62828; }
.admin-tag.gl-soon { background: rgba(247,201,72,0.20); border-color: rgba(247,201,72,0.45); color: #8A6D0B; }
.admin-tag.gl-behind { background: rgba(255,138,91,0.16); border-color: rgba(255,138,91,0.38); color: #B8501F; }
.admin-tag.gl-ontrack { background: rgba(76,175,80,0.14); border-color: rgba(76,175,80,0.30); color: #2E7D32; }
.admin-tag.gl-done-tag { background: rgba(76,175,80,0.14); border-color: rgba(76,175,80,0.30); color: #2E7D32; }
.admin-tag.gl-archived { background: var(--bg); color: var(--muted); }
.admin-pill.gl-done { background: rgba(76,175,80,0.14); color: #2E7D32; }
.admin-pill.gl-off { background: rgba(122,117,110,0.16); color: var(--muted); }
.gl-item { display: flex; align-items: flex-start; gap: 9px; min-width: 0; max-width: 320px; }
/* The emoji the family picked for the goal, at the size it wears on their own
   screen — it is how a parent recognises their goal on a call. */
.gl-ic { flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; border-radius: 9px; background: rgba(255,138,91,0.12); font-size: 16px; line-height: 1; }
.gl-flags { display: flex; align-items: center; gap: 5px; flex-wrap: wrap; }
.gl-measure { gap: 4px; }
.gl-measure-n { font-size: 13.5px; font-weight: 700; white-space: nowrap; }
.gl-untargeted, .gl-nodate { font-size: 12.5px; }
.gl-status { gap: 5px; }
.gl-driver { font-style: italic; }
/* "Nothing driving it" is a finding rather than a note — a goal with no chore
   pointed at it is a wish with a deadline — so it is legible rather than
   faded to the same weight as the neutral sub-lines around it. */
.gl-nodriver { font-style: normal; font-weight: 600; }
/* The progress column reads down rather than across, so it lines up right. */
.gl-th-num, .gl-td-num { text-align: right; }
.gl-prog { display: flex; flex-direction: column; align-items: flex-end; gap: 4px; min-width: 96px; }
.gl-prog-n { font-size: 14px; font-weight: 800; white-space: nowrap; }
.gl-bar { width: 100%; max-width: 110px; height: 5px; border-radius: 999px; background: var(--bg); overflow: hidden; }
.gl-bar-fill { display: block; height: 100%; border-radius: 999px; background: linear-gradient(90deg, var(--orange), var(--yellow)); }
/* An achieved goal gets the green, so "they got there" reads without having to
   check the status column beside it. */
.gl-bar-fill.done { background: #4CAF50; }
/* A goal nobody has ever logged against. Not muted into invisibility: it is the
   line that stops a percentage reading as activity. */
.gl-nocheck { font-weight: 600; }

/* ── Admin → Bills ──────────────────────────────────────────────────────
   Every household's bills and subscriptions, cross-tenant. Reuses the
   catalogues' furniture wholesale — .admin-filters, .admin-search,
   .admin-table, .admin-pager for the shape, .ct-item/.ct-title/.ct-sub for the
   multi-line cells, .ct-fam-btn for the household filter. Only what belongs to
   a bill is new: the money column, and the small vocabulary of states that
   change what the row MEANS (late, landing this week, paid, never once
   ticked). */
.bi-table td { vertical-align: top; }
.bi-head { margin-bottom: 12px; }
.bi-head-tags { display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap; }
/* The section's colour vocabulary, and it is deliberately small. Red is only
   ever "past its due day"; amber is "landing this week and nobody has ticked
   it"; green is paid. Autopay and Subscription stay cool and neutral because
   they describe the bill rather than demand anything, and everything else is
   plain — so the two colours that mean act-on-this keep their meaning. */
.admin-tag.bi-overdue { background: rgba(229,57,53,0.12); border-color: rgba(229,57,53,0.28); color: #C62828; }
.admin-tag.bi-soon { background: rgba(247,201,72,0.20); border-color: rgba(247,201,72,0.45); color: #8A6D0B; }
.admin-tag.bi-auto { background: rgba(76,175,80,0.12); border-color: rgba(76,175,80,0.28); color: #2E7D32; }
.admin-tag.bi-sub { background: rgba(255,138,91,0.14); border-color: rgba(255,138,91,0.34); color: #B8501F; }
.admin-tag.bi-archived { background: var(--bg); color: var(--muted); }
/* "Never ticked paid" is an adoption finding, not a debt, so it is legible
   without being alarming — amber's job here is to be countable at a glance. */
.admin-tag.bi-never-tag { background: rgba(247,201,72,0.16); border-color: rgba(247,201,72,0.38); color: #8A6D0B; }
.admin-pill.bi-paid { background: rgba(76,175,80,0.14); color: #2E7D32; }
.admin-pill.bi-unpaid { background: rgba(122,117,110,0.16); color: var(--muted); }
/* A deleted bill is still real data (DELETE only switches it off), so it is
   dimmed rather than hidden — and it comes back to full strength on hover,
   because a row an admin is actually reading should not fight them. */
.bi-row-off td { opacity: .68; }
.bi-row-off:hover td { opacity: 1; }
/* …except the Edit button, which is the ONLY way to put a deleted bill back —
   dimming the control that undoes the state is exactly backwards. */
.bi-row-off td.admin-td-actions { opacity: 1; }
.bi-item { display: flex; align-items: flex-start; gap: 9px; min-width: 0; max-width: 320px; }
/* The emoji the family picked for the bill, at the size it wears on their own
   screen — it is how a parent recognises their bill on a call. */
.bi-ic { flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; border-radius: 9px; background: rgba(255,138,91,0.12); font-size: 16px; line-height: 1; }
.bi-flags { display: flex; align-items: center; gap: 5px; flex-wrap: wrap; }
/* The family's own note on the bill — an account number, which card pays it.
   One line, because it is context rather than content, and the full text is on
   the title attribute. */
.bi-notes { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 260px; }
.bi-noday { font-size: 12.5px; }
.bi-cycle { gap: 5px; }
/* The money column reads down rather than across, so it lines up right. */
.bi-th-num, .bi-td-num { text-align: right; }
.bi-money { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; }
.bi-money-n { font-size: 14px; font-weight: 800; white-space: nowrap; }
.bi-rate, .bi-oneoff { white-space: nowrap; }
/* The three lines that stop a number reading as more than it is: the amount
   recorded at tick time when the price has since changed, the cycle a household
   is actually behind on, and the autopay caveat under an unpaid row. Italic
   rather than faded — they qualify the cell above them. */
.bi-drift, .bi-behind, .bi-autonote { font-style: italic; }
/* A bill nobody has ever ticked. Not muted into invisibility: it is the line
   that stops an empty payment history reading as a quiet month. */
.bi-never { font-weight: 600; }
/* The two caveats under the table. They keep their own space rather than being
   tucked against the pager, because both change what the whole screen means. */
.bi-scope-note { margin-top: 14px; max-width: 76ch; }

/* ── Admin → Meals ──────────────────────────────────────────────────────
   Every household's meal plan, cross-tenant. Reuses the catalogues' furniture
   wholesale — .admin-filters, .admin-search, .admin-table, .admin-pager for the
   shape, .ct-item/.ct-title/.ct-sub for the multi-line cells, .ct-cat for the
   slot, .ct-fam-btn for the household filter, and the calendar's own .ac-range
   date row so the two date-ranged sections work identically. Only what belongs
   to a meal is new: the day, and the ingredient count that decides whether the
   planner reaches the shopping list at all. */
.ml-table td { vertical-align: top; }
/* A row whose own DELETE is in flight: dimmed with its buttons disabled, so a
   second click is an obvious mistake rather than a race. Same treatment as
   every other catalogue's row actions (.ct-table, .cal-table, .sh-table). */
.ml-table tr.is-busy { opacity: .55; }
.ml-head { margin-bottom: 12px; }
.ml-head-tags { display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap; }
/* The section's colour vocabulary, and it is deliberately small — this screen
   has nothing anyone has to ACT on, so nothing is red. Amber is "no ingredients
   on it", which is the one finding worth counting at a glance; green is "still
   ahead of them"; the rest stay plain. */
.admin-tag.ml-noing-tag { background: rgba(247,201,72,0.20); border-color: rgba(247,201,72,0.45); color: #8A6D0B; }
.admin-tag.ml-ing-tag { background: rgba(255,138,91,0.12); border-color: rgba(255,138,91,0.32); color: #B8501F; }
.admin-tag.ml-soon { background: rgba(76,175,80,0.12); border-color: rgba(76,175,80,0.28); color: #2E7D32; }
.admin-tag.ml-orphan-tag { background: var(--bg); color: var(--muted); }
/* The planned day. Bold enough to scan a date-sorted page down the left edge,
   which is how this table is actually read. */
.ml-day { gap: 5px; }
.ml-day-main { font-size: 13.5px; font-weight: 700; white-space: nowrap; }
.ml-slot { white-space: nowrap; font-weight: 600; }
/* The family's own note on the meal — "double it, freeze half". One line,
   because it is context rather than content, and the full text is on the title
   attribute. */
.ml-notes { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 240px; }
/* The ingredient count reads down rather than across, so it lines up right with
   the other numeric columns on this surface. */
.ml-th-num, .ml-td-num { text-align: right; }
.ml-ing { display: inline-flex; align-items: center; justify-content: flex-end; gap: 6px; white-space: nowrap; }
.ml-ing-n { font-size: 14px; font-weight: 800; }
/* The trolley, at the size the family app puts it beside its own count — it is
   what makes the number read as "goes to the shopping list" rather than as an
   abstract quantity. Sized in em like every other .ui-ic on this surface. */
.ml-ing-ic { display: inline-flex; align-items: center; flex: none; font-size: 13px; color: var(--muted); }
/* A meal carrying nothing. Not muted into invisibility: it is the line that
   stops an empty count reading as a missing number, and it is the answer to
   "why did their groceries push add nothing?". */
.ml-noing { font-weight: 600; font-size: 12.5px; }
.ml-noauthor { font-size: 12.5px; }
/* The caveats under the table. They keep their own space rather than being
   tucked against the pager, because all three change what the screen means. */
.ml-scope-note { margin-top: 14px; max-width: 76ch; }

/* ── Admin → Points ─────────────────────────────────────────────────────
   The cross-tenant points ledger. It reuses the catalogues' furniture whole —
   .admin-filters, .ac-range, .admin-search, .admin-table, .admin-pager — and
   adds only what a LEDGER needs that a list doesn't: numbers that line up in a
   column, and one colour telling earned from spent. There are no row actions,
   because the ledger is append-only. */
.pt-table td { vertical-align: top; }
.pt-head { margin-bottom: 12px; }
.pt-head-tags { display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap; }
/* Green is money in, amber is money out. Deliberately NOT red: a debit is a kid
   spending what they earned, which is the feature working, not a problem. */
.admin-tag.pt-credit-tag { background: rgba(76,175,80,0.12); border-color: rgba(76,175,80,0.28); color: #2E7D32; }
.admin-tag.pt-debit-tag { background: rgba(247,201,72,0.20); border-color: rgba(247,201,72,0.45); color: #8A6D0B; }
/* A row a staff account wrote by hand. Plain, because it is a provenance note
   rather than a verdict — but present on every such row, since "who moved
   these points" is the question this screen exists to answer. */
.admin-tag.pt-admin-tag { background: var(--bg); color: var(--muted); }
.pt-by-admin { margin-top: 4px; }
/* The two numeric columns. Right-aligned and tabular so a run of entries can be
   checked down the page by eye — balance-after only means anything if the
   digits line up. */
.pt-th-num, .pt-num { text-align: right; }
.pt-num { font-variant-numeric: tabular-nums; white-space: nowrap; font-weight: 700; }
.pt-pos { color: #2E7D32; }
.pt-neg { color: #8A6D0B; }
/* The caveats under the table. They keep their own space rather than being
   tucked against the pager, because both change what the screen means. */
.pt-scope-note { margin-top: 14px; max-width: 76ch; }

/* ── Admin → Rooms ──────────────────────────────────────────────────────
   Every household's cleaning zones, cross-tenant. Reuses the catalogues'
   furniture wholesale — .admin-filters, .admin-search, .admin-table,
   .admin-pager for the shape, .ct-item/.ct-title/.ct-sub for the multi-line
   cells, .ct-pts for the points, .ct-fam-btn for the household filter,
   .admin-pill for live/archived. Only what belongs to a ROOM is new: the mark
   the household chose for it, what it is made of, and whether it has ever
   generated anything. */
.rm-table td { vertical-align: top; }
.rm-head { margin-bottom: 12px; }
.rm-head-tags { display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.rm-name { display: flex; align-items: center; gap: 7px; min-width: 0; }
/* The room's own mark, at reading size — an emoji for most of them (the room
   editor saves free text), a library icon for the rest, which is exactly what
   iconMarkup() decides between. */
.rm-ic { width: 1.2em; height: 1.2em; font-size: 16px; line-height: 1; flex: none; }
/* The section's colour vocabulary, and it is deliberately small — nothing on
   this screen has to be ACTED on, so nothing is red. Amber is the one finding
   worth counting at a glance ("no steps": the room generates nothing useful);
   orange is what the rooms have actually produced; the rest stay plain. */
.admin-tag.rm-nosteps-tag { background: rgba(247,201,72,0.20); border-color: rgba(247,201,72,0.45); color: #8A6D0B; }
.admin-tag.rm-unused-tag { background: rgba(247,201,72,0.20); border-color: rgba(247,201,72,0.45); color: #8A6D0B; }
.admin-tag.rm-gen-tag { background: rgba(255,138,91,0.12); border-color: rgba(255,138,91,0.32); color: #B8501F; }
.admin-tag.rm-archived-tag { background: var(--bg); color: var(--muted); }
.admin-tag.rm-orphan-tag { background: var(--bg); color: var(--muted); }
/* The two counted columns read down rather than across, so they line up right
   with the other numeric columns on this surface. */
.rm-th-num, .rm-td-num { text-align: right; }
.rm-steps { font-size: 14px; font-weight: 800; }
/* A room carrying nothing, and a room worth nothing. Neither is muted into
   invisibility: both are the line that stops an empty count reading as a
   missing number (admin-portal.md §D), and the first is the answer to "their
   generate button does nothing". */
.rm-nosteps, .rm-nopts, .rm-unused { font-weight: 600; font-size: 12.5px; }
.rm-noauthor { font-size: 12.5px; }
/* An archived room is still the household's data and is listed by default, so
   it is dimmed rather than hidden — enough to scan past, not enough to make the
   row it is on unreadable. */
.rm-table tr.rm-row-archived td { opacity: .72; }
/* …except the Edit button, which is the one thing on an archived row that has
   to be pressed — it is how the room gets put back. Dimming the control that
   undoes the state is the wrong way round. */
.rm-table tr.rm-row-archived td.admin-td-actions { opacity: 1; }
/* The caveats under the table. They keep their own space rather than being
   tucked against the pager, because all three change what the screen means. */
.rm-scope-note { margin-top: 14px; max-width: 78ch; }

/* ── Admin → Categories ─────────────────────────────────────────────────
   Every household's chore taxonomy, cross-tenant. Reuses the catalogues'
   furniture wholesale — .admin-filters, .admin-search, .admin-table,
   .admin-pager for the shape, .ct-item/.ct-title/.ct-sub for the multi-line
   cells, .ct-fam-btn for the household filter, .admin-pill for live/archived.
   Only what belongs to a CATEGORY is new: the mark and the colour the household
   chose for it, its place in their order, and whether anything is filed under
   it. */
.cg-table td { vertical-align: top; }
.cg-head { margin-bottom: 12px; }
.cg-head-tags { display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.cg-name { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; min-width: 0; }
/* The category's own mark, at reading size — an emoji for some (the picker
   allows free text), a library icon for the rest, which is exactly what
   iconMarkup() decides between. */
.cg-ic { width: 1.2em; height: 1.2em; font-size: 16px; line-height: 1; flex: none; }
/* The section's colour vocabulary, and it is deliberately small — nothing here
   has to be ACTED on, so nothing is red. Orange marks what a household BUILT
   (the adoption signal), amber the one finding worth counting at a glance (a
   label made and never used), and the rest stay plain. */
.admin-tag.cg-custom-tag { background: rgba(255,138,91,0.12); border-color: rgba(255,138,91,0.32); color: #B8501F; }
.admin-tag.cg-chores-tag { background: rgba(255,138,91,0.12); border-color: rgba(255,138,91,0.32); color: #B8501F; }
.admin-tag.cg-unused-tag { background: rgba(247,201,72,0.20); border-color: rgba(247,201,72,0.45); color: #8A6D0B; }
.admin-tag.cg-seeded-tag { background: var(--bg); color: var(--muted); }
.admin-tag.cg-archived-tag { background: var(--bg); color: var(--muted); }
.admin-tag.cg-orphan-tag { background: var(--bg); color: var(--muted); }
/* Seeded versus their own, on the row itself: the pair is the whole reason this
   screen can tell "the seeder ran" apart from "this household built a
   taxonomy", so both are stated rather than only the interesting one. */
.admin-pill.cg-seed-pill { background: var(--bg); color: var(--muted); }
.admin-pill.cg-own-pill { background: rgba(255,138,91,0.12); color: #B8501F; }
/* The order column reads down rather than across, so it lines up with the other
   numeric columns on this surface. */
.cg-th-num, .cg-td-num { text-align: right; }
.cg-pos { font-size: 14px; font-weight: 800; }
.cg-slug { font-size: 12.5px; color: var(--muted); overflow-wrap: anywhere; }
/* The colour as the household set it: the swatch is what makes a chip that
   reads badly on their own screen visible here, and the hex beside it is what
   makes it reportable. */
.cg-colour { display: inline-flex; align-items: center; gap: 7px; }
.cg-swatch { width: 16px; height: 16px; border-radius: 5px; border: 1px solid var(--border); flex: none; }
.cg-hex { font-size: 12.5px; color: var(--muted); }
.cg-colour-odd { font-size: 12.5px; font-weight: 600; overflow-wrap: anywhere; }
/* A category with no colour, and one with nothing filed under it. Neither is
   muted into invisibility: both are the line that stops an empty cell reading
   as a missing number (admin-portal.md §D). */
.cg-nocolour, .cg-unused, .cg-unused-seed { font-weight: 600; font-size: 12.5px; }
/* An archived category is still the household's data — the chores filed under
   it still point at it — so it is dimmed rather than hidden. */
.cg-table tr.cg-row-archived td { opacity: .72; }
/* The caveats under the table. They keep their own space rather than being
   tucked against the pager, because all three change what the screen means. */
.cg-scope-note { margin-top: 14px; max-width: 78ch; }

/* ── Admin → Google Calendar ────────────────────────────────────────────
   Every household's Google Calendar connection. Reuses the catalogues'
   furniture wholesale — .admin-filters, .admin-search, .admin-table,
   .admin-pager for the shape, .ct-item/.ct-title/.ct-sub for the multi-line
   cells, .ct-fam-btn for the household filter — so this screen reads as one of
   the lists rather than as a console. Only what belongs to an INTEGRATION is
   new: the account it was granted from, the credential's clock, and the error
   text verbatim.

   The colour vocabulary is the only place this section departs from the
   household screens, and deliberately: rows here CAN be broken, so red means
   broken, amber means it will break unless something happens, and the rest stay
   plain. Nothing else on this surface earns red. */
.gc-table td { vertical-align: top; }
.gc-head { margin-bottom: 12px; }
.gc-head-tags { display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap; }
/* The Google account, which is usually NOT the address that parent signs in
   with — long, and allowed to wrap rather than to widen the column. */
.gc-email { overflow-wrap: anywhere; }
/* The last import's write, and it reads down with the other numeric columns.
   The header needs the table's own specificity to beat `.admin-table th`. */
.gc-table th.gc-th-num, .gc-table td.gc-td-num { text-align: right; }
.gc-td-num .ct-item { align-items: flex-end; text-align: right; }
.gc-count { font-size: 14px; font-weight: 800; }
/* The status pills. Erroring is the only red on the screen, an expired token is
   amber because it usually renews itself, and healthy is the same green every
   other section uses for "nothing to do here". */
.admin-pill.gc-ok { background: rgba(76,175,80,0.14); color: #2E7D32; text-transform: none; }
.admin-pill.gc-warn { background: rgba(247,201,72,0.24); color: #8A6D0B; text-transform: none; }
.admin-pill.gc-bad { background: rgba(229,57,53,0.14); color: var(--red); text-transform: none; }
/* "Must reconnect" is the one state nothing can fix by itself, so it is the one
   tag that carries the red. Everything else is a finding: erroring may clear on
   the next sync, quiet is a household that stopped looking, no-longer-Premium is
   revenue rather than breakage, and an orphan is a grant with nobody behind it. */
.admin-tag.gc-dead-tag { background: rgba(229,57,53,0.12); border-color: rgba(229,57,53,0.28); color: #C62828; }
.admin-tag.gc-error-tag { background: rgba(255,138,91,0.14); border-color: rgba(255,138,91,0.34); color: #B8501F; }
.admin-tag.gc-never-tag { background: rgba(247,201,72,0.20); border-color: rgba(247,201,72,0.45); color: #8A6D0B; }
.admin-tag.gc-stale-tag { background: rgba(247,201,72,0.20); border-color: rgba(247,201,72,0.45); color: #8A6D0B; }
.admin-tag.gc-free-tag { background: rgba(156,39,176,0.12); border-color: rgba(156,39,176,0.30); color: var(--purple); }
.admin-tag.gc-orphan-tag { background: var(--bg); color: var(--muted); }
/* Google's message, verbatim. Monospaced and clamped to three lines: it is a
   string to be MATCHED against a parent's screenshot, not prose to be read, and
   a 500-character stack trace must not push the row off the screen. The whole
   message is on the cell's tooltip and in the CSV. */
.gc-err {
  margin-top: 4px; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11.5px; line-height: 1.45; color: #C62828; max-width: 340px;
  overflow-wrap: anywhere; display: -webkit-box; -webkit-line-clamp: 3;
  -webkit-box-orient: vertical; overflow: hidden;
}
/* A token already past its date. Amber rather than red on its own, because on
   most rows it is about to renew itself — the red is reserved for the tag under
   it that says nothing can. */
.gc-tok-past { color: #8A6D0B; }
/* The stated absences. None of them is muted into invisibility: each is the line
   that stops an empty cell reading as a missing number (admin-portal.md §D). */
.gc-never, .gc-noimport, .gc-notoken, .gc-noemail, .gc-noactor { font-weight: 600; font-size: 12.5px; }
/* A broken row is tinted rather than badged twice — the pill already says which
   kind, and the tint is what makes the shape of the list readable at a glance. */
.gc-table tr.gc-row-bad td { background: rgba(229,57,53,0.045); }
.gc-table tr.gc-row-warn td { background: rgba(247,201,72,0.07); }
/* A row whose own disconnect is in flight. Same fade the other action tables
   use, so "this one is working" reads identically wherever an admin meets it. */
.gc-table tr.is-busy { opacity: .55; }
/* The caveats under the table. They keep their own space rather than being
   tucked against the pager, because every one of them changes what the numbers
   above them mean. */
.gc-scope-note { margin-top: 14px; max-width: 80ch; }

/* ── Admin → CRM ────────────────────────────────────────────────
   The contact desk. The status pill is the row's whole meaning, so the palette
   is the pipeline: neutral for untouched, amber for waiting on us, green for
   qualified and converted, muted red for ruled out. `crm-unknown` exists so a
   status written by something other than this portal still renders as itself
   rather than being quietly filed under one we recognise. */
/* A search box sitting under a chip row needs the gap the section head would
   otherwise have given it. Shared by CRM's status chips and Families' plan
   chips, so the two lists space identically. */
.crm-search, .fam-search { margin-top: 14px; }
.crm-table td { vertical-align: top; }
.crm-cell { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; min-width: 0; }
.crm-sub { font-size: 12px; }
.crm-acc-pills { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.admin-pill.crm-new { background: rgba(122,117,110,0.16); color: var(--muted); }
.admin-pill.crm-contacted { background: rgba(247,201,72,0.24); color: #8A6D0B; }
.admin-pill.crm-qualified { background: rgba(76,175,80,0.14); color: #2E7D32; }
.admin-pill.crm-nurturing { background: rgba(156,39,176,0.13); color: var(--purple); }
.admin-pill.crm-converted { background: rgba(76,175,80,0.22); color: #1B5E20; }
.admin-pill.crm-cold { background: rgba(229,57,53,0.12); color: var(--red); }
.admin-pill.crm-unknown { background: var(--bg); color: var(--muted); border: 1px dashed var(--border); }

/* Owner and follow-up — the two columns that make this a chase list rather than
   a record. `Unassigned` is deliberately quiet: it is the common case on a new
   signup, and colouring every fresh row as a problem would drown the one thing
   here that IS one. */
.crm-owner { font-weight: 600; overflow-wrap: anywhere; }
.crm-unassigned { font-style: italic; }
.admin-pill.crm-owner-pill { background: rgba(46,196,182,0.16); color: #14746C; }
/* The follow-up palette is a deadline, not a pipeline: red only once the day has
   actually passed, amber on the day itself, neutral while there is still time.
   A date that is merely in the future is not news. */
.admin-pill.crm-due-overdue { background: rgba(229,57,53,0.14); color: var(--red); font-weight: 800; }
.admin-pill.crm-due-today { background: rgba(247,201,72,0.28); color: #8A6D0B; }
.admin-pill.crm-due-soon { background: rgba(122,117,110,0.14); color: var(--text); }

/* The contact panel: the current status above the control that moves it, the
   two composers, and the note history. */
.crm-status-now { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 14px; }
.crm-field { display: block; margin-top: 12px; }
.crm-textarea { font-size: 13.5px; padding: 10px 12px; min-height: 0; line-height: 1.5; }
/* The date and its one-line reason sit at the same weight as the status select
   they sit under, so the three controls read as one desk. */
.crm-field input[type="date"], .crm-follow-note { padding: 8px 10px; font-size: 13px; font-weight: 600; }
.crm-field input:disabled { opacity: .6; cursor: not-allowed; }
.crm-extid { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; overflow-wrap: anywhere; }
.crm-fams { margin-top: 10px; }
/* Newest first — a note history read top-down is a list of things nobody has
   done anything about since. */
.crm-notes { list-style: none; margin: 16px 0 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.crm-note { background: var(--bg); border: 1px solid var(--border); border-radius: 10px; padding: 10px 12px; }
.crm-note-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; font-size: 11.5px; }
.crm-note-by { font-weight: 700; overflow-wrap: anywhere; }
.crm-note-at { color: var(--muted); white-space: nowrap; flex-shrink: 0; }
/* Notes are typed prose: line breaks are meaningful and a pasted URL must not
   push the panel sideways. */
.crm-note-text { font-size: 13.5px; line-height: 1.55; margin-top: 5px; white-space: pre-wrap; overflow-wrap: anywhere; }

/* ── Admin → Chore library ──────────────────────────────────────────────
   The platform's own chore catalogue — the global task_template rows every
   household's picker offers. Reuses the catalogues' furniture wholesale
   (.admin-filters, .admin-search, .admin-table, .admin-pager for the shape,
   .ct-item/.ct-title/.ct-sub for the multi-line cells, .ct-cat for the category
   chip, .ct-acts for the row buttons), so only what belongs to a LIBRARY is
   new: what an entry ships with, and whether anyone has ever picked it. */
.cl-table td { vertical-align: top; }
.cl-head { margin-bottom: 12px; }
.cl-name { display: flex; align-items: center; gap: 7px; min-width: 0; }
/* The entry's own mark, at reading size — an emoji for most of them, an app
   icon for the rest, which is exactly what iconMarkup() decides between. */
.cl-ic { width: 1.2em; height: 1.2em; font-size: 16px; line-height: 1; flex: none; }
.cl-tags { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
/* The category chip is a filter, so it has to read as pressable rather than as
   a label — .ct-cat gives it the shape, this gives it the affordance. */
.cl-cat-btn { border: 0; padding: 0; background: none; color: inherit; font: inherit; font-size: 13px; text-align: left; cursor: pointer; }
.cl-cat-btn:hover { color: var(--orange); }
.cl-cat-btn:focus-visible { outline: 2px solid var(--orange); outline-offset: 2px; border-radius: 6px; }
/* The colour vocabulary, deliberately small. Amber is "the next deploy has an
   opinion about this row" (a seeded name); the checklist chip is plain, because
   having steps is neither good nor bad. */
.admin-tag.cl-seeded { background: rgba(247,201,72,0.20); border-color: rgba(247,201,72,0.45); color: #8A6D0B; }
.admin-tag.cl-checklist { background: var(--bg); color: var(--muted); }
.admin-tag.cl-use-tag { background: rgba(255,138,91,0.12); border-color: rgba(255,138,91,0.32); color: #B8501F; }
/* An entry nobody has ever picked. Not muted into invisibility: it is the whole
   finding this screen exists for, and a bare 0 in a column of counts would read
   as a missing number rather than as an answer (admin-portal.md §D). */
.cl-unused { font-weight: 600; font-size: 12.5px; }
/* The caveats under the table. They keep their own space rather than being
   tucked against the pager, because both change what the adoption column means. */
.cl-scope-note { margin-top: 14px; max-width: 78ch; }
/* The add/edit dialog is a real form rather than a one-field confirm, so it is
   given the width three controls need to sit on a line. */
.cl-form { max-width: 620px; }
.cl-form-row { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px; }
/* The icon box holds one emoji — a third of a row is furniture, not a field. */
.cl-field-icon { max-width: 120px; }
/* The one warning that has to be read BEFORE the rename rather than after it:
   the next deploy re-creates a seeded name that has been freed. */
.cl-seeded-warn { margin-top: 8px; color: #8A6D0B; font-weight: 600; }
/* The row whose PATCH is in flight reads as busy, so a second click on a
   neighbouring row is an obvious mistake rather than a race. */
.cl-table tr.is-busy { opacity: .55; }

/* ── Admin → Templates ──────────────────────────────────────────
   The shared-routine moderation desk. The three states are exclusive, so the
   status pill carries the whole meaning of a row and is coloured accordingly:
   green is live to strangers, grey is the author's own copy only, red is
   retired everywhere. */
.tpl-filters { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin: 0 0 14px; }
.tpl-sort { display: inline-flex; align-items: center; gap: 8px; margin: 0; }
.tpl-sort-lbl { font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); font-weight: 700; }
.tpl-sort select { width: auto; min-width: 178px; padding: 9px 12px; font-size: 13px; font-weight: 600; margin: 0; }

.tpl-table td { vertical-align: top; }
.tpl-cell { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; min-width: 0; }
.tpl-main { max-width: 340px; }
.tpl-sub { font-size: 12px; }
.tpl-name { display: flex; align-items: center; gap: 7px; min-width: 0; }
.tpl-ic { font-size: 15px; line-height: 1; }
.tpl-tags { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.tpl-copy { color: var(--purple); border-color: rgba(156,39,176,0.35); }
.tpl-items { font-style: italic; overflow-wrap: anywhere; }
.admin-pill.tpl-published { background: rgba(76,175,80,0.14); color: #2E7D32; }
.admin-pill.tpl-unpublished { background: rgba(122,117,110,0.16); color: var(--muted); }
.admin-pill.tpl-deactivated { background: rgba(229,57,53,0.14); color: var(--red); }
/* Stacked, not inline: two takedowns side by side in a narrow column is how a
   Deactivate gets clicked when an Unpublish was meant. */
.tpl-acts { display: flex; flex-direction: column; align-items: stretch; gap: 6px; min-width: 116px; }
.tpl-acts .btn { white-space: nowrap; }
/* The row whose action is in flight reads as busy, so a second click on a
   neighbouring row is an obvious mistake rather than a race. */
.tpl-table tr.is-busy { opacity: .55; }

/* ── Admin → Revenue ────────────────────────────────────────────
   Five stat cards over the two lists that are actual work. The cards
   deliberately reuse the Overview tile's visual language (same surface, same
   uppercase label, same 30px figure) but are plain sections rather than
   buttons — on the section that OWNS these numbers there is nowhere to drill
   into, and a card that looks clickable and isn't is worse than a flat one. */
.rv-rows { display: inline-flex; align-items: center; gap: 8px; margin: 0; }
.rv-rows-lbl { font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); font-weight: 700; white-space: nowrap; }
.rv-rows select { width: auto; min-width: 72px; padding: 9px 12px; font-size: 13px; font-weight: 600; margin: 0; }
/* Sandbox has to be legible at a glance from beside the as-of stamp — it is the
   difference between reading these figures as revenue and as test data. */
.rv-env { text-transform: uppercase; color: #8A6D0B; background: rgba(247,201,72,0.24); border-color: rgba(247,201,72,0.55); font-size: 10.5px; padding: 3px 8px; }
/* Five controls share this row — as-of stamp, environment tag, list size,
   Export CSV and Refresh — which is more than a narrow desktop fits beside a
   heading, so this one head-actions row wraps rather than squeezing the title
   out of the way. It stays right-aligned so the wrapped line reads as part of
   the same group. */
#admin-rv-actions { flex-wrap: wrap; justify-content: flex-end; row-gap: 8px; }

.rv-banner { display: flex; align-items: flex-start; gap: 12px; background: var(--surface); border: 1px solid var(--border); border-left: 4px solid var(--yellow); border-radius: var(--radius); box-shadow: var(--shadow); padding: 14px 18px; margin: 0 0 18px; }
.rv-banner.is-off { border-left-color: var(--red); }
.rv-banner-ic { display: inline-flex; flex-shrink: 0; color: var(--yellow); margin-top: 1px; }
.rv-banner-ic svg { width: 22px; height: 22px; }
.rv-banner.is-off .rv-banner-ic { color: var(--red); }
.rv-banner-title { font-size: 15.5px; font-weight: 800; letter-spacing: -0.2px; }
.rv-banner-sub { font-size: 12.5px; color: var(--muted); line-height: 1.5; margin-top: 3px; max-width: 74ch; }

.rv-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 14px; }
.rv-stat { display: flex; flex-direction: column; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow); padding: 16px 18px 14px; }
.rv-stat-head { display: flex; align-items: center; gap: 8px; }
.rv-stat-ic { display: inline-flex; color: var(--orange); flex-shrink: 0; }
.rv-stat-ic svg { width: 18px; height: 18px; }
.rv-stat-label { font-size: 12px; font-weight: 800; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); }
/* Tabular figures so a column of money lines up on the decimal point. */
.rv-stat-value { font-size: 30px; font-weight: 800; letter-spacing: -1px; line-height: 1.15; margin-top: 10px; font-variant-numeric: tabular-nums; }
.rv-stat-caption { font-size: 12.5px; color: var(--muted); }
.rv-stat-delta { margin-top: 8px; }
.rv-stat-rows { display: flex; flex-direction: column; gap: 6px; margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border); }
.rv-stat-row { display: flex; align-items: baseline; gap: 8px; font-size: 13px; }
.rv-stat-row-l { color: var(--muted); flex: 1; min-width: 0; }
.rv-stat-row-v { font-weight: 700; font-variant-numeric: tabular-nums; }
.rv-stat-note { font-size: 11.5px; color: var(--muted); line-height: 1.45; margin-top: 10px; }

.rv-list { margin-top: 22px; }
.rv-list-head { margin-bottom: 12px; }
.rv-list-title { font-size: 15.5px; font-weight: 800; letter-spacing: -0.2px; }
.rv-list-sub { font-size: 12.5px; color: var(--muted); line-height: 1.5; margin-top: 3px; max-width: 74ch; }
.rv-empty { padding: 32px 24px; }

.rv-table td { vertical-align: top; }
.rv-when { white-space: nowrap; }
.rv-when-sub { font-size: 12px; }
.rv-th-amt, .rv-td-amt { text-align: right; white-space: nowrap; }
.rv-amt { font-weight: 800; font-variant-numeric: tabular-nums; color: #2E7D32; }
/* Money leaving — a refund or an uncollected balance. Not red: it is a normal
   thing that happened, not an error state. */
.rv-amt.is-out { color: var(--orange); }
.rv-tries { font-weight: 700; font-variant-numeric: tabular-nums; }
.rv-subid { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11.5px; color: var(--muted); background: var(--bg); border: 1px solid var(--border); border-radius: 5px; padding: 2px 6px; display: inline-block; max-width: 150px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: middle; }
.rv-sub-fam { font-size: 12px; color: var(--muted); margin-top: 2px; }
.rv-unmatched { font-style: italic; }

/* Transaction kind and subscription state. Green is money in and a live
   subscription; orange is money out; red is a subscription that has stopped. */
.admin-pill.rv-kind-payment { background: rgba(76,175,80,0.14); color: #2E7D32; }
.admin-pill.rv-kind-refund { background: rgba(255,107,53,0.14); color: var(--orange); }
.admin-pill.rv-kind-failed { background: rgba(229,57,53,0.14); color: var(--red); }
.admin-pill.rv-active { background: rgba(76,175,80,0.14); color: #2E7D32; }
.admin-pill.rv-cancelled { background: rgba(122,117,110,0.16); color: var(--muted); }
.admin-pill.rv-past_due { background: rgba(247,201,72,0.24); color: #8A6D0B; }
.admin-pill.rv-suspended { background: rgba(229,57,53,0.14); color: var(--red); }
.admin-pill.rv-none { background: var(--bg); color: var(--muted); }
.admin-pill.rv-recovered { background: rgba(76,175,80,0.14); color: #2E7D32; }
.admin-pill.rv-chase { background: rgba(229,57,53,0.14); color: var(--red); }
/* A row that fixed itself recedes, so the ones still needing a person stand out
   without the list hiding what already recovered. */
.rv-fail-table tr.is-recovered { opacity: .62; }

/* The per-subscriber Cancel/Refund column. Stacked like the templates takedown
   buttons so the two never sit at different widths, and the disabled state is
   readable rather than invisible — a greyed button whose title says WHY is the
   whole point of showing it at all. */
.rv-acts { display: flex; flex-direction: column; align-items: stretch; gap: 6px; min-width: 104px; }
.rv-acts .btn { white-space: nowrap; }
.rv-acts .btn:disabled { opacity: .5; cursor: not-allowed; }

/* What the reminder for THIS row did, said in the row it was fired from. A
   chase is read down a list one subscriber at a time, so "did this one get an
   email?" has to be answerable where the button is — the notice at the top of
   the screen can only ever say it about the last one. */
.rv-act-msg { font-size: 11.5px; line-height: 1.4; max-width: 22ch; }
.rv-act-msg.is-ok { color: var(--green, #2E7D32); font-weight: 700; }
.rv-act-msg.is-err { color: var(--red); }

/* What the last cancel or refund did, kept on screen until dismissed. Green
   when it is finished, amber when the money moved but something is still
   outstanding, red when nothing happened — and its own grey-red for "PayPal was
   never configured", which is a deploy fact rather than a failure to retry. */
.rv-notice { display: flex; align-items: flex-start; gap: 12px; background: var(--surface); border: 1px solid var(--border); border-left: 4px solid var(--green, #2E7D32); border-radius: var(--radius); box-shadow: var(--shadow); padding: 14px 16px; margin: 0 0 18px; }
.rv-notice.is-warn { border-left-color: var(--yellow); }
.rv-notice.is-err { border-left-color: var(--red); }
.rv-notice.is-off { border-left-color: var(--muted); }
.rv-notice-main { flex: 1; min-width: 0; }
.rv-notice-title { font-size: 14.5px; font-weight: 800; letter-spacing: -0.2px; }
.rv-notice-line { font-size: 12.5px; color: var(--muted); line-height: 1.5; margin-top: 3px; max-width: 74ch; }
.rv-notice-x { background: none; border: none; color: var(--muted); font-size: 20px; line-height: 1; cursor: pointer; padding: 0 2px; }
.rv-notice-x:hover { color: var(--text); }

/* A choice inside a confirm dialog — "revoke Premium now", "also cancel the
   subscription". The box keeps its own line beside multi-line copy. */
.rv-confirm-opt { display: flex; align-items: flex-start; gap: 8px; margin-top: 12px; font-size: 12.5px; line-height: 1.45; cursor: pointer; }
.rv-confirm-opt input { width: auto; margin: 2px 0 0; flex-shrink: 0; }

/* The "is the webhook arriving?" footer. Deliberately quiet — it is the line
   that turns an empty screen from ambiguous into diagnosed. */
.rv-ledger { display: flex; align-items: baseline; gap: 8px 14px; flex-wrap: wrap; margin-top: 20px; padding: 12px 16px; background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius); font-size: 12.5px; color: var(--muted); }
.rv-ledger-h { font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: .06em; }
.rv-ledger-i strong { color: var(--text); font-weight: 800; }
.rv-ledger-i.is-warn, .rv-ledger-i.is-warn strong { color: var(--red); }

/* ── Admin → Revenue → Subscribers (the payer directory) ────────────────
   Reuses the generic list furniture — .admin-filters, .admin-search,
   .admin-table, .admin-pager — so the directory reads as the same kind of
   screen as Users and Families. Only the two annotations that belong to money
   are new: an account still being billed behind a locked door, and a
   subscription id that survives only in the ledger. */
.rv-subs-intro { max-width: 82ch; margin: 0 0 16px; }
.rv-subs-table .admin-uc-email { word-break: break-word; }
/* Money leaving through a door nobody can walk through — the row this screen
   exists to surface, so it is red rather than muted. */
.rv-subs-warn { font-size: 11.5px; font-weight: 700; color: var(--red); margin-top: 4px; }
.rv-subs-former { font-size: 11.5px; color: var(--muted); margin-top: 4px; font-style: italic; }

/* ── Admin → Analytics ──────────────────────────────────────────
   A filter row above stat cards, a funnel, two small-multiple charts and the
   source table the screen exists for. The two series colours are darker steps
   of the app's teal and orange, picked so each clears 3:1 against the white
   card AND the pair stays separable under deuteranopia — validated, not
   eyeballed. Visitors and dollars never share an axis: they are two charts. */
.an-range { display: inline-flex; align-items: center; gap: 8px; margin: 0; }
.an-range-lbl { font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); font-weight: 700; white-space: nowrap; }
.an-range select, .an-range input { width: auto; min-width: 76px; padding: 9px 12px; font-size: 13px; font-weight: 600; margin: 0; }
.an-custom input { width: 84px; }

/* Refetch holds the previous render, dimmed — no skeleton flash, no jump. */
#admin-an-body.is-loading { opacity: .55; transition: opacity .15s; pointer-events: none; }

.an-rangebar { display: flex; align-items: baseline; gap: 8px 14px; flex-wrap: wrap; margin: 0 0 16px; padding: 11px 16px; background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius); font-size: 12.5px; color: var(--muted); }
.an-rangebar-h { font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: .06em; }
.an-rangebar-i strong { color: var(--text); font-weight: 800; }
.an-rangebar-i.is-muted { font-style: italic; }

/* The totals. Plain sections rather than buttons — this is the screen that owns
   these numbers, so there is nowhere to drill into. */
.an-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 14px; }
.an-stat { display: flex; flex-direction: column; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow); padding: 16px 18px 14px; }
.an-stat-head { display: flex; align-items: center; gap: 8px; }
.an-stat-ic { display: inline-flex; color: var(--orange); flex-shrink: 0; }
.an-stat-ic svg { width: 18px; height: 18px; }
.an-stat-label { font-size: 12px; font-weight: 800; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); }
/* Proportional figures: a standalone 30px number set in tabular digits reads
   loose. Columns of numbers get tabular-nums; headline values don't. */
.an-stat-value { font-size: 30px; font-weight: 800; letter-spacing: -1px; line-height: 1.15; margin-top: 10px; }
.an-stat-caption { font-size: 12.5px; color: var(--muted); }
.an-stat-delta { margin-top: 8px; }

/* Visitors → signups → buyers, with the drop-off between them. */
.an-funnel { display: flex; align-items: center; gap: 10px 16px; flex-wrap: wrap; margin-top: 16px; padding: 14px 18px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow); }
.an-funnel-step { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.an-funnel-n { font-size: 22px; font-weight: 800; letter-spacing: -0.5px; line-height: 1.1; }
.an-funnel-l { font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); }
.an-funnel-r { font-size: 12.5px; color: var(--muted); }
.an-funnel-r.is-muted { font-style: italic; }
.an-funnel-arrow { display: inline-flex; color: var(--border); flex-shrink: 0; }
.an-funnel-arrow svg { width: 18px; height: 18px; }

.an-block { margin-top: 22px; }
.an-block-head { margin-bottom: 12px; }
.an-block-title { font-size: 15.5px; font-weight: 800; letter-spacing: -0.2px; }
.an-block-sub { font-size: 12.5px; color: var(--muted); line-height: 1.5; margin-top: 3px; max-width: 78ch; }
.an-empty { padding: 28px 24px; }
.an-empty code, .admin-empty-sub code, .an-block-sub code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .92em; background: var(--bg); border: 1px solid var(--border); border-radius: 5px; padding: 0 4px; }

/* The trend: two charts in one card, one shared tooltip. */
.an-trend { position: relative; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow); padding: 16px 18px 14px; }
.an-chart { margin: 14px 0 0; }
.an-chart:first-of-type { margin-top: 6px; }
.an-chart-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; flex-wrap: wrap; margin-bottom: 8px; }
/* Text never wears the data colour — the small key beside it carries identity. */
.an-chart-title { display: inline-flex; align-items: center; gap: 8px; font-size: 13px; font-weight: 700; }
.an-key { width: 14px; height: 3px; border-radius: 2px; background: var(--an-series); flex-shrink: 0; }
.an-chart-peak { font-size: 12.5px; color: var(--muted); }
.an-chart-peak strong { color: var(--text); font-weight: 800; }
.an-chart-peak.is-muted { font-style: italic; }

.an-plot { position: relative; height: 108px; padding-left: 46px; border-bottom: 1px solid var(--border); }
/* Hairline, solid, one step off the surface — never dashed, never heavy. */
.an-plot::before { content: ""; position: absolute; left: 46px; right: 0; top: 0; border-top: 1px solid var(--border); }
.an-ytick { position: absolute; left: 0; width: 42px; text-align: right; font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; }
.an-ytick.is-top { top: -6px; }
.an-ytick.is-zero { bottom: -6px; }
.an-svg { position: absolute; left: 46px; right: 0; top: 0; bottom: 0; width: calc(100% - 46px); height: 100%; }

/* One band per day: the hover target everywhere, and the column itself on the
   bar form. The band is the hit area, so it is always the full plot height. */
.an-bands { position: absolute; left: 46px; right: 0; top: 0; bottom: 0; display: flex; align-items: flex-end; gap: 2px; }
/* On the line form the bands are pure hit area over the plot, so they run edge
   to edge — a gap there would be a dead zone the pointer falls into. */
.an-plot.is-line .an-bands { gap: 0; }
/* Every band is a LINK into that one day, so it is a real button — the reset
   below is what a button costs; the affordance, the focus ring and the keyboard
   activation are what it buys. */
.an-band { flex: 1 1 0; min-width: 0; height: 100%; display: flex; align-items: flex-end; justify-content: center; border-radius: 3px 3px 0 0; padding: 0; border: 0; background: none; font: inherit; color: inherit; cursor: pointer; -webkit-appearance: none; appearance: none; }
.an-band.is-hover { background: rgba(45,42,38,0.06); }
.an-band:focus-visible { outline: 2px solid var(--orange); outline-offset: 1px; }
/* The hour columns inside the day view go nowhere — a day is the finest window
   this screen has — so they are bands without the pointer's promise. */
.an-band-static { cursor: default; }
/* Capped so a 7-day chart is columns, not slabs — the band's leftover is air. */
.an-bar { display: block; width: 100%; max-width: 24px; background: var(--an-series); border-radius: 4px 4px 0 0; }

.an-xaxis { display: flex; justify-content: space-between; gap: 8px; margin: 6px 0 0 46px; font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; }

/* One tooltip for both charts — pointing at a day reads every series at once. */
.an-tip { position: absolute; z-index: 5; pointer-events: none; background: var(--surface); border: 1px solid var(--border); border-radius: 10px; box-shadow: var(--shadow-lg); padding: 8px 11px; min-width: 132px; }
.an-tip[hidden] { display: none; }
.an-tip-day { font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); margin-bottom: 5px; }
.an-tip-row { display: flex; align-items: baseline; gap: 7px; font-size: 13px; }
.an-tip-row + .an-tip-row { margin-top: 3px; }
.an-tip-key { width: 12px; height: 3px; border-radius: 2px; flex-shrink: 0; align-self: center; }
.an-tip-row strong { font-weight: 800; font-variant-numeric: tabular-nums; }
.an-tip-l { color: var(--muted); font-size: 12px; }

/* The chart's table twin. Every value the hover shows is reachable without a
   pointer, which is what keeps the trend usable on a keyboard. */
.an-daily { margin-top: 16px; border-top: 1px solid var(--border); padding-top: 12px; }
.an-daily-summary { font-size: 12.5px; font-weight: 700; color: var(--muted); cursor: pointer; }
.an-daily-summary:hover { color: var(--orange); }
.an-daily-summary:focus-visible { outline: 2px solid var(--orange); outline-offset: 3px; border-radius: 4px; }
.an-daily-body { margin-top: 12px; }
.an-daily-table { max-height: none; }

.an-th-n, .an-td-n { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }
.an-src { font-weight: 700; }
.an-money { font-weight: 800; color: #2E7D32; }
.an-src-table tfoot td, .an-ev-table tfoot td { padding: 14px 16px; border-top: 1px solid var(--border); background: var(--bg); font-weight: 700; }

/* Where they are: one rollup at a time, picked here. Buttons rather than tabs —
   they re-sort a table in place, they don't navigate. */
.an-geo-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px 16px; flex-wrap: wrap; }
.an-geo-head .btn { flex-shrink: 0; display: inline-flex; align-items: center; gap: 6px; }
.an-geo-head .btn .ui-ic { width: 16px; height: 16px; }
.an-geo-levels { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 12px; }
.an-geo-lvl { display: inline-flex; align-items: baseline; gap: 7px; padding: 7px 12px; border: 1px solid var(--border); border-radius: 999px; background: var(--surface); font: inherit; font-size: 12.5px; font-weight: 700; color: var(--muted); cursor: pointer; }
.an-geo-lvl:hover:not(:disabled) { color: var(--text); border-color: var(--muted); }
.an-geo-lvl:focus-visible { outline: 2px solid var(--orange); outline-offset: 2px; }
.an-geo-lvl.is-on { background: var(--text); border-color: var(--text); color: var(--surface); }
.an-geo-lvl:disabled { opacity: .5; cursor: not-allowed; }
.an-geo-lvl-n { font-size: 11.5px; font-weight: 800; font-variant-numeric: tabular-nums; opacity: .75; }
.an-geo-lvl-n.is-none { opacity: .55; }

.an-geo-th-rank, .an-geo-td-rank { width: 1%; text-align: right; color: var(--muted); font-variant-numeric: tabular-nums; font-size: 12.5px; }
.an-geo-place { font-weight: 700; }
/* A row whose location the beacon couldn't resolve is not a place — it reads as
   the absence it is rather than sitting in the list like a country. */
.an-geo-place.is-none { font-weight: 600; font-style: italic; color: var(--muted); }
/* Magnitude, so one hue: the same teal the visitors chart is drawn in, because
   it is the same measure. The percentage beside it is the label — the bar is
   there to be scanned down a column, not read off. */
.an-geo-th-share { width: 26%; }
/* The flex lives on a span inside the cell, never on the <td> itself — a cell
   that stops being a table-cell stops lining up with its column. */
.an-geo-share { display: flex; align-items: center; gap: 9px; }
.an-geo-meter { flex: 1 1 auto; min-width: 40px; height: 6px; border-radius: 999px; background: var(--bg); overflow: hidden; }
.an-geo-meter-fill { display: block; height: 100%; border-radius: 999px; background: #12968A; }
.an-geo-pct { font-size: 12.5px; font-variant-numeric: tabular-nums; color: var(--muted); flex-shrink: 0; min-width: 44px; text-align: right; }
.an-geo-table tfoot td { padding: 14px 16px; border-top: 1px solid var(--border); background: var(--bg); font-weight: 700; }
.an-geo-more { margin-top: 12px; }

/* ── Analytics → one day ────────────────────────────────────────────────────
   The day behind a bar. Same furniture as the range screen — the same stat
   cards, the same chart parts, the same rollup table — because it is the same
   data over a shorter window, and a day that looked like a different product
   would make the drill-down feel like leaving the screen. */
#admin-anday-body.is-loading { opacity: .55; transition: opacity .15s; pointer-events: none; }

/* The way back to the range, above the day's own title: the arrow points back,
   so it is rotated rather than given a second icon. */
.an-day-back { display: inline-flex; align-items: center; gap: 5px; margin-bottom: 8px; padding: 0; border: 0; background: none; font: inherit; font-size: 12.5px; font-weight: 700; color: var(--muted); cursor: pointer; }
.an-day-back:hover { color: var(--orange); }
.an-day-back:focus-visible { outline: 2px solid var(--orange); outline-offset: 3px; border-radius: 4px; }
.an-day-back svg { width: 15px; height: 15px; transform: rotate(180deg); }

.an-day-steps { display: inline-flex; gap: 6px; }
.an-day-note { display: block; padding: 12px 14px; border: 1px solid var(--border); border-left: 3px solid var(--red); border-radius: var(--radius); background: var(--surface); margin-bottom: 16px; }
.an-day-note strong { display: block; color: var(--text); margin-bottom: 3px; }

.an-day-neighbours { display: inline-flex; gap: 10px; flex-wrap: wrap; }
.an-day-link, .an-daylink { padding: 0; border: 0; background: none; font: inherit; font-weight: 700; color: var(--orange); cursor: pointer; text-align: left; }
.an-day-link:hover, .an-daylink:hover { text-decoration: underline; }
.an-day-link:focus-visible, .an-daylink:focus-visible { outline: 2px solid var(--orange); outline-offset: 2px; border-radius: 4px; }

.an-day-chart { margin-top: 4px; }
/* What the day was made of, above two thousand rows of it. */
.an-day-kinds { display: flex; flex-wrap: wrap; gap: 6px 8px; margin-bottom: 12px; }
.an-day-kind { display: inline-flex; align-items: baseline; gap: 5px; padding: 5px 10px; border: 1px solid var(--border); border-radius: 999px; background: var(--surface); font-size: 12.5px; color: var(--muted); }
.an-day-kind strong { font-weight: 800; color: var(--text); font-variant-numeric: tabular-nums; }
.an-day-kind.is-total { background: var(--bg); }

/* An anonymous browser id and a URL path are both machine strings: monospaced,
   so two of them can be compared down a column, and clipped rather than allowed
   to set the table's width. */
.an-day-id, .an-day-path { display: inline-block; max-width: 30ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: bottom; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; }
.an-day-table td { vertical-align: middle; }

/* The Overview tile's 14-day strip. Spans only — the tile is a <button>, so a
   <div> in here would be reparented straight out of it by the parser. */
.an-spark-wrap { gap: 6px; }
.an-spark { display: flex; align-items: flex-end; gap: 2px; height: 30px; }
.an-spark-b { display: block; flex: 1 1 0; min-width: 0; background: var(--teal); border-radius: 2px 2px 0 0; }
.an-spark-l { font-size: 11.5px; color: var(--muted); }

/* ── Admin → AI usage ───────────────────────────────────────────────────────
   What the Anthropic calls cost. The furniture is deliberately the Analytics
   screen's — the same stat cards, the same chart parts, the same share meter —
   because it is the same shape of question over a shorter, cheaper series, and
   a cost screen that looked like a different product would read as a different
   source. Only the things that are genuinely new to this screen live here.

   The series hue is the Analytics REVENUE orange, not its visitors teal: one hue
   per MEASURE across the portal, and this measure is dollars. */

/* Refetch holds the previous render, dimmed — no skeleton flash, no jump. */
#admin-ai-body.is-loading { opacity: .55; transition: opacity .15s; pointer-events: none; }

.ai-filters { display: flex; flex-direction: column; gap: 10px; margin: 0 0 18px; }
.ai-filter-row { display: flex; align-items: center; gap: 8px 10px; flex-wrap: wrap; }
.ai-filter-lbl { font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); font-weight: 700; white-space: nowrap; }
.ai-filters .em-select { min-width: 150px; }
.ai-clear { margin-left: auto; }

/* The drilled-into day wears the chip shape but is not a chip you can press —
   it is a statement of where you are, carrying its own way out. */
.ai-daychip { padding-right: 7px; }
.ai-daychip-utc { font-size: 10.5px; font-weight: 800; letter-spacing: .04em; opacity: .7; }
.ai-daychip-x { display: inline-flex; align-items: center; justify-content: center; width: 18px; height: 18px; padding: 0; border: 0; border-radius: 999px; background: none; font: inherit; font-size: 12px; line-height: 1; color: inherit; cursor: pointer; }
.ai-daychip-x:hover { background: rgba(255,107,53,0.18); }
.ai-daychip-x:focus-visible { outline: 2px solid var(--orange); outline-offset: 1px; }

.ai-stats { margin-bottom: 4px; }
/* The supporting pair under each headline number. Columns of figures get
   tabular digits; the headline above them deliberately does not. */
.ai-stat-rows { margin-top: 12px; border-top: 1px solid var(--border); padding-top: 8px; }
.ai-stat-row { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; font-size: 12.5px; }
.ai-stat-row + .ai-stat-row { margin-top: 4px; }
.ai-stat-row-l { color: var(--muted); }
.ai-stat-row-v { font-weight: 800; font-variant-numeric: tabular-nums; }

/* Money reads as money wherever it appears on this screen, and always in
   tabular digits — these columns are meant to be summed by eye. */
.ai-money { font-weight: 800; color: #B23E12; font-variant-numeric: tabular-nums; white-space: nowrap; }

/* Three cuts of one window, side by side on a wide screen and stacked below it.
   They are the same table three times, so they share every rule. */
.ai-breakdowns { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 16px; }
.ai-bd { min-width: 0; }
.ai-bd-title { font-size: 13.5px; font-weight: 800; letter-spacing: -0.1px; }
.ai-bd-sub { font-size: 12.5px; color: var(--muted); line-height: 1.45; margin: 3px 0 10px; }
.ai-bd-table td { padding: 11px 14px; }
.ai-bd-table thead th { padding: 10px 14px; }
/* The row you are currently filtered to, so the screen says which of its own
   rows narrowed it rather than leaving the picker above as the only clue. */
.ai-bd-table tbody tr.is-on { background: rgba(255,107,53,0.08); }
.ai-bd-table tbody tr.is-on .ai-bd-link { text-decoration: underline; }
.ai-bd-link { font-size: 13.5px; overflow-wrap: anywhere; }
.ai-bd-failed { font-size: 11.5px; color: var(--red); margin-top: 2px; }
.ai-bd-sub-n { font-size: 11.5px; color: var(--muted); font-weight: 600; margin-top: 2px; }
/* Share is of COST, so it takes the money hue — except in the one window where
   nothing could be priced and the bar falls back to counting calls, which is
   the magnitude the Analytics teal already means. */
.ai-bd .an-geo-meter-fill { background: #D14A16; }
.ai-bd .an-geo-meter-fill.is-calls { background: #12968A; }

.ai-calls td { vertical-align: top; }
.ai-calls tbody tr.is-failed td { background: rgba(229,57,53,0.04); }
.ai-when { font-size: 13.5px; }
.ai-sub { font-size: 11.5px; margin-top: 2px; }
.ai-err { font-size: 11.5px; color: var(--red); margin-top: 4px; max-width: 34ch; overflow-wrap: anywhere; }
.admin-pill.ai-ok { background: rgba(76,175,80,0.14); color: #2E7D32; }
.admin-pill.ai-failed { background: rgba(229,57,53,0.14); color: var(--red); }

/* The price list the dollars were worked out from — folded away, because it is
   the answer to "is this number right?", not to "what did we spend?". */
.ai-pricing { margin-top: 22px; border-top: 1px solid var(--border); padding-top: 12px; }

/* ── Admin → Bugs ───────────────────────────────────────────────────────────
   This product's own error reports. The list is a stack of expandable rows
   rather than a table: an error's detail is a multi-line blob (a stack trace, an
   nginx line), which a table cell either clips into uselessness or lets set the
   width of every column beside it. */
#admin-bug-body.is-loading { opacity: .55; transition: opacity .15s; pointer-events: none; }
.bug-stats { margin-bottom: 16px; }
.bug-counts-note { grid-column: 1 / -1; margin-top: 0; }

/* The open/archive switch, in the head actions beside Refresh. A segmented pair
   rather than two chips, because the two are exclusive views of one thing. */
.bug-views { display: inline-flex; border: 1.5px solid var(--border); border-radius: 999px; overflow: hidden; background: var(--surface); }
.bug-view { padding: 6px 14px; font-size: 12.5px; font-weight: 700; color: var(--muted); background: none; white-space: nowrap; }
.bug-view + .bug-view { border-left: 1.5px solid var(--border); }
.bug-view:hover:not(:disabled) { color: var(--text); }
.bug-view.is-on { background: rgba(255,107,53,0.1); color: var(--orange); }
.bug-view:disabled { opacity: .6; cursor: not-allowed; }
.bug-view:focus-visible { outline: 2px solid var(--orange); outline-offset: -2px; }

/* Search on the left, Export CSV on the right, above the source chips. */
.bug-filters { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin: 0 0 14px; }
.bug-filters .em-search { flex: 0 1 340px; }

.bug-chips { margin-bottom: 12px; }
.bug-noise-note { margin: 0 0 12px; max-width: 90ch; }
/* A button that reads as a link — used where the sentence itself is the control
   ("…add ?include=noise to see them. Show them"). */
.admin-linkbtn { background: none; padding: 0; font: inherit; font-weight: 700; color: var(--orange); text-decoration: underline; text-underline-offset: 3px; }
.admin-linkbtn:disabled { opacity: .5; cursor: not-allowed; }
.admin-linkbtn:focus-visible { outline: 2px solid var(--orange); outline-offset: 2px; }

.bug-listhead { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; flex-wrap: wrap; font-size: 12.5px; font-weight: 600; color: var(--muted); margin-bottom: 8px; }
.bug-list { display: flex; flex-direction: column; gap: 10px; }
.bug-row { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow); overflow: hidden; }
.bug-row.is-open { border-color: color-mix(in srgb, var(--orange) 45%, var(--border)); }
.bug-row.is-busy { opacity: .6; }
.bug-row-main { display: flex; align-items: flex-start; gap: 12px; padding: 12px 14px; }
.bug-row-toggle { flex: 1; min-width: 0; display: flex; align-items: flex-start; gap: 10px; background: none; padding: 0; text-align: left; }
.bug-row-caret { flex-shrink: 0; width: 14px; font-size: 11px; line-height: 1.5; color: var(--muted); }
.bug-row-body { min-width: 0; flex: 1; display: block; }
/* Two lines of the message, then an ellipsis — the whole thing is one click
   away, and a 4kB nginx line as a row header buries every row under it. */
.bug-row-summary { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; font-size: 13.5px; font-weight: 600; line-height: 1.45; word-break: break-word; }
.bug-row-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 6px; font-size: 11.5px; color: var(--muted); }
.admin-pill.bug-src { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 10.5px; background: var(--bg); color: var(--muted); border: 1px solid var(--border); }
.bug-count { font-weight: 800; color: var(--red); }
.bug-when { white-space: nowrap; }
.bug-resolve { flex-shrink: 0; }

.bug-detail { border-top: 1px solid var(--border); background: var(--bg); padding: 12px 14px 14px; }
.bug-facts { display: flex; flex-wrap: wrap; gap: 8px 26px; margin: 0 0 10px; }
.bug-fact dt { font-size: 10.5px; font-weight: 800; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); }
.bug-fact dd { margin: 1px 0 0; font-size: 12.5px; font-weight: 600; }
/* The blob is preformatted but must not widen the panel, and it scrolls inside
   its own surface rather than handing the card a system scrollbar. */
.bug-blob { margin: 0; max-height: 340px; overflow: auto; overscroll-behavior: contain; background: var(--surface); border: 1px solid var(--border); border-radius: 9px; padding: 10px 12px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11.5px; line-height: 1.55; white-space: pre-wrap; word-break: break-word; }
@supports (scrollbar-width: thin) {
  .bug-blob { scrollbar-width: thin; scrollbar-color: var(--border) transparent; }
}
.bug-blob::-webkit-scrollbar { width: 8px; height: 8px; }
.bug-blob::-webkit-scrollbar-track { background: transparent; }
.bug-blob::-webkit-scrollbar-thumb { background: var(--border); border-radius: 999px; border: 2px solid transparent; background-clip: content-box; }
.bug-detail-note { font-size: 11.5px; color: var(--muted); line-height: 1.5; margin: 8px 0 0; }

/* ── Admin → Backlog ────────────────────────────────────────────
   An editable list of open work, grouped by the file's own headings. Completed
   items appear behind the toggle in a visibly read-only style — the difference
   between "you can change this" and "this is history" has to be obvious at a
   glance, not discovered by clicking. */
.bl-toggle { display: inline-flex; align-items: center; gap: 7px; font-size: 13px; font-weight: 600; color: var(--muted); cursor: pointer; user-select: none; white-space: nowrap; }
.bl-toggle input { width: 15px; height: 15px; accent-color: var(--orange); cursor: pointer; margin: 0; }
.bl-toggle input:disabled { cursor: not-allowed; }

/* The auto-runner switch. Sits above everything else on the screen because it
   is the one control that decides whether any of the work below happens, and it
   is colour-coded on/off so its state reads from across a room. */
.bl-runner { display: flex; align-items: flex-start; gap: 14px; background: var(--surface); border: 1px solid var(--border); border-left: 4px solid var(--green, #4CAF50); border-radius: var(--radius); box-shadow: var(--shadow); padding: 14px 18px; margin: 0 0 16px; }
.bl-runner.is-off { border-left-color: var(--red); }
.bl-runner.is-default { border-left-color: var(--border); }
.bl-runner-text { min-width: 0; flex: 1; }
.bl-runner-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.bl-runner-label { font-size: 15.5px; font-weight: 800; letter-spacing: -0.2px; }
.bl-runner-state { font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; padding: 2px 8px; border-radius: 999px; background: rgba(76,175,80,0.14); color: #2E7D32; white-space: nowrap; }
.bl-runner.is-off .bl-runner-state { background: rgba(229,57,53,0.12); color: var(--red); }
.bl-runner.is-default .bl-runner-state { background: var(--bg); color: var(--muted); }
.bl-runner-sub { font-size: 12.5px; color: var(--muted); line-height: 1.5; margin-top: 3px; max-width: 66ch; }
.bl-runner-sub code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11.5px; background: var(--bg); border: 1px solid var(--border); border-radius: 5px; padding: 1px 4px; }
.bl-runner-err { margin-top: 6px; font-size: 12.5px; font-weight: 600; color: var(--red); }
.bl-switch { position: relative; flex-shrink: 0; width: 46px; height: 26px; margin-top: 2px; padding: 0; border-radius: 999px; background: var(--border); transition: background .15s ease; }
.bl-runner.is-on .bl-switch { background: var(--green, #4CAF50); }
.bl-runner.is-default .bl-switch { background: rgba(122,117,110,0.35); }
.bl-switch:disabled { opacity: .5; cursor: not-allowed; }
.bl-switch:focus-visible { outline: 2px solid var(--orange); outline-offset: 2px; }
.bl-switch-knob { position: absolute; top: 3px; left: 3px; width: 20px; height: 20px; border-radius: 50%; background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.25); transition: transform .15s ease; }
.bl-runner.is-on .bl-switch-knob { transform: translateX(20px); }

/* Today's request allowance, sharing the runner's card because it qualifies the
   switch beside it: how many more builds flipping this on can actually spend
   (admin-portal.md §A.10). Green when the project is exempt, red when a kind is
   spent, and deliberately muted in the "not connected" / "couldn't read" states
   so an unwired panel never reads like an allowance of zero. */
.bl-allow { flex-shrink: 0; align-self: stretch; display: flex; flex-direction: column; gap: 8px; padding-left: 18px; border-left: 1px solid var(--border); max-width: 340px; }
.bl-allow-h { font-size: 10.5px; font-weight: 800; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); }
.bl-allow-kinds { display: flex; gap: 20px; flex-wrap: wrap; }
.bl-allow-kind { display: flex; flex-direction: column; min-width: 104px; }
.bl-allow-v { font-size: 18px; font-weight: 800; letter-spacing: -0.3px; white-space: nowrap; }
.bl-allow-kind.is-unlimited .bl-allow-v { color: #2E7D32; }
.bl-allow-kind.is-zero .bl-allow-v { color: var(--red); }
.bl-allow-l { font-size: 12px; font-weight: 600; margin-top: 1px; }
.bl-allow-s { font-size: 11.5px; color: var(--muted); line-height: 1.4; margin-top: 2px; }
.bl-allow-kind.is-zero .bl-allow-s { color: var(--red); font-weight: 600; }
.bl-allow-note { font-size: 11.5px; color: var(--muted); line-height: 1.45; }
.bl-allow.is-bad .bl-allow-note { color: var(--red); }
/* The divider carries the state too, so "nothing left to spend today" reads from
   the same distance the runner's own on/off stripe does. */
.bl-allow.is-zero { border-left-color: var(--red); }
.bl-allow.is-unlimited { border-left-color: rgba(76,175,80,0.5); }

@media (max-width: 860px) {
  /* Under the switch rather than beside it, full width — the numbers are the
     point, and a 100px column squeezed against the runner copy isn't readable. */
  .bl-runner { flex-wrap: wrap; }
  .bl-allow { max-width: none; width: 100%; padding-left: 0; padding-top: 12px; border-left: none; border-top: 1px solid var(--border); }
  .bl-allow.is-zero { border-top-color: var(--red); }
  .bl-allow.is-unlimited { border-top-color: rgba(76,175,80,0.5); }
}

.bl-meta { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; flex-wrap: wrap; font-size: 12.5px; color: var(--muted); margin: -8px 0 16px; }
.bl-meta-counts strong { color: var(--text); font-weight: 800; }
.bl-banner { margin-bottom: 16px; }

.bl-groups { display: flex; flex-direction: column; gap: 22px; }
.bl-group-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 8px; }
.bl-group-title { font-size: 13px; font-weight: 800; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); }
.bl-group-count { font-size: 12px; color: var(--muted); white-space: nowrap; }
.bl-list { list-style: none; margin: 0; padding: 0; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow); overflow: hidden; }
.bl-list:empty { display: none; }
.bl-list-done { margin-top: 10px; box-shadow: none; background: var(--bg); }
.bl-none { padding: 16px 18px; font-size: 13px; color: var(--muted); }

.bl-item { display: flex; align-items: flex-start; gap: 12px; padding: 12px 14px; border-bottom: 1px solid var(--border); }
.bl-list li:last-child { border-bottom: none; }
.bl-item.is-editing { display: block; padding: 12px 14px; }
.bl-item-main { flex: 1; min-width: 0; }
.bl-text { font-size: 14px; line-height: 1.5; overflow-wrap: anywhere; }
.bl-text code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12.5px; background: var(--bg); border: 1px solid var(--border); border-radius: 5px; padding: 1px 5px; }
.bl-item.is-ticked .bl-text { text-decoration: line-through; color: var(--muted); }
.bl-item.is-new { background: rgba(255,107,53,0.05); }

.bl-flags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.bl-flags:empty { display: none; }
.bl-flag { display: inline-flex; align-items: center; font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; padding: 2px 8px; border-radius: 999px; background: var(--bg); color: var(--muted); white-space: nowrap; }
.bl-flag.flight { background: rgba(255,107,53,0.12); color: var(--orange); }
.bl-flag.done { background: rgba(76,175,80,0.14); color: #2E7D32; }
.bl-flag.new { background: rgba(255,107,53,0.14); color: var(--orange); }
.bl-flag.edited { background: rgba(122,117,110,0.16); color: var(--muted); }
.bl-flag.skipped { background: rgba(229,57,53,0.12); color: var(--red); }

/* The tick target. A completed item's marker is a <span>, not a button, so the
   read-only rule is visible in the cursor as well as in the styling. */
.bl-check { flex-shrink: 0; width: 20px; height: 20px; margin-top: 1px; border: 1.5px solid var(--border); border-radius: 6px; background: var(--surface); color: transparent; display: inline-flex; align-items: center; justify-content: center; padding: 0; }
.bl-check:hover { border-color: var(--orange); }
.bl-check svg { width: 14px; height: 14px; }
.bl-check.is-on { background: var(--green, #4CAF50); border-color: var(--green, #4CAF50); color: #fff; }
.bl-check-static { cursor: default; opacity: .75; }
.bl-check-static.is-skipped { background: rgba(229,57,53,0.14); border-color: rgba(229,57,53,0.3); color: var(--red); font-size: 13px; font-weight: 800; }
.bl-item-done { padding: 10px 14px; }
.bl-item-done .bl-text { color: var(--muted); font-size: 13px; }

.bl-item-actions { display: flex; align-items: center; gap: 2px; flex-shrink: 0; }
.bl-ic-btn { display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 30px; border-radius: 8px; background: none; color: var(--muted); padding: 0; }
.bl-ic-btn:hover:not(:disabled) { background: var(--bg); color: var(--text); }
.bl-ic-btn:disabled { opacity: .3; cursor: not-allowed; }
.bl-ic-btn svg { width: 16px; height: 16px; }
.bl-ic-danger:hover:not(:disabled) { color: var(--red); }

.bl-compose { display: flex; flex-direction: column; gap: 10px; }
.bl-input { width: 100%; padding: 10px 12px; border: 1.5px solid var(--border); border-radius: 10px; font-family: inherit; font-size: 14px; line-height: 1.5; color: var(--text); background: var(--surface); resize: vertical; }
.bl-input:focus { outline: none; border-color: var(--orange); box-shadow: 0 0 0 3px rgba(255,107,53,0.15); }
.bl-compose-btns { display: flex; align-items: center; justify-content: flex-end; gap: 8px; flex-wrap: wrap; }
.bl-compose-hint { flex: 1; min-width: 160px; font-size: 12px; color: var(--muted); }
.bl-add { display: inline-flex; align-items: center; gap: 6px; margin-top: 8px; padding: 8px 12px; border-radius: 9px; background: none; font-size: 13px; font-weight: 700; color: var(--muted); }
.bl-add:hover { background: var(--bg); color: var(--orange); }
.bl-add svg { width: 15px; height: 15px; }

/* Roadmap — a whole-file markdown editor over the real roadmap.md. Monospace
   and no soft-wrap, because the file's own line breaks are what the owner is
   editing and a rewrapped view hides them. */
.rm-editor-wrap { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow); overflow: hidden; }
.rm-editor { display: block; width: 100%; min-height: 58vh; padding: 16px 18px; border: none; background: none; color: var(--text); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 13px; line-height: 1.65; tab-size: 2; resize: vertical; overflow: auto; white-space: pre; }
.rm-editor:focus { outline: none; }
.rm-editor-wrap:focus-within { border-color: var(--orange); box-shadow: 0 0 0 3px rgba(255,107,53,0.15); }
.rm-editor[readonly] { color: var(--muted); cursor: default; }
.rm-hint { margin-top: 10px; font-size: 12.5px; color: var(--muted); }
.rm-hint code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11.5px; background: var(--bg); border: 1px solid var(--border); border-radius: 5px; padding: 1px 4px; }

/* Below 1024px: sidebar becomes an off-canvas drawer; table becomes cards */
@media (max-width: 1023px) {
  .admin-sidebar { position: fixed; top: 0; left: 0; z-index: 60; transform: translateX(-100%); transition: transform .25s ease; box-shadow: var(--shadow-lg); }
  .admin.drawer-open .admin-sidebar { transform: translateX(0); }
  .admin.drawer-open .admin-scrim { display: block; position: fixed; inset: 0; background: rgba(0,0,0,0.4); z-index: 50; }
  .admin-hamburger { display: inline-flex; }
  .admin-main { padding: 16px; }

  /* Overview stacks in priority order — needs-attention, then the tiles — never
     a horizontal scroller of cards. */
  .admin-tiles { grid-template-columns: 1fr; }
  .admin-attn { flex-direction: column; align-items: stretch; }
  .admin-attn-main { flex-wrap: wrap; }
  .admin-attn-delta { margin-left: 0; align-self: flex-start; width: 100%; }
  .admin-attn-items { width: 100%; }
  .admin-asof { white-space: normal; }

  /* The feed sits below the tiles on every width; on a narrow screen the event
     title is given the whole row and the "when" drops under it, rather than the
     two of them competing for one line. */
  .admin-feed-row { align-items: flex-start; flex-wrap: wrap; }
  .admin-feed-ev { white-space: normal; max-width: none; }
  .admin-feed-when { width: 100%; order: 3; }
  .admin-feed-go { display: none; }

  .admin-table thead { display: none; }
  .admin-tablewrap { border: none; border-radius: 0; background: none; box-shadow: none; overflow: visible; }
  .admin-table, .admin-table tbody, .admin-table tfoot, .admin-table tr, .admin-table td { display: block; width: 100%; }
  .admin-table tr { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); margin-bottom: 12px; box-shadow: var(--shadow); overflow: hidden; }
  .admin-table td { display: flex; justify-content: space-between; align-items: center; gap: 16px; padding: 11px 14px; border-bottom: 1px solid var(--border); }
  .admin-table tr td:last-child { border-bottom: none; }
  .admin-table td::before { content: attr(data-label); font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); font-weight: 700; flex-shrink: 0; }
  .admin-table td.admin-td-user { justify-content: flex-start; }
  .admin-table td.admin-td-user::before { display: none; }
  .admin-table td[data-label="Families"] { align-items: flex-start; }
  .admin-table td[data-label="Families"] .admin-fams { justify-content: flex-end; }
  .admin-uc-email { max-width: 60vw; }
  .admin-td-actions { width: auto; }
  /* Families: the value stack sits on the right of its label, reading right-to
     left like every other card row here. The name cell keeps the left edge. */
  .afm-cell { text-align: right; }
  .admin-table td[data-label="Family"] .afm-cell { text-align: left; }
  .admin-table td[data-label="Family"]::before { display: none; }

  /* The slide-over becomes a near-full-screen sheet, and its two-column grids
     collapse to one so nothing gets squeezed. */
  .admin-panel { width: 100%; }
  .admin-panel-head .admin-uc-email { max-width: 55vw; }
  .admin-facts { grid-template-columns: 1fr; gap: 10px; }
  .admin-fam-card-fields { grid-template-columns: 1fr; }

  /* List filters: one per line with the select taking the width its label
     leaves, rather than three dropdowns squeezed to nothing across a phone. */
  .admin-filters { flex-direction: column; align-items: stretch; gap: 10px; }
  .admin-filter { justify-content: space-between; }
  .admin-filter select { flex: 1; min-width: 0; margin-left: 12px; }

  /* CRM: the two-line cells read right-to-left against their own label, like
     every other card row here, and the contact cell keeps the left edge. The
     note timestamp drops under the author rather than squeezing it. */
  .crm-cell { align-items: flex-end; text-align: right; }
  .admin-table td[data-label="Account"],
  .admin-table td[data-label="Owner"],
  .admin-table td[data-label="Follow-up"],
  .admin-table td[data-label="Notes"] { align-items: flex-start; }
  .crm-note-head { flex-wrap: wrap; }
  .crm-note-at { white-space: normal; }

  /* Email: filters stack, and the search box takes the full width rather than
     being squeezed to nothing beside the purpose select. The status cell's note
     needs the left edge, so it opts out of the card layout's right-alignment. */
  .em-filters, .em-filter-right { flex-direction: column; align-items: stretch; }
  .em-select, .em-search { width: 100%; flex: none; min-width: 0; }
  .em-test-fields { grid-template-columns: 1fr; }
  .admin-table td[data-label="Status"] { align-items: flex-start; }
  .em-note { text-align: right; max-width: 62vw; }

  /* Email templates: the head actions drop under the blurb, the two-line cells
     keep the left edge, and the test row stacks so the address field isn't
     squeezed to nothing beside its button. */
  .em-viewhead { flex-direction: column; align-items: stretch; }
  .admin-table td[data-label="Template"], .admin-table td[data-label="Used for"], .admin-table td[data-label="What’s sent"] { align-items: flex-start; }
  .et-editorhead { align-items: flex-start; }
  .et-testrow { flex-direction: column; align-items: stretch; }
  .et-btns .btn { flex: 1; }

  /* SMS: the filters stack like Email's (they are the same components), and the
     three multi-line cells keep the left edge for their own content while the
     stack itself reads right-to-left with the rest of the card. A message keeps
     left-aligned text inside its bubble — it is prose, not a value. */
  .admin-table td[data-label="Message"],
  .admin-table td[data-label="Reply"],
  .admin-table td[data-label="Member / household"],
  .admin-table td[data-label="From"] { align-items: flex-start; }
  .sm-msg { align-items: flex-end; max-width: 66vw; }
  .sm-bubble { text-align: left; }
  .sm-status { align-self: flex-end; }
  .sm-who { align-items: flex-end; text-align: right; }
  .sm-who-top { justify-content: flex-end; }
  .sm-when { text-align: right; white-space: normal; }
  .sm-num { text-align: right; }
  .sm-err { text-align: right; max-width: 62vw; }

  /* SMS templates: the copy cells keep the left edge (they are prose, and the
     Message card row is already left-aligned by the log's rule above), and the
     preview bubble takes the width it is given rather than a 34ch column that
     would leave half a phone screen empty. */
  .admin-table td[data-label="Sent when"] { align-items: flex-start; }
  .st-excerpt, .st-where { max-width: 62vw; }
  .st-bubble { max-width: 100%; }

  /* Notifications: the notification itself keeps the LEFT edge — its title and
     message are prose, and a wrapped sentence read right-to-left is unreadable
     — and so does the recipient, which is the row's identity. Everything else
     stays right, which is where it already is on the desktop table. Scoped to
     .nt-table rather than to .admin-table: "Household" and "Read" are labels
     other sections use too, and an unscoped rule here would reach into theirs. */
  .nt-table td[data-label="Notification"] { align-items: flex-start; }
  .nt-table td[data-label="Notification"] .ct-item { align-items: flex-start; text-align: left; max-width: none; }
  .nt-table td[data-label="Recipient"] { align-items: flex-start; }
  .nt-table td[data-label="Recipient"] .admin-uc { text-align: left; }
  .nt-table td[data-label="Sent"],
  .nt-table td[data-label="Household"],
  .nt-table td[data-label="Read"] { align-items: flex-end; }
  .nt-table td[data-label="Household"] .ct-item,
  .nt-table td[data-label="Read"] .ct-item { align-items: flex-end; text-align: right; }
  .nt-table td[data-label="Sent"] .ct-when { text-align: right; white-space: normal; }
  /* The unread marker moves to the top of the card: on a phone the row is a
     stack, so a bar down the first cell marks one line rather than the row. */
  .nt-table tr.nt-row-unread td:first-child { box-shadow: none; }
  .nt-table tr.nt-row-unread { box-shadow: inset 3px 0 0 rgba(247,201,72,0.75), var(--shadow); }

  /* Activity: the date range and the search box each take a full width rather
     than sharing one squeezed line, and the two multi-line cells (the event and
     the person) keep the left edge instead of being pushed right. */
  .ac-filters { flex-direction: column; align-items: stretch; }
  .ac-range { align-items: stretch; }
  .ac-range-dates { display: grid; grid-template-columns: auto 1fr; align-items: center; gap: 8px 10px; }
  .ac-date { width: 100%; min-width: 0; }
  .admin-table td[data-label="Event"], .admin-table td[data-label="Who"], .admin-table td[data-label="When"] { align-items: flex-start; }
  .admin-table td[data-label="Who"] .admin-uc { text-align: left; }
  .ac-ev-top { justify-content: flex-end; }
  .ac-ev-title { text-align: right; }
  .ac-when { text-align: right; white-space: normal; }
  .ac-fam { align-items: flex-end; }

  /* Pending queue: the sort select and the search box each take a full line,
     and the two multi-line cells (the item and the person) keep the left edge
     instead of being pushed right. The waiting time is the row's headline, so
     it stays first in the card. */
  .ap-filters { flex-direction: column; align-items: stretch; }
  .ap-sort { justify-content: space-between; }
  .ap-sort select { flex: 1; min-width: 0; }
  .admin-table td[data-label="What"] { align-items: flex-start; }
  .ap-item { align-items: flex-end; text-align: right; max-width: 62vw; }
  .ap-when { text-align: right; white-space: normal; }
  /* The two decisions go side by side because the card gives them the width the
     desktop column didn't. */
  .ap-acts { flex-direction: row; justify-content: flex-end; flex-wrap: wrap; min-width: 0; }

  /* Chore catalogue: the same treatment — the multi-line cells stack against
     the right of their label, and the assignee card keeps the left edge so the
     avatar sits beside the name rather than under it. */
  .admin-table td[data-label="Chore"] { align-items: flex-start; }
  .ct-item, .ct-fam { align-items: flex-end; text-align: right; max-width: 62vw; }
  .ct-when { text-align: right; white-space: normal; }
  .admin-table td[data-label="Assigned to"] .admin-uc { text-align: left; }
  .ct-acts { flex-direction: row; justify-content: flex-end; flex-wrap: wrap; min-width: 0; }

  /* Reward catalogue: the same treatment as the chore catalogue — the
     multi-line cells stack against the right of their label, and the reward's
     icon keeps the left edge so it sits beside the name rather than under it. */
  .admin-table td[data-label="Reward"] { align-items: flex-start; }
  .rc-item { max-width: 62vw; }
  .rc-unlock { text-align: right; white-space: normal; }
  .admin-table td[data-label="Earned by"] { align-items: flex-end; }

  /* Calendar: the same treatment. The when-cell and the entry keep the LEFT
     edge — a date read right-aligned under its own label reads as two columns
     of numbers, and the colour swatch has to stay beside the title rather than
     wrap under it. Everything else stacks against the right like the rest. */
  .admin-table td[data-label="When"],
  .admin-table td[data-label="Entry"] { align-items: flex-start; }
  .cal-item { max-width: 62vw; }
  /* The swatch is on the left, so the title beside it stays left too — the
     generic right-align would leave the dot orphaned across a gap. */
  .cal-item .ct-item { align-items: flex-start; text-align: left; }
  .cal-when { text-align: left; }
  .admin-table td[data-label="For"] { align-items: flex-end; }

  /* Shopping: the list keeps the LEFT edge — the emoji has to stay beside the
     name rather than wrap under it — and the three numeric cells stay right,
     which is where they already are on the desktop table. */
  .admin-table td[data-label="List"] { align-items: flex-start; }
  .sh-item { max-width: 62vw; }
  .sh-item .ct-item { align-items: flex-start; text-align: left; }
  .admin-table td[data-label="Store"],
  .admin-table td[data-label="In the cart"],
  .admin-table td[data-label="Budget"],
  .admin-table td[data-label="Estimated"] { align-items: flex-end; }
  .sh-stores { align-items: flex-end; text-align: right; }
  .sh-stops { justify-content: flex-end; }

  /* Goals: the goal keeps the LEFT edge — the emoji has to stay beside the
     title rather than wrap under it — and every other cell stays right, which
     is where it already is on the desktop table. Scoped to .gl-table rather
     than to .admin-table: "Owner" and "Status" are labels other sections use
     too, and an unscoped rule here would reach into CRM's table. */
  .gl-table td[data-label="Goal"] { align-items: flex-start; }
  .gl-item { max-width: 62vw; }
  .gl-item .ct-item { align-items: flex-start; text-align: left; }
  .gl-table td[data-label="Owner"],
  .gl-table td[data-label="Measure"],
  .gl-table td[data-label="Progress"],
  .gl-table td[data-label="Deadline"],
  .gl-table td[data-label="Status"],
  .gl-table td[data-label="Actions"] { align-items: flex-end; }
  /* .gl-measure/.gl-status/.gl-prog need nothing more: the shared mobile rule a
     few blocks up already right-aligns .ct-item, which is what they are. */

  /* Bills: the bill keeps the LEFT edge — the emoji has to stay beside the name
     rather than wrap under it — and every other cell stays right, which is
     where it already is on the desktop table. Scoped to .bi-table rather than
     to .admin-table: "Household" and "Amount" are labels other sections use
     too, and an unscoped rule here would reach into theirs. */
  .bi-table td[data-label="Bill"] { align-items: flex-start; }
  .bi-item { max-width: 62vw; }
  .bi-item .ct-item { align-items: flex-start; text-align: left; }
  .bi-notes { max-width: 58vw; }
  .bi-table td[data-label="Household"],
  .bi-table td[data-label="Amount"],
  .bi-table td[data-label="Due"],
  .bi-table td[data-label="This cycle"],
  .bi-table td[data-label="Last payment"],
  .bi-table td[data-label="Actions"] { align-items: flex-end; }

  /* Meals: the day and the meal itself keep the LEFT edge — they are what the
     card is ABOUT, and a right-aligned title under a right-aligned date reads
     as a column of fragments — and everything else stays right, which is where
     it already is on the desktop table. Scoped to .ml-table rather than to
     .admin-table: "Household" and "Added by" are labels other sections use too,
     and an unscoped rule here would reach into theirs. */
  .ml-table td[data-label="Day"],
  .ml-table td[data-label="Planned"] { align-items: flex-start; }
  .ml-table td[data-label="Day"] .ct-item,
  .ml-table td[data-label="Planned"] .ct-item { align-items: flex-start; text-align: left; }
  .ml-notes { max-width: 58vw; }
  .ml-table td[data-label="Meal"],
  .ml-table td[data-label="Ingredients"],
  .ml-table td[data-label="Household"],
  .ml-table td[data-label="Added by"],
  .ml-table td[data-label="Actions"] { align-items: flex-end; }

  /* Rooms: the room itself keeps the LEFT edge — its icon has to stay beside
     the name rather than wrap under it — and every other cell stays right,
     which is where it already is on the desktop table. Scoped to .rm-table
     rather than to .admin-table: "Status" and "Household" are labels other
     sections use too, and an unscoped rule here would reach into theirs. */
  .rm-table td[data-label="Room"] { align-items: flex-start; }
  .rm-table td[data-label="Room"] .ct-item { align-items: flex-start; text-align: left; }
  .rm-table td[data-label="Steps"],
  .rm-table td[data-label="Points"],
  .rm-table td[data-label="Chores generated"],
  .rm-table td[data-label="Status"],
  .rm-table td[data-label="Household"],
  .rm-table td[data-label="Set up by"],
  .rm-table td[data-label="Actions"] { align-items: flex-end; }

  /* Categories: the category itself keeps the LEFT edge — its icon has to stay
     beside the name rather than wrap under it — and so does the description
     under it. Everything else stays right, which is where it already is on the
     desktop table. Scoped to .cg-table rather than to .admin-table: "Status"
     and "Household" are labels other sections use too. */
  .cg-table td[data-label="Category"] { align-items: flex-start; }
  .cg-table td[data-label="Category"] .ct-item { align-items: flex-start; text-align: left; }
  .cg-table td[data-label="Slug"],
  .cg-table td[data-label="Colour"],
  .cg-table td[data-label="Order"],
  .cg-table td[data-label="Chores"],
  .cg-table td[data-label="Status"],
  .cg-table td[data-label="Household"],
  .cg-table td[data-label="Made"] { align-items: flex-end; }

  /* Google Calendar: the account keeps the LEFT edge — it is the row's title
     and a wrapped email read right-to-left is unreadable — and so does the error
     message, for the same reason. Everything else stays right, which is where it
     already is on the desktop table. Scoped to .gc-table rather than to
     .admin-table: "Status" and "Household" are labels other sections use too,
     and an unscoped rule here would reach into theirs. */
  .gc-table td[data-label="Google account"] { align-items: flex-start; }
  /* The account is the card's TITLE, so it drops its label and takes the whole
     width — the same treatment the Families card gives the household name. A
     Google address is long enough that sharing a 390px line with an eight-
     character label breaks it mid-domain. */
  .gc-table td[data-label="Google account"]::before { display: none; }
  .gc-table td[data-label="Google account"] .ct-item { align-items: flex-start; text-align: left; max-width: none; }
  .gc-table td[data-label="Last synced"],
  .gc-table td[data-label="Last import"],
  .gc-table td[data-label="Token"],
  .gc-table td[data-label="Status"],
  .gc-table td[data-label="Household"],
  .gc-table td[data-label="Connected by"],
  .gc-table td[data-label="Actions"] { align-items: flex-end; }
  .gc-table td[data-label="Status"] .ct-item { align-items: flex-end; text-align: right; }
  /* The error message keeps the left edge and the whole line under its label:
     it is a string to be matched character for character, and a stack trace
     rewrapped into a 150px gutter cannot be. */
  .gc-table td[data-label="Status"] { flex-wrap: wrap; }
  .gc-table .gc-err { text-align: left; max-width: none; width: 100%; }

  /* Kid requests: the status control takes the width its own label leaves,
     exactly as the filter selects do, rather than sitting as a stub against the
     right edge of the card. Scoped to .kr-table because "Actions" is a label
     every other section uses too. */
  .kr-table td[data-label="Actions"] .kr-acts { flex: 1; min-width: 0; }

  /* Chore library: the entry itself keeps the LEFT edge — the icon has to stay
     beside the name rather than wrap under it — and every other cell stays
     right, which is where it already is on the desktop table. The three-across
     form row becomes one field a line, because 120px of icon box beside a
     points box is unusable on a phone. Scoped to .cl-table rather than to
     .admin-table: "Category" and "Status" are labels other sections use too,
     and an unscoped rule here would reach into theirs. */
  .cl-table td[data-label="Entry"] { align-items: flex-start; }
  .cl-table td[data-label="Entry"] .ct-item { align-items: flex-start; text-align: left; }
  .cl-table td[data-label="Category"],
  .cl-table td[data-label="When"],
  .cl-table td[data-label="Points"],
  .cl-table td[data-label="Taken up by"],
  .cl-table td[data-label="Status"] { align-items: flex-end; }
  .cl-form-row { grid-template-columns: 1fr; }
  .cl-field-icon { max-width: none; }
  /* Four controls in the head (as-of, Export, Refresh, Add) is one more than the
     shared rule's flex-wrap can save on a phone: .admin-head-actions is
     flex-shrink: 0, so it holds its max-content width and Add an entry runs off
     the right edge instead of wrapping. Same fix the Analytics day view needed. */
  #admin-cl-actions { width: 100%; flex-shrink: 1; min-width: 0; }

  /* Templates: the sort select and the search box each take a full line, the
     routine's own multi-line cell keeps the left edge, and the two takedown
     buttons go side by side because the card gives them the width the desktop
     column didn't. */
  .tpl-filters { flex-direction: column; align-items: stretch; }
  .tpl-sort { justify-content: space-between; }
  .tpl-sort select { flex: 1; min-width: 0; }
  .admin-table td[data-label="Routine"] { align-items: flex-start; }
  .tpl-main { max-width: none; }
  .admin-table td[data-label="Author household"] .tpl-cell,
  .admin-table td[data-label="Items"] .tpl-cell,
  .admin-table td[data-label="Downloads"] .tpl-cell,
  .admin-table td[data-label="Status"] .tpl-cell { align-items: flex-end; text-align: right; }
  .tpl-acts { flex-direction: row; justify-content: flex-end; flex-wrap: wrap; min-width: 0; }

  /* Revenue: the head controls wrap rather than pushing Refresh off the screen,
     and both tables become cards. The subscriber cell is the only multi-line one
     so it keeps the left edge; everything else reads better right-aligned
     against its label. */
  .rv-rows { flex: 1; justify-content: space-between; }
  .rv-rows select { flex: 1; min-width: 0; }
  .admin-table td[data-label="Subscriber"] { align-items: flex-start; }
  .admin-table td[data-label="Subscriber"] .admin-uc { text-align: left; }
  .rv-td-amt, .admin-table td[data-label="When"] .rv-when { text-align: right; }
  .rv-subid { max-width: 55vw; }
  .rv-stats { grid-template-columns: 1fr; }
  /* Cancel and Refund go side by side on the card — it gives them the width the
     desktop column didn't, same as the templates takedown pair. */
  .rv-acts { flex-direction: row; justify-content: flex-end; flex-wrap: wrap; min-width: 0; }
  /* The reminder's outcome is a sentence, not a button — it takes its own line
     under them rather than being squeezed into the row of controls. */
  .rv-act-msg { flex: 1 0 100%; max-width: none; text-align: right; }
  .rv-notice { flex-wrap: nowrap; }

  /* Analytics: the range controls take a full line each rather than squeezing
     Refresh off the screen, the totals stack two-up (they are single figures, so
     one per row wastes a phone screen), and the charts get a little more height
     back since they lose most of their width. The source table becomes cards,
     with every number right-aligned against its own label. */
  .an-range { flex: 1; justify-content: space-between; }
  .an-range select, .an-range input { flex: 1; min-width: 0; width: auto; }
  .an-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .an-stat-value { font-size: 26px; }
  .an-funnel { gap: 8px 12px; }
  .an-plot { height: 120px; }
  .an-td-n, .admin-table td[data-label="Signup rate"] { text-align: right; }
  .an-src-table tfoot tr, .an-geo-table tfoot tr, .an-ev-table tfoot tr { background: var(--surface); }
  .an-src-table tfoot td, .an-geo-table tfoot td, .an-ev-table tfoot td { background: none; border-top: none; border-bottom: 1px solid var(--border); }
  /* The locations card is already in rank order, so the rank column is noise on
     a phone; the share meter takes the width the column used to give it. */
  .admin-table td.an-geo-td-rank { display: none; }
  .an-geo-share { flex: 1; min-width: 0; }
  .an-geo-head .btn { width: 100%; justify-content: center; }

  /* AI usage: with the three pickers full-width beneath each other, an auto
     margin would strand Clear on a line of its own with nothing beside it. */
  .ai-filters .em-select { min-width: 0; }
  .ai-clear { margin-left: 0; width: 100%; justify-content: center; }
  .ai-err { max-width: none; }

  /* The day view: the day-steps wrap with the rest of the head actions rather
     than pushing Refresh off the screen, and the machine strings (a browser id,
     a URL path) are clipped to the card rather than setting its width. */
  /* The head actions are flex-shrink:0 everywhere else because they are two
     small buttons; this screen's are a stamp, a day stepper and Refresh, which
     is more than a phone's width — so here the row takes the full line and its
     parts wrap inside it rather than running off the right edge. */
  #admin-anday-actions { width: 100%; flex-wrap: wrap; flex-shrink: 1; min-width: 0; }
  .an-day-steps { display: flex; flex: 1 1 240px; }
  .an-day-steps .btn { flex: 1 1 auto; justify-content: center; }
  .an-day-id, .an-day-path { max-width: 52vw; }
  .an-day-table td { text-align: right; }
  .an-day-table td::before { text-align: left; }

  /* Backlog: the row actions drop under the text rather than squeezing it, and
     the head actions wrap instead of pushing Save off the screen. */
  .admin-head-actions { flex-wrap: wrap; }
  .bl-item { flex-wrap: wrap; }
  .bl-item-actions { margin-left: auto; }
  .bl-compose-hint { flex-basis: 100%; }

  /* Roadmap: a shorter box on a phone so the head actions and the save state
     stay on screen with it. */
  .rm-editor { min-height: 50vh; padding: 12px 14px; font-size: 12.5px; }

  /* Bugs: the open/archive switch takes its own line rather than pushing
     Refresh off the right edge, and Resolve drops under the message instead of
     squeezing it to a column of single words. */
  .bug-views { flex: 1 1 100%; }
  .bug-view { flex: 1; text-align: center; }
  /* The search box takes the full width rather than being squeezed beside the
     export, which then sits under it. */
  .bug-filters { flex-direction: column; align-items: stretch; }
  .bug-filters .em-search { width: 100%; flex: none; min-width: 0; }
  .bug-row-main { flex-wrap: wrap; }
  .bug-resolve { margin-left: auto; }
  .bug-facts { gap: 8px 18px; }
  .bug-blob { max-height: 260px; font-size: 11px; }
}

/* ---- Show/hide password toggle (password-reveal.js) ----
   The script wraps each input[type=password] in .pw-reveal-wrap and parks an
   eye button against the right edge. The wrapper is display:block so it drops
   into the field's old slot without disturbing the surrounding layout. */
.pw-reveal-wrap { position: relative; display: block; width: 100%; }
.pw-reveal-wrap > input { padding-right: 42px; }
.pw-reveal-btn {
  position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
  display: flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; padding: 0;
  background: none; border: none; border-radius: 8px;
  color: var(--muted); line-height: 0;
}
.pw-reveal-btn:hover { color: var(--text); background: rgba(0,0,0,0.04); }
.pw-reveal-btn:focus-visible { outline: 2px solid var(--orange); outline-offset: -2px; }

/* ══════════════ Desktop: use the whole window ══════════════
   Every screen is built phone-first — a 600px column centred in the viewport —
   which on a laptop leaves the app as a narrow strip with empty margins either
   side. From 900px up the content stretches to fill the window (capped on
   ultrawides so a line of text stays readable), the tile grids take the extra
   room, and the bottom-sheet modal becomes a centred dialog. */
@media (min-width: 900px) {
  .topbar { padding: 0 32px; height: 64px; }
  .main { max-width: 1440px; padding: 28px 32px 40px; }
  .stats-row { gap: 16px; }
  .reward-grid { grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); }

  /* The tab bar becomes a left rail that sits INSIDE the app frame: the topbar
     keeps the full window width so the GuudHome logo stays top-left, and the
     rail hangs directly beneath it, alongside the page content ("Hey Simon!").
     The nav is a sibling of the views in the markup, so it's pinned under the
     64px topbar and the active view reserves the same width with padding-left.
     app.js adds .has-rail to #app only on the screens that show the nav, so
     login / setup / the admin portal keep the full width.
     The rail is a plain transparent column — no panel, no divider line — so it
     reads as part of the page rather than a second frame beside it, and it's
     kept narrow with a small left inset so the nav icons line up under the
     GuudHome logo in the topbar instead of floating in a wide empty gutter. */
  #app.has-rail > .view.active { padding-left: 176px; }
  #app.has-rail > .view.active > .topbar { margin-left: -176px; }
  .bottom-nav {
    position: fixed; top: 64px; left: 0; bottom: 0; width: 176px; z-index: 90;
    flex-direction: column; justify-content: flex-start; align-items: stretch; gap: 2px;
    overflow-y: auto;
    padding: 14px 10px 14px 20px;
    background: transparent; box-shadow: none;
    border-top: none; border-right: none;
  }
  /* Icon beside the label instead of stacked over it, at reading size. */
  .nav-item {
    flex-direction: row; justify-content: flex-start; align-items: center; gap: 11px;
    padding: 10px 12px; font-size: 14.5px; text-align: left;
  }
  .nav-item .nav-ic, .nav-item .nav-icon-img { width: 21px; height: 21px; flex: none; }
  /* The rail has no panel behind it now, so hover has to be a light wash of the
     surface colour — var(--bg) would match the page and read as no hover. */
  .nav-item:hover { background: rgba(255,255,255,0.72); color: var(--text); }
  .nav-item.active, .nav-item.active:hover { background: rgba(255,107,53,0.12); color: var(--orange); }

  .modal-backdrop { align-items: center; }
  .modal { border-radius: 24px; margin: 24px; padding: 24px; max-height: 88dvh; }
}
