/* Sheriff design system -- "shire ledger": a warm household almanac.
   No framework; CSS variables mirror templates/core/home.html's naming
   style (--bg/--fg/--accent/--muted/--card-bg/--border) so the two
   stylesheets read as one family. See docs/plans/2026-07-28_m3_pwa_ui.md
   Stage 1 for the design brief this file implements. */

/* ---------------------------------------------------------------------
   Fonts -- self-hosted, no external CDN at runtime.
   Fraunces (variable, wght 100-900) for display/headers.
   Alegreya Sans (400/700 statics) for body/UI.
   --------------------------------------------------------------------- */
@font-face {
  font-family: "Fraunces";
  src: url("../fonts/Fraunces-Variable.eb2486760f75.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Fraunces";
  src: url("../fonts/Fraunces-Italic-Variable.701ae03b5183.woff2") format("woff2");
  font-weight: 100 900;
  font-style: italic;
  font-display: swap;
}
@font-face {
  font-family: "Alegreya Sans";
  src: url("../fonts/AlegreyaSans-Regular.dec96422d9be.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Alegreya Sans";
  src: url("../fonts/AlegreyaSans-Bold.c198fa600447.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

/* ---------------------------------------------------------------------
   Palette -- light "parchment" default, dark "candlelit" via
   prefers-color-scheme. Brass stays constant across both themes.
   --------------------------------------------------------------------- */
:root {
  color-scheme: light dark;

  --bg: #f6f1e5; /* parchment */
  --fg: #2a2620; /* ink */
  --accent: #8f3b2c; /* sheriff crimson */
  --accent-ink: #ffffff;
  --brass: #b08b4f; /* secondary accent */
  --muted: #746b5a;
  --card-bg: #fffdf6;
  --border: #ded2b8;
  --hairline: rgba(42, 38, 32, 0.16);
  --shadow: rgba(42, 38, 32, 0.12);

  --urgency-low: var(--brass);
  --urgency-high: #c9603f; /* ember, used for the hot end of the meter in both themes */

  /* Claim hues (0.9.0, #61): one per household account, mixed against
     --card-bg at the point of use so the chip stays a quiet parchment
     tint rather than a saturated block. Chosen far apart in BOTH hue and
     lightness (deep indigo vs. mid teal) so they stay separable for
     red-green colour blindness, and the chip always carries the person's
     initials as well -- colour is never the only signal. */
  --claim-a: #3f4d99; /* deeper -- michael */
  --claim-b: #1f7a72; /* lighter -- kaijen */

  --font-display: "Fraunces", Georgia, "Times New Roman", serif;
  --font-body: "Alegreya Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;

  --radius: 10px;
  --radius-sm: 6px;
  --touch-min: 44px;

  /* 0.9.2: width of the occurrence row's category column. Sized for the
     longest category actually in the catalog ("House & systems") on one
     line at the chip's 0.78rem; anything longer wraps inside the chip
     rather than stealing width from the chore name. The narrow override
     lives with .ledger-row-occ further down. */
  --row-chip-col: 128px;

  /* 1.0.0 (D-RX4): the reactive sweep's motion timing tokens -- fast for
     fades, base for view transitions, slow for collapses/pulses. All of
     them are used ONLY inside prefers-reduced-motion: no-preference. */
  --motion-fast: 150ms;
  --motion-base: 250ms;
  --motion-slow: 400ms;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #171512; /* candlelit */
    --fg: #e8e2d4; /* warm parchment-ink */
    --accent: #c9603f; /* ember */
    --accent-ink: #171512;
    --brass: #b08b4f; /* unchanged */
    --muted: #a89a80;
    --card-bg: #201d18;
    --border: #3a342a;
    --hairline: rgba(232, 226, 212, 0.14);
    --shadow: rgba(0, 0, 0, 0.4);

    --urgency-low: var(--brass);
    --urgency-high: #e07a52;

    /* Lifted for the candlelit ground -- same two hues, enough luminance
       to read as a tint against #201d18 instead of disappearing into it. */
    --claim-a: #8d9ae8;
    --claim-b: #5fc2b8;
  }
}

/* ---------------------------------------------------------------------
   Base
   --------------------------------------------------------------------- */
* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-body);
  -webkit-font-smoothing: antialiased;
}

body {
  position: relative;
  min-height: 100vh;
}

/* Very subtle paper-grain texture (opacity <= 0.04), tiled. Purely
   decorative; sits behind content and never intercepts input. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: 0.04;
  background-image: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='180'%20height='180'%3E%3Cfilter%20id='n'%3E%3CfeTurbulence%20type='fractalNoise'%20baseFrequency='0.85'%20numOctaves='2'%20stitchTiles='stitch'/%3E%3C/filter%3E%3Crect%20width='100%25'%20height='100%25'%20filter='url(%23n)'/%3E%3C/svg%3E");
  background-repeat: repeat;
}

#page-root {
  position: relative;
  z-index: 1;
}

h1,
h2,
h3,
h4 {
  font-family: var(--font-display);
  font-weight: 600;
  color: var(--fg);
  margin: 0 0 0.4em 0;
}

a {
  color: var(--accent);
}

p {
  line-height: 1.5;
}

.small-caps {
  font-variant: small-caps;
  letter-spacing: 0.04em;
  font-size: 0.78rem;
  color: var(--muted);
  font-weight: 700;
}

/* ---------------------------------------------------------------------
   Buttons
   --------------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5em;
  min-height: var(--touch-min);
  padding: 0.6em 1.1em;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--card-bg);
  color: var(--fg);
  font-family: var(--font-body);
  font-size: 1rem;
  font-weight: 700;
  text-decoration: none;
  cursor: pointer;
  transition: transform 0.08s ease, box-shadow 0.15s ease;
}

.btn:active {
  transform: translateY(1px) scale(0.98);
}

.btn-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
  border-radius: 4px; /* "slightly squared" per brief */
}

.btn-primary:hover {
  filter: brightness(1.05);
}

.btn-block {
  display: flex;
  width: 100%;
}

/* ---------------------------------------------------------------------
   Ledger list / rows -- the primary list component for later stages.
   --------------------------------------------------------------------- */
.ledger-list {
  list-style: none;
  margin: 0;
  padding: 0;
  border-top: 1px solid var(--hairline);
}

.ledger-row {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 56px;
  padding: 10px 4px;
  border-bottom: 1px solid var(--hairline);
  text-decoration: none;
  color: inherit;
  opacity: 0;
  animation: ledger-fade-up 0.4s ease-out forwards;
}

/* Staggered fade-up on page load, capped at 8 rows. */
.ledger-row:nth-child(1) { animation-delay: 0.02s; }
.ledger-row:nth-child(2) { animation-delay: 0.06s; }
.ledger-row:nth-child(3) { animation-delay: 0.1s; }
.ledger-row:nth-child(4) { animation-delay: 0.14s; }
.ledger-row:nth-child(5) { animation-delay: 0.18s; }
.ledger-row:nth-child(6) { animation-delay: 0.22s; }
.ledger-row:nth-child(7) { animation-delay: 0.26s; }
.ledger-row:nth-child(8) { animation-delay: 0.3s; }
.ledger-row:nth-child(n + 9) { animation-delay: 0.3s; }

@keyframes ledger-fade-up {
  from {
    opacity: 0;
    transform: translateY(6px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.ledger-row .title {
  flex: 1 1 auto;
  min-width: 0;
}

.ledger-row .title strong {
  display: block;
  font-family: var(--font-body);
  font-weight: 700;
}

.ledger-row .subtitle {
  color: var(--muted);
  font-size: 0.85rem;
}

/* 0.7.1 plan item 3: overdue subtitle text reuses the urgency meter's hot
   end instead of the default muted color, on Today (Schedule/wall never
   produce an overdue row through this same partial -- see
   chores/_occurrence_row.html). */
.subtitle-overdue {
  color: var(--urgency-high);
  font-weight: 700;
}

/* 0.7.1 plan item 1: the shared "Last done ..." line, one muted row of
   text under the due-date subtitle. */
.last-done-line {
  display: block;
}

/* A ledger-row can also be a <form> (e.g. logout, which Django requires as
   POST); this makes the submit button inside it look like plain row text. */
.ledger-btn {
  all: unset;
  display: flex;
  align-items: center;
  width: 100%;
  min-height: 44px;
  cursor: pointer;
  font-family: inherit;
  color: inherit;
}

/* Notifications card (M4 stage B, More page) -- sits below the ledger-list,
   same card surface as .login-card/other rows rather than a new component. */
.notify-card {
  margin-top: 24px;
  padding: 16px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--card-bg);
}

.notify-card .subtitle {
  color: var(--muted);
  font-size: 0.85rem;
  margin: 4px 0 12px 0;
}

.notify-note {
  margin-top: 12px;
  color: var(--muted);
  font-size: 0.85rem;
}

/* ---------------------------------------------------------------------
   Chips -- category / status pills, muted natural hues.
   --------------------------------------------------------------------- */
.chip {
  display: inline-flex;
  align-items: center;
  padding: 0.15em 0.7em;
  border-radius: 999px;
  font-size: 0.78rem;
  font-weight: 700;
  line-height: 1.6;
  background: var(--border);
  color: var(--fg);
  white-space: nowrap;
}

.chip-brass {
  background: color-mix(in srgb, var(--brass) 25%, var(--card-bg));
  color: var(--fg);
}

.chip-crimson {
  background: color-mix(in srgb, var(--accent) 20%, var(--card-bg));
  color: var(--fg);
}

.chip-muted {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--muted);
}

/* Batch A item 6 (inbox #14): unassigned occurrences render a quiet
   "any" owner chip -- italic to read as a word, not someone's initials. */
.chip-any {
  font-style: italic;
}

/* ---------------------------------------------------------------------
   Claim chips (0.9.0, #61 -- Michael 2026-09-22: "colour-coded claim
   chips (KJ / Michael / Both) for easy screening"). A recolour of the
   existing owner chip, not a new component: same pill, same initials,
   same place in the row.

   Accessibility: the tint is mixed against --card-bg so body text keeps
   its normal contrast in both themes, the hues are far apart in BOTH hue
   and lightness, "Both" additionally carries a two-tone fill and a
   heavier border, and the initials inside the chip are always the
   primary signal -- greyscale and colour-blind readers lose nothing.
   --------------------------------------------------------------------- */
.chip-claim {
  background: var(--border);
  color: var(--fg);
  border: 1px solid var(--muted);
}

.chip-claim-michael {
  background: color-mix(in srgb, var(--claim-a) 32%, var(--card-bg));
  border-color: var(--claim-a);
}

.chip-claim-kaijen {
  background: color-mix(in srgb, var(--claim-b) 18%, var(--card-bg));
  border-color: var(--claim-b);
}

/* Two-tone: a hard split down the middle, so "Both" is recognizable as
   "these two together" even with the colours washed out. */
.chip-claim-both {
  background: linear-gradient(
    100deg,
    color-mix(in srgb, var(--claim-a) 32%, var(--card-bg)) 0 50%,
    color-mix(in srgb, var(--claim-b) 18%, var(--card-bg)) 50% 100%
  );
  border-width: 2px;
  border-color: var(--claim-a);
}

/* 0.7.1 plan item 6: inbox items still awaiting a decision after 7+ days
   -- a gentle parchment-yellowing nudge to revisit, not a warning (brass,
   never crimson/urgency-high; this is about the item, never a person). */
.chip-aging {
  background: color-mix(in srgb, var(--brass) 22%, var(--card-bg));
  color: var(--fg);
  border: 1px solid var(--brass);
}

.ledger-row-aging {
  background: color-mix(in srgb, var(--brass) 6%, transparent);
}

/* ---------------------------------------------------------------------
   Urgency meter -- thin bar, brass -> ember gradient fill proportional
   to --urgency (0-100+ %). Pulses gently at >=100%.
   --------------------------------------------------------------------- */
.urgency {
  --urgency: 0;
  position: relative;
  width: 100%;
  height: 5px;
  border-radius: 999px;
  background: var(--hairline);
  overflow: hidden;
}

.urgency > .urgency-fill {
  display: block;
  height: 100%;
  width: clamp(0%, var(--urgency), 100%);
  border-radius: 999px;
  background: linear-gradient(90deg, var(--urgency-low), var(--urgency-high));
}

.urgency.is-due > .urgency-fill {
  animation: urgency-pulse 2s ease-in-out infinite;
}

@keyframes urgency-pulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.55;
  }
}

/* ---------------------------------------------------------------------
   Wax check -- the check-off button. On completion (htmx swap), plays a
   "wax seal" stamp-in: scale + rotate overshoot + fade.
   --------------------------------------------------------------------- */
.wax-check {
  flex: 0 0 auto;
  width: var(--touch-min);
  height: var(--touch-min);
  border-radius: 50%;
  border: 2px solid var(--accent);
  background: var(--card-bg);
  color: var(--accent);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  padding: 0;
}

.wax-check.is-done {
  background: var(--accent);
  color: var(--accent-ink);
  border-color: var(--accent);
  animation: wax-stamp 350ms cubic-bezier(0.34, 1.56, 0.64, 1) both;
}

@keyframes wax-stamp {
  0% {
    opacity: 0;
    transform: scale(1.4) rotate(6deg);
  }
  100% {
    opacity: 1;
    transform: scale(1) rotate(0deg);
  }
}

/* ---------------------------------------------------------------------
   0.9.8 (#96): reactive check-off -- see static/js/checkoff.js.
   A checked-off row leaves in place instead of the page reloading.
   .is-settled stops the page-load fade-up (its fill would fight the
   fade); .is-leaving fades the row and, motion permitting, collapses it.
   Reduced motion gets the gentle fade only -- no collapse, no slide.
   Keep the total under checkoff.js's LEAVE_MS (420ms).
   --------------------------------------------------------------------- */
.ledger-row.is-settled {
  animation: none;
  opacity: 1;
}

/* !important + two classes: beats the reduced-motion block's
   `.ledger-row { opacity: 1 !important }` further down. */
.ledger-row.is-leaving {
  opacity: 0 !important;
  pointer-events: none;
  transition: opacity 400ms ease;
}

@media (prefers-reduced-motion: no-preference) {
  .ledger-row.is-leaving {
    overflow: hidden;
    max-height: 0 !important;
    min-height: 0 !important;
    padding-top: 0 !important;
    padding-bottom: 0 !important;
    border-bottom-color: transparent;
    transition:
      opacity 180ms ease-out,
      max-height 300ms ease-in-out 100ms,
      min-height 300ms ease-in-out 100ms,
      padding 300ms ease-in-out 100ms,
      border-color 300ms ease 100ms;
  }
}

/* The in-place refresh swaps <main id="sheriff-main"> for a fresh server
   render; the server marks that htmx-fetched copy .is-live-swap
   (templates/base_app.html) so the staggered fade-up does not replay on
   every row -- that replay would be the "confusing flash" again. */
.is-live-swap .ledger-row {
  animation: none;
  opacity: 1;
}

/* ---------------------------------------------------------------------
   0.9.8 (#88): check-off celebration -- see static/js/celebrate.js.
   Hidden outright unless the user has expressed no motion preference;
   every animation lives inside the no-preference query below.
   --------------------------------------------------------------------- */
.celebrate {
  display: none;
}

@media (prefers-reduced-motion: no-preference) {
  .celebrate {
    display: block;
    position: fixed;
    width: 0;
    height: 0;
    z-index: 1000;
    pointer-events: none;
  }

  .celebrate > span {
    position: absolute;
    left: 0;
    top: 0;
    display: block;
  }

  /* Confetti: small paper flecks thrown out, then drifting down. */
  .celebrate-bit {
    width: 5px;
    height: 9px;
    margin: -4px 0 0 -2px;
    border-radius: 1px;
    opacity: 0;
    animation: celebrate-confetti 900ms cubic-bezier(0.2, 0.7, 0.3, 1) var(--delay, 0ms) forwards;
  }

  @keyframes celebrate-confetti {
    0% {
      opacity: 1;
      transform: translate(0, 0) rotate(0deg);
    }
    60% {
      opacity: 1;
      transform: translate(var(--dx), var(--dy)) rotate(var(--rot));
    }
    100% {
      opacity: 0;
      transform: translate(var(--dx), calc(var(--dy) + 22px)) rotate(var(--rot));
    }
  }

  /* Sparkle: four-point brass glints winking in and out. */
  .celebrate-glint {
    width: var(--size, 10px);
    height: var(--size, 10px);
    margin: calc(var(--size, 10px) / -2) 0 0 calc(var(--size, 10px) / -2);
    background: var(--brass);
    clip-path: polygon(50% 0, 62% 38%, 100% 50%, 62% 62%, 50% 100%, 38% 62%, 0 50%, 38% 38%);
    opacity: 0;
    animation: celebrate-glint 700ms ease-out var(--delay, 0ms) forwards;
  }

  @keyframes celebrate-glint {
    0% {
      opacity: 0;
      transform: translate(var(--x), var(--y)) scale(0) rotate(0deg);
    }
    40% {
      opacity: 1;
      transform: translate(var(--x), var(--y)) scale(1) rotate(45deg);
    }
    100% {
      opacity: 0;
      transform: translate(var(--x), var(--y)) scale(0.2) rotate(90deg);
    }
  }

  /* Tip of the hat: the app icon's six-point star rises off the seal. */
  .celebrate-star {
    width: 26px;
    height: 26px;
    margin: -13px 0 0 -13px;
    background: var(--brass);
    clip-path: polygon(50% 8%, 60.5% 31.8%, 86.4% 29%, 71% 50%, 86.4% 71%, 60.5% 68.2%, 50% 92%, 39.5% 68.2%, 13.6% 71%, 29% 50%, 13.6% 29%, 39.5% 31.8%);
    opacity: 0;
    animation: celebrate-star 1000ms cubic-bezier(0.25, 0.8, 0.35, 1) forwards;
  }

  @keyframes celebrate-star {
    0% {
      opacity: 0;
      transform: translateY(0) scale(0.5) rotate(-20deg);
    }
    25% {
      opacity: 1;
      transform: translateY(-14px) scale(1.15) rotate(0deg);
    }
    70% {
      opacity: 1;
      transform: translateY(-30px) scale(1) rotate(12deg);
    }
    100% {
      opacity: 0;
      transform: translateY(-40px) scale(0.9) rotate(18deg);
    }
  }

  .celebrate-ring {
    width: 44px;
    height: 44px;
    margin: -22px 0 0 -22px;
    border: 2px solid var(--brass);
    border-radius: 50%;
    opacity: 0;
    animation: celebrate-ring 650ms ease-out forwards;
  }

  @keyframes celebrate-ring {
    0% {
      opacity: 0.7;
      transform: scale(0.6);
    }
    100% {
      opacity: 0;
      transform: scale(1.6);
    }
  }
}

/* ---------------------------------------------------------------------
   Banners / empty states
   --------------------------------------------------------------------- */
.banner-stale {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--brass) 18%, var(--card-bg));
  border: 1px solid var(--border);
  color: var(--fg);
  font-size: 0.9rem;
}

/* Refresh pill -- static/js/freshness.js (0.5.1 plan item 2): the
   non-destructive "tap to refresh" affordance shown either when stale
   content couldn't auto-reload because a form is dirty/focused, or when
   a new service worker is waiting to take over. Fixed near the top of
   the viewport (the bottom is already owned by .bottom-nav), a floating
   pill rather than a banner so it never shifts page layout underneath a
   mid-edit form. */
.refresh-pill {
  position: fixed;
  top: calc(env(safe-area-inset-top, 0px) + 10px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 5;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: var(--touch-min);
  padding: 8px 18px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: color-mix(in srgb, var(--brass) 30%, var(--card-bg));
  color: var(--fg);
  font-family: var(--font-body);
  font-size: 0.85rem;
  font-weight: 700;
  box-shadow: 0 2px 10px var(--shadow);
  cursor: pointer;
}

.refresh-pill[hidden] {
  display: none;
}

.empty-state {
  text-align: center;
  padding: 48px 20px;
  color: var(--muted);
  font-family: var(--font-display);
  font-style: italic;
  font-size: 1.15rem;
}

/* ---------------------------------------------------------------------
   Authenticated app shell -- base_app.html
   --------------------------------------------------------------------- */
.app-shell {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

.app-header {
  padding: 18px 16px 10px 16px;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
}

.header-refresh {
  background: none;
  border: none;
  padding: 6px;
  margin: 2px 0 0 8px;
  color: var(--muted);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.header-refresh svg {
  width: 24px;
  height: 24px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.header-refresh:active {
  color: var(--fg);
}

.header-actions {
  display: flex;
  align-items: flex-start;
}

/* ---------------------------------------------------------------------
   Search (0.7.3) -- header toggle expands this bar; static/js/search.js
   filters the page's .ledger-row set client-side.
   --------------------------------------------------------------------- */
.search-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 16px 10px 16px;
  max-width: 640px; /* track .app-content's centered column */
  width: 100%;
  margin: 0 auto;
}

.search-bar[hidden] {
  display: none;
}

.search-bar input[type="search"] {
  flex: 1 1 auto;
  min-width: 0;
  min-height: var(--touch-min);
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-body);
  font-size: 1rem;
}

.search-count {
  color: var(--muted);
  font-size: 0.85rem;
  white-space: nowrap;
}

mark.search-mark {
  background: rgba(176, 139, 79, 0.35); /* --brass at 35% for pre-color-mix engines */
  background: color-mix(in srgb, var(--brass) 35%, transparent);
  color: inherit;
  border-radius: 2px;
}

/* 0.8.1 (inbox plan #31): the "you arrived here from a search" chip a
   detail page grows when it paints carried-over highlights. Built by
   static/js/search.js, dismissible, styled entirely from theme tokens via
   .chip so it reads right in both light and dark. */
.search-carry {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 0 0 10px 0;
}

/* Filtered-out rows/groups. Explicit rules because .ledger-row and
   .search-bar are display:flex, which beats the UA's [hidden] rule. */
.ledger-row[hidden],
.ledger-list[hidden],
.app-content section[hidden],
.app-content h3[hidden] {
  display: none;
}

/* While a search is live, rows re-show as the query changes; the
   staggered load-in animation must not replay on every keystroke
   (it leaves rows at opacity 0 until it runs). */
body.search-active .ledger-row {
  animation: none;
  opacity: 1;
}

.app-header h1 {
  font-size: 1.6rem;
  margin: 0;
}

.app-header .app-date {
  margin: 2px 0 0 0;
  color: var(--muted);
  font-size: 0.9rem;
}

.app-content {
  flex: 1 1 auto;
  padding: 4px 16px 24px 16px;
  max-width: 640px;
  width: 100%;
  margin: 0 auto;
}

.app-footer {
  text-align: center;
  padding: 10px 16px 14px 16px;
  font-size: 0.72rem;
  color: var(--muted);
}

/* Bottom nav -- 4 items, inline-SVG icons, fixed to the viewport bottom. */
.bottom-nav {
  position: sticky;
  bottom: 0;
  z-index: 2;
  display: flex;
  background: var(--card-bg);
  border-top: 1px solid var(--border);
  box-shadow: 0 -2px 8px var(--shadow);
  padding-bottom: env(safe-area-inset-bottom, 0);
}

.bottom-nav a {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  min-height: 56px;
  padding: 6px 4px;
  color: var(--muted);
  text-decoration: none;
  font-size: 0.7rem;
  font-weight: 700;
}

.bottom-nav a svg {
  width: 24px;
  height: 24px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.bottom-nav a.is-active {
  color: var(--accent);
}

/* ---------------------------------------------------------------------
   Login page -- centered card on parchment, public, kept quiet.
   --------------------------------------------------------------------- */
.login-page {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px 16px;
}

.login-card {
  width: 100%;
  max-width: 360px;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 32px 28px;
  box-shadow: 0 6px 24px var(--shadow);
  text-align: center;
}

.login-card h1 {
  font-size: 1.5rem;
  margin-bottom: 4px;
}

.login-card .tagline {
  color: var(--muted);
  font-size: 0.9rem;
  margin: 0 0 24px 0;
}

.login-card .errorlist {
  list-style: none;
  margin: 0 0 4px 0;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--accent) 12%, var(--card-bg));
  color: var(--accent);
  font-size: 0.85rem;
}

.login-footer {
  margin-top: 20px;
  font-size: 0.7rem;
  color: var(--muted);
}

/* Google sign-in (M3 auth section) -- the only sign-in method since
   password login was retired 2026-08-02. Reuses .btn/.btn-block. */
.btn-google {
  margin-top: 8px;
  background: var(--card-bg);
}

/* Shown instead of the Google button when GOOGLE_LOGIN_ENABLED is False
   (no OAuth env vars, e.g. a fresh dev clone) -- same quiet treatment as
   .errorlist without implying anything went wrong. */
.login-not-configured {
  margin: 8px 0 0 0;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--muted) 12%, var(--card-bg));
  color: var(--muted);
  font-size: 0.85rem;
}

/* ---------------------------------------------------------------------
   Stage 2 -- Today/Schedule ledger rows that mix a wax-check button with
   a tappable link (row-link/row-top), section spacing, form fields for
   the occurrence-detail action row, inbox capture, and the markdown/
   chore-facts read views. Reuses the same tokens as everything above.
   --------------------------------------------------------------------- */
.app-content section {
  margin-top: 22px;
}

.app-content section:first-child {
  margin-top: 4px;
}

.app-content h3 {
  margin-top: 20px;
  font-size: 1.05rem;
}

.wax-check-form {
  flex: 0 0 auto;
  display: flex;
}

.wax-check svg {
  width: 20px;
  height: 20px;
}

.row-link {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
  text-decoration: none;
  color: inherit;
}

.row-top {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

.ack-form,
.drop-form {
  flex: 0 0 auto;
}

/* ---------------------------------------------------------------------
   Occurrence detail / chore detail read views
   --------------------------------------------------------------------- */
.occ-detail-head {
  display: flex;
  gap: 8px;
  margin-bottom: 6px;
}

.markdown-body {
  font-size: 0.95rem;
  line-height: 1.55;
}

.markdown-body :first-child {
  margin-top: 0;
}

.markdown-body pre {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 10px 12px;
  overflow-x: auto;
}

.markdown-body code {
  background: var(--card-bg);
  border-radius: 4px;
  padding: 0.1em 0.35em;
}

.chore-facts {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 6px 14px;
  margin: 0;
}

.chore-facts dt {
  color: var(--muted);
  font-weight: 700;
  font-size: 0.85rem;
}

.chore-facts dd {
  margin: 0;
}

/* ---------------------------------------------------------------------
   One-click claim buttons (0.9.0, #61) -- KJ / Michael / Both in the
   occurrence's Claim area. Each button wears the same hue as that
   person's chip, so the button you press and the chip you then see
   everywhere else are visibly the same thing. The pressed state is a
   filled, ringed button (plus aria-pressed) -- never colour alone.
   --------------------------------------------------------------------- */
.claim-heading {
  margin-bottom: 6px;
}

.claim-buttons {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 8px;
}

.claim-btn {
  min-height: var(--touch-min);
  padding: 0 18px;
  border-radius: 999px;
  border: 1px solid var(--muted);
  background: var(--card-bg);
  color: var(--fg);
  font-family: var(--font-body);
  font-size: 0.95rem;
  font-weight: 700;
  cursor: pointer;
}

.claim-btn.is-active {
  box-shadow: inset 0 0 0 2px var(--fg);
}

.claim-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ---------------------------------------------------------------------
   Move-to-top / move-to-bottom (0.9.0, #61). Small and subtle on a Today
   row -- the row is for reading and checking off, the reorder controls
   are for the rarer deliberate act -- and a full-size pair in the
   occurrence-detail action area.
   --------------------------------------------------------------------- */
.row-reorder {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.reorder-form {
  display: flex;
}

.reorder-btn {
  width: 28px;
  height: 26px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--muted);
  cursor: pointer;
}

.reorder-btn svg {
  width: 16px;
  height: 16px;
}

.reorder-btn:hover,
.reorder-btn:focus-visible {
  color: var(--fg);
  border-color: var(--border);
}

.reorder-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.occ-reorder {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 12px 0;
}

.occ-reorder-label {
  color: var(--muted);
}

/* Detail-page pair: side by side and full tap targets, not the row's
   stacked miniatures. */
.occ-reorder .row-reorder {
  flex-direction: row;
  gap: 8px;
}

.occ-reorder .reorder-btn {
  width: var(--touch-min);
  height: var(--touch-min);
  border-color: var(--border);
  background: var(--card-bg);
}

.occ-reorder .reorder-btn svg {
  width: 22px;
  height: 22px;
}

/* ---------------------------------------------------------------------
   Drag to reorder (0.9.1, #61 -- static/js/reorder.js + SortableJS).
   Everything here is token-driven, so light, dark (prefers-color-scheme)
   and the always-dark /wall kiosk all follow without a second copy.
   --------------------------------------------------------------------- */

/* The grip. A full --touch-min square (Michael's failure mode 4:
   "unfindably small drop zones") holding a small, quiet icon.

   The touch-action / user-select block is on the HANDLE ONLY and must
   stay that way: putting touch-action:none on the <ul> would kill
   ordinary vertical scrolling of the whole list, which is what a finger
   does on this page 99% of the time. */
.drag-handle {
  flex: 0 0 auto;
  width: var(--touch-min);
  height: var(--touch-min);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--muted);
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
  cursor: grab;
}

.drag-handle:active {
  cursor: grabbing;
}

.drag-handle svg {
  width: 20px;
  height: 20px;
}

/* Lifted: the row under the finger. */
.ledger-row.occ-chosen {
  transform: scale(1.02);
  background: var(--card-bg);
  box-shadow: 0 6px 18px var(--shadow);
  border-radius: var(--radius-sm);
}

/* The placeholder left behind in the list -- a dashed drop outline, with
   the row's own content faded so the gap reads as "this is where it will
   land" rather than as a duplicate row. Dimming the CHILDREN (not the
   row) keeps clear of .ledger-row's fade-up animation, which drives the
   row's own opacity. */
.ledger-row.occ-ghost {
  outline: 2px dashed var(--accent);
  outline-offset: -2px;
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
}

.ledger-row.occ-ghost > * {
  opacity: 0.35;
}

/* The floating clone (forceFallback + fallbackOnBody). It must override
   .ledger-row's entry animation, whose from-state is opacity:0 -- without
   this the thing being dragged is invisible for the first 400ms. */
.ledger-row.occ-drag {
  opacity: 1;
  animation: none;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: 0 10px 26px var(--shadow);
}

/* A failed move animates back to where it started (reorder.js's
   failMove) -- the revert has to be visible, not a teleport. */
.ledger-row.occ-reverting {
  animation: occ-revert-flash 0.4s ease-out;
}

@keyframes occ-revert-flash {
  from {
    background: color-mix(in srgb, var(--accent) 18%, transparent);
  }
  to {
    background: transparent;
  }
}

/* The loud inline failure line, inserted at the top of the section.
   Same voice and weight as .claim-error, shaped as a list row because it
   lives inside the <ul>. */
li.row-error {
  list-style: none;
  padding: 8px 4px;
  color: var(--accent);
  font-size: 0.85rem;
  font-weight: 700;
  border-bottom: 1px solid var(--hairline);
}

/* ---------------------------------------------------------------------
   Occurrence-detail actions row + inbox capture -- shared field styling.
   --------------------------------------------------------------------- */
.occ-actions {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.occ-action-form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  padding: 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--card-bg);
}

.occ-action-form > .btn,
.occ-action-form > .wax-check {
  flex: 0 0 auto;
}

.occ-action-label {
  font-weight: 700;
}

.occ-action-form details {
  flex: 1 1 100%;
  font-size: 0.85rem;
}

.occ-action-form details label {
  display: block;
  margin-top: 8px;
  font-size: 0.8rem;
  color: var(--muted);
  font-weight: 700;
}

.occ-action-form label.small-caps {
  flex: 0 0 auto;
}

.occ-action-form input[type="text"],
.occ-action-form input[type="date"],
.occ-action-form input[type="time"],
.occ-action-form input[type="number"],
.occ-action-form input[type="file"],
.occ-action-form select,
.occ-action-form textarea,
.capture-form input[type="file"],
.capture-form textarea {
  min-height: var(--touch-min);
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-body);
  font-size: 1rem;
  width: 100%;
}

.occ-action-form textarea,
.capture-form textarea {
  min-height: 44px;
  resize: vertical;
}

.capture-form {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 18px;
}

/* 0.9.9 (#78): static/js/capture.js's honest outcome line under the
   capture button ("Saved while you were away." etc). */
.capture-status {
  margin: 0;
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--fg);
}

.capture-status[hidden] {
  display: none;
}

/* 0.9.9 sw.js fail-loudly fix: the label static/sw.js injects into a page
   it served from cache because the network was unreachable. sw.js also
   inlines equivalent styles (the cached page's stylesheet may itself be
   unavailable offline); these rules just match the app's look when it is. */
.offline-copy-banner {
  position: sticky;
  top: 0;
  z-index: 7;
  padding: 10px 14px;
  background: color-mix(in srgb, var(--brass) 30%, var(--card-bg));
  border-bottom: 1px solid var(--border);
  color: var(--fg);
  font-weight: 700;
  font-size: 0.9rem;
  text-align: center;
}

/* ---------------------------------------------------------------------
   1.0.0 (D-RX2(b)/D-RX4): live pages -- static/js/live.js.
   --------------------------------------------------------------------- */

/* Content that must stay OUTSIDE the morphed <main id="sheriff-main">
   (the inbox capture form) but keep the content column's geometry. */
.app-pre-main {
  flex: 0 0 auto;
  padding: 4px 16px 0 16px;
  max-width: 640px;
  width: 100%;
  margin: 0 auto;
}

/* The honest status line: "Offline -- last synced 3:42 PM", "Signed out",
   "Couldn't update this page", or a change held for your draft. Same
   sticky look as sw.js's offline-copy label (one voice for both). */
.live-status {
  position: sticky;
  top: 0;
  z-index: 7;
  margin: 0;
  padding: 10px 14px;
  background: color-mix(in srgb, var(--brass) 30%, var(--card-bg));
  border-bottom: 1px solid var(--border);
  color: var(--fg);
  font-weight: 700;
  font-size: 0.9rem;
  text-align: center;
}

.live-status[data-kind="held"] {
  font-weight: 400;
  background: color-mix(in srgb, var(--brass) 14%, var(--card-bg));
}

.live-status[hidden] {
  display: none;
}

/* Occurrence detail: "Marked done on another device at 3:41 PM." */
.live-watch-notice {
  margin: 0 0 12px 0;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-left: 4px solid var(--accent);
  border-radius: var(--radius-sm);
  background: var(--card-bg);
  font-weight: 700;
}

/* A row that arrived or changed in a live morph: one brief brass wash, so
   a list that moved is SEEN to move (D-RX4). Motion-permitting only --
   under reduced motion the row simply shows its new content. */
@media (prefers-reduced-motion: no-preference) {
  .live-pulse {
    animation: live-pulse 650ms ease-out;
  }

  @keyframes live-pulse {
    from {
      background-color: color-mix(in srgb, var(--brass) 38%, transparent);
    }
    to {
      background-color: transparent;
    }
  }

  /* The same-page View Transition live.js wraps each morph in (htmx's
     transition:true): a short crossfade, so rows that left fade out. */
  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation-duration: var(--motion-base);
  }
}

/* live.js never asks for a View Transition under reduced motion; this is
   the belt to that brace (e.g. a preference flipped mid-session). */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}

/* Batch B #16: capture-photo thumbnail on inbox rows. Lives inside the
   row's .title span (never a new column -- the fixed 92px .deputy-cell
   layout is untouched); chip-ish framing, tap-through to the full image. */
.inbox-photo-link {
  display: inline-block;
  margin-top: 4px;
  line-height: 0;
}

.inbox-photo-thumb {
  max-height: 44px;
  max-width: 88px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  display: inline-block;
}

/* ---------------------------------------------------------------------
   0.7.0 stage B -- type-kicker badges (D7): small-caps label above the
   h2 on both detail pages, visually distinct per page so "Chore vs
   Occurrence" is unmistakable at a glance (Michael's complaint that the
   two pages looked like twins).
   --------------------------------------------------------------------- */
.type-kicker {
  display: inline-block;
  font-variant: small-caps;
  letter-spacing: 0.08em;
  font-size: 0.72rem;
  font-weight: 700;
  padding: 3px 10px;
  border-radius: 999px;
  margin: 0 0 8px 0;
}

.type-kicker-chore {
  background: color-mix(in srgb, var(--brass) 30%, var(--card-bg));
  color: var(--fg);
}

.type-kicker-occurrence {
  background: color-mix(in srgb, var(--accent) 22%, var(--card-bg));
  color: var(--accent);
}

.next-up {
  font-weight: 700;
}

.next-up a {
  text-decoration: none;
}

.schedule-provenance {
  margin-top: -8px;
}

/* ---------------------------------------------------------------------
   Tag chips (D5) -- the chip row sits OUTSIDE any wrapping row-link/
   occ-detail-head anchor so a real clickable <a> chip never nests
   inside another <a> (invalid HTML, unreliable tap targets).
   --------------------------------------------------------------------- */
.chip-link {
  text-decoration: none;
  cursor: pointer;
}

.chip-link:hover,
.chip-link:focus-visible {
  filter: brightness(1.08);
  text-decoration: underline;
}

.row-tags {
  flex: 0 0 auto;
  display: flex;
  flex-wrap: wrap;
  align-content: center;
  justify-content: flex-end;
  gap: 6px;
  max-width: 45%;
}

/* ---------------------------------------------------------------------
   0.9.2 -- occurrence LIST rows (templates/chores/_occurrence_row.html):
   a fixed right column for category chips.

   Kaijen, 2026-09-23: "make category chips stack (and wordy categories
   wrap) so that categories stay in a fixed right column. The chore names
   are now too cramped." Before this, .row-tags was an inline flex item
   allowed up to 45% of the row, so a two-tag chore squeezed the name to a
   sliver and every row's chips landed in a different place.

   Five explicit grid tracks, one per child class, so the columns line up
   down the whole list even though three of the children are conditional
   (move buttons and grip only on reorderable Today sections; the chip
   column itself always renders, empty if the chore has no tags -- that
   empty track is what keeps the chips of its neighbours in line):

     [wax check] [name + meta, flexible] [chips, FIXED] [moves] [grip]

   Spacing is per-child margins rather than grid `gap` on purpose: the
   optional tracks collapse to zero width on a Schedule row, and a gap
   would still reserve its space around them. */
.ledger-row-occ {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) var(--row-chip-col) auto auto;
  align-items: center;
  gap: 0;
}

.ledger-row-occ > * + * {
  margin-left: 12px;
}

/* The column itself: chips stacked, right-aligned, each one free to wrap
   to a second line inside the column's width (.chip is nowrap by
   default, which is exactly what made a wordy category widen the row). */
.row-chip-col {
  width: 100%;
  min-width: 0;
  max-width: none;
  flex-direction: column;
  flex-wrap: nowrap;
  align-items: flex-end;
  justify-content: center;
  gap: 4px;
}

.row-chip-col .chip {
  max-width: 100%;
  white-space: normal;
  overflow-wrap: break-word;
  text-align: right;
  line-height: 1.25;
  padding: 0.25em 0.6em;
}

/* Phone (the tablet/desktop width keeps the roomy column). At ~390px
   logical the row is already carrying a 44px check, the move buttons and
   a 44px grip, so the category column gives some width back and lets the
   chips wrap more instead of pushing the chore name into a sliver. */
@media (max-width: 430px) {
  :root {
    --row-chip-col: 88px;
  }

  .ledger-row-occ > * + * {
    margin-left: 8px;
  }
}

/* ---------------------------------------------------------------------
   Claim panel (D3) -- inline reveal (native <details>, never
   window.confirm/alert) for the Take-over/Join choice when the
   occurrence is already assigned to the other household member.
   --------------------------------------------------------------------- */
.claim-panel {
  margin: 10px 0 4px 0;
}

.claim-summary {
  list-style: none;
  cursor: pointer;
}

.claim-summary::-webkit-details-marker {
  display: none;
}

.claim-choices {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 10px;
  padding: 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--card-bg);
}

.claim-form {
  display: flex;
}

.claim-form .btn {
  width: 100%;
}

.claim-cancel {
  align-self: flex-start;
}

.claim-error {
  margin-top: 8px;
  color: var(--accent);
  font-size: 0.85rem;
  font-weight: 700;
}

.claim-error[hidden] {
  display: none;
}

.handoff-blocked-note {
  padding: 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

/* ---------------------------------------------------------------------
   Chore edit form (D6) -- vertical label-above-field layout, distinct
   from .occ-action-form's compact inline rows above (those are for
   short single-purpose action rows; this is a full multi-field form).
   --------------------------------------------------------------------- */
.edit-form {
  display: flex;
  flex-direction: column;
  gap: 16px;
  margin-top: 12px;
}

.edit-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.edit-field label {
  font-weight: 700;
  font-size: 0.85rem;
  color: var(--muted);
}

.edit-field input[type="text"],
.edit-field input[type="number"],
.edit-field select,
.edit-field textarea {
  min-height: var(--touch-min);
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-body);
  font-size: 1rem;
  width: 100%;
}

.edit-field textarea {
  min-height: 80px;
  resize: vertical;
}

.new-tag-field {
  margin-top: 4px;
}

/* New-chore page: one per-schedule-type field group (chore_new.html).
   Server renders every group visible; the page's inline script hides
   all but the selected type's group (progressive enhancement). */
.schedule-type-fields {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.schedule-type-fields[hidden] {
  display: none;
}

.form-help {
  font-size: 0.85rem;
  color: var(--muted);
}

.chip-toggle-list {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.chip-toggle {
  display: inline-flex;
  cursor: pointer;
}

.chip-toggle input {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.chip-toggle .chip {
  border: 1px solid var(--border);
  background: var(--card-bg);
}

.chip-toggle input:checked + .chip {
  background: color-mix(in srgb, var(--accent) 22%, var(--card-bg));
  border-color: var(--accent);
  color: var(--fg);
}

.chip-toggle input:focus-visible + .chip {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.checkbox-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.checkbox-row {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 400;
  color: var(--fg);
}

.propagate-row {
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--brass) 18%, var(--card-bg));
}

.propagate-row[hidden] {
  display: none;
}

.errorlist {
  list-style: none;
  margin: 0 0 12px 0;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--accent) 12%, var(--card-bg));
  color: var(--accent);
  font-size: 0.85rem;
}

/* ---------------------------------------------------------------------
   Motion restraint: everything above is off under reduced motion.
   --------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .ledger-row {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
  .urgency.is-due > .urgency-fill {
    animation: none !important;
  }
  .wax-check.is-done {
    animation: none !important;
  }
  * {
    scroll-behavior: auto !important;
  }
}

/* ---------------------------------------------------------------------
   /wall kiosk (M3 stage 3) -- always dark via html.wall-dark (see
   templates/wall.html), independent of the device's own light/dark
   setting: html.wall-dark has higher specificity than the
   prefers-color-scheme media query above, so it wins regardless. Sized
   for a Tab A9+ 1920x1200 landscape panel at arm's length -- large type,
   a 75px tap target, burn-in-safe slow drift + idle dim.
   --------------------------------------------------------------------- */
html.wall-dark {
  color-scheme: dark;
  --bg: #171512;
  --fg: #e8e2d4;
  --accent: #c9603f;
  --accent-ink: #171512;
  --brass: #b08b4f;
  --muted: #a89a80;
  --card-bg: #201d18;
  --border: #3a342a;
  --hairline: rgba(232, 226, 212, 0.14);
  --shadow: rgba(0, 0, 0, 0.4);
  --urgency-low: var(--brass);
  --urgency-high: #e07a52;
  /* The kiosk is always dark, so it takes the dark claim hues regardless
     of the tablet's own light/dark setting -- same rule as every other
     token in this block. */
  --claim-a: #8d9ae8;
  --claim-b: #5fc2b8;
}

.wall-shell {
  min-height: 100vh;
  padding: 28px 40px;
  transition: transform 6s linear, filter 1.2s ease;
}

/* Burn-in mitigation: a few px of slow position drift, alternated each
   time the row list refreshes (poll or check-off -- see templates/wall.html
   JS). The Tab A9+ is LCD (low burn-in risk) but this is free insurance
   and matches the design brief. */
.wall-shell.wall-drift-a {
  transform: translate(0, 0);
}

.wall-shell.wall-drift-b {
  transform: translate(8px, -6px);
}

body.wall-dimmed .wall-shell {
  filter: brightness(0.6);
}

.wall-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 24px;
}

.wall-date {
  font-family: var(--font-display);
  font-size: 2.6rem;
  margin: 0;
}

.wall-clock {
  font-family: var(--font-body);
  font-size: 1.6rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.wall-banner-stale {
  margin-bottom: 20px;
  font-size: 1.1rem;
  padding: 16px 20px;
}

/* M6 stage C: compact weather strip -- today hi/lo, precip if any, active
   NWS alert badges, and the dead-man stale banner (people/views.py's
   _wall_weather_context / wx.services.staleness). Deliberately its own
   block, not #wall-banner-slot -- that div is JS-managed (wall.html's
   htmx:afterSwap handler does bannerSlot.innerHTML = "") and would wipe
   this out on every row refresh. */
.wall-weather {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 14px;
  margin-bottom: 20px;
  font-family: var(--font-body);
  font-size: 1.1rem;
  color: var(--muted);
}

.wall-weather-hilo {
  font-family: var(--font-display);
  font-size: 1.5rem;
  color: var(--fg);
  font-variant-numeric: tabular-nums;
}

.wall-weather-precip {
  color: var(--muted);
}

.wall-alert-badge {
  display: inline-flex;
  align-items: center;
  padding: 0.2em 0.9em;
  border-radius: 999px;
  font-weight: 700;
  font-size: 0.95rem;
  background: #6b0f0f;
  color: #f7ece4;
}

.wall-weather-stale {
  color: var(--brass);
  font-weight: 700;
}

.wall-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.wall-row {
  display: flex;
  align-items: center;
  gap: 24px;
  min-height: 96px;
  padding: 14px 8px;
  border-bottom: 1px solid var(--hairline);
}

.wall-row-main {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.wall-chore-name {
  font-family: var(--font-display);
  font-size: 2.5rem;
  font-weight: 600;
  line-height: 1.1;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.wall-owner-chip {
  align-self: flex-start;
  font-size: 1.1rem;
  padding: 0.2em 0.9em;
}

/* 0.7.1 plan item 1: same shared "Last done ..." line as the phone rows,
   sized for the kiosk's much larger type scale. */
.wall-last-done {
  display: block;
  font-size: 1.1rem;
  color: var(--muted);
}

.wall-urgency {
  height: 10px;
  max-width: 480px;
}

.wall-check-form {
  flex: 0 0 auto;
}

.wall-check.wax-check {
  width: 75px;
  height: 75px;
  border-width: 3px;
}

.wall-check svg {
  width: 36px;
  height: 36px;
}

.wall-overflow {
  text-align: center;
  color: var(--muted);
  font-family: var(--font-display);
  font-style: italic;
  font-size: 1.3rem;
  padding: 12px;
}

.wall-empty {
  padding: 25vh 20px;
  font-size: 2rem;
}

@media (prefers-reduced-motion: reduce) {
  .wall-shell {
    transition: none !important;
  }
}

/* ---------------------------------------------------------------------
   0.7.1 plan item 2 -- detail-page back/parent link. Sits above the
   type-kicker so it reads as "how I got here" before "what this is".
   --------------------------------------------------------------------- */
.back-link {
  margin: 0 0 8px 0;
  font-size: 0.85rem;
}

.back-link a {
  color: var(--muted);
  text-decoration: none;
}

.back-link a:hover,
.back-link a:focus-visible {
  color: var(--fg);
  text-decoration: underline;
}

/* ---------------------------------------------------------------------
   0.7.1 plan items 12/13 -- Schedule cadence chips + Schedule/Today
   assignee focus filter. Plain toggle-link chips (not the chip-toggle
   checkbox pattern used in the chore-edit form -- these navigate via a
   plain href, so state lives in the URL and stays bookmarkable).
   --------------------------------------------------------------------- */
.filter-chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 14px;
}

.filter-chip {
  display: inline-flex;
  align-items: center;
  padding: 0.3em 0.85em;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--card-bg);
  color: var(--fg);
  font-size: 0.82rem;
  font-weight: 700;
  text-decoration: none;
  white-space: nowrap;
}

.filter-chip.is-active {
  background: color-mix(in srgb, var(--accent) 22%, var(--card-bg));
  border-color: var(--accent);
  color: var(--fg);
}

.filter-chip:hover,
.filter-chip:focus-visible {
  filter: brightness(1.06);
}

/* ---------------------------------------------------------------------
   0.7.1 plan item 16 -- shared "Processing..." button state (static/js/
   processing.js) + the shared dismissible error banner every
   hx-processing-form's failure writes into (base_app.html).
   --------------------------------------------------------------------- */
.is-processing {
  opacity: 0.55;
  cursor: not-allowed;
}

.action-error-banner {
  position: fixed;
  top: calc(env(safe-area-inset-top, 0px) + 10px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 6;
  display: flex;
  align-items: center;
  gap: 10px;
  max-width: calc(100vw - 32px);
  padding: 10px 14px;
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--accent) 16%, var(--card-bg));
  border: 1px solid var(--accent);
  color: var(--accent);
  font-size: 0.85rem;
  font-weight: 700;
  box-shadow: 0 2px 10px var(--shadow);
}

.action-error-banner[hidden] {
  display: none;
}

.action-error-dismiss {
  all: unset;
  cursor: pointer;
  font-size: 1.15rem;
  line-height: 1;
  padding: 0 2px;
}

/* ---------------------------------------------------------------------
   0.7.1 plan items 5/7/8 -- More page DND status line + "Your devices"
   collapsible list + honest push-device count.
   --------------------------------------------------------------------- */
.dnd-status,
.push-device-count {
  font-weight: 700;
}

.device-list-details {
  margin-top: 14px;
}

.device-list-details summary {
  cursor: pointer;
}

.device-list-details .ledger-list {
  margin-top: 8px;
}

/* ---------------------------------------------------------------------
   Deputy row states (amended D-LLM4) -- inbox rows' "Draft now" section
   (templates/inbox/_item_row.html). Status text sits in the row's muted
   register; the failed state is LOUD (accent, bold -- same voice as
   .claim-error) per the fail-loudly rule: a failed draft must never
   read like nothing happened.
   --------------------------------------------------------------------- */
/* Fixed narrow Deputy column (Kaijen 2026-08-22): the status must never
   crowd the note text out of the row. Labels are short; details live
   behind the cell's deep links (chore page / admin draft page). */
.deputy-cell {
  flex: 0 0 92px;
  width: 92px;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
}

.deputy-cell .btn {
  font-size: 0.8rem;
  padding: 4px 8px;
}

.deputy-status {
  color: var(--muted);
  font-size: 0.8rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}

a.deputy-status {
  text-decoration: underline;
}

.deputy-status-failed {
  color: var(--accent);
  font-weight: 700;
}

/* ---------------------------------------------------------------------
   Retire/revive (Kaijen 2026-09-04): retire disclosure on chore_detail,
   retired-chores section on the directory, and the post-revive
   "check the date" notice on occurrence_detail. */
.retire-chore,
.retired-chores {
  margin: 12px 0;
}

.retire-chore summary,
.retired-chores summary {
  cursor: pointer;
  color: var(--muted);
}

.retire-chore .subtitle {
  margin: 8px 0;
}

/* 0.9.3: the post-save schedule-edit confirmation on chore detail reads
   exactly like the revived notice -- same "here is what just happened to
   your open work" job, so same shape. */
.revived-notice,
.schedule-saved-notice {
  margin: 8px 0;
  padding: 8px 12px;
  border-left: 3px solid var(--accent);
  font-size: 0.9rem;
}

/* 0.9.3 (D-S1 Option B): the pre-save preview above the editable
   schedule section -- what a save will do to open occurrences, computed
   server-side from the same predicate the save uses. */
.schedule-edit-preview {
  margin: 8px 0 12px;
  padding: 8px 12px;
  border-left: 3px solid var(--brass);
  font-size: 0.9rem;
}

.schedule-edit-preview p {
  margin: 4px 0;
}

/* ---------------------------------------------------------------------
   In-app Deputy draft review (2026-09-15, ratified roadmap item 3): the
   "Deputy review" panel on a DRAFT chore's detail page -- the Deputy's
   case, its open questions with answer boxes, and promote / redraft /
   drop. Card-shaped so it reads as a decision to make, not more spec
   text; the "redrafting" line is the honest in-flight state (fail
   loudly: never buttons that would race the worker). */
.deputy-review {
  margin: 16px 0;
  padding: 12px 14px;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-left: 3px solid var(--brass);
  border-radius: 4px;
}

.deputy-review h3 {
  margin-top: 0;
}

.deputy-review-pending {
  margin: 8px 0;
  color: var(--muted);
}

.deputy-rationale {
  font-size: 0.92rem;
}

/* 1.0.2: an escalated draft (photo case) -- the Claude-queue callout and
   the item's photos, larger than the inbox row's thumbnails so the
   reviewer can actually see them. */
.deputy-escalation {
  margin: 8px 0;
  padding: 8px 10px;
  border-left: 3px solid var(--accent);
  background: var(--bg);
}

.deputy-escalation .inbox-photo-thumb {
  max-height: 160px;
  max-width: 100%;
}

.deputy-question {
  margin: 12px 0;
}

.deputy-question-text {
  margin: 0 0 4px;
  font-weight: 700;
}

.deputy-question textarea {
  width: 100%;
  box-sizing: border-box;
}

.deputy-review-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 12px 0 4px;
}

.deputy-drop {
  margin-top: 12px;
}

/* Photo upload progress (Kaijen 2026-09-16): moving-or-stalled must be
   visible, never a mystery pause. */
.upload-progress {
  position: relative;
  margin-top: 6px;
  height: 18px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg);
  overflow: hidden;
}

.upload-progress[hidden] {
  display: none;
}

.upload-progress-fill {
  position: absolute;
  inset: 0 auto 0 0;
  width: 0%;
  background: var(--brass);
  opacity: 0.45;
  transition: width 0.3s ease;
}

.upload-progress-label {
  position: relative;
  display: block;
  text-align: center;
  font-size: 0.75rem;
  line-height: 18px;
  color: var(--muted);
  white-space: nowrap;
}

/* ---------------------------------------------------------------------
   1.1.0 (inbox #74 + the phone-width bug in inbox #99's screenshot):
   inbox rows and groups.

   Rows: the 0.9.2 occurrence-row approach (.ledger-row-occ) -- explicit
   grid tracks instead of one flex line. The note text gets the flexible
   track; chips + the 92px Deputy cell + Drop live in ONE child,
   .inbox-row-actions, in a fixed auto track beside it. At phone width the
   grid drops to a single column and the action cluster wraps BELOW the
   text, so a Captured chip + Draft now + Drop can never squeeze the note
   into one word per line again.
   --------------------------------------------------------------------- */
.ledger-row-inbox {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  column-gap: 12px;
  row-gap: 8px;
}

.inbox-row-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: 8px;
  min-width: 0;
}

@media (max-width: 430px) {
  .ledger-row-inbox {
    grid-template-columns: minmax(0, 1fr);
  }

  .inbox-row-actions {
    justify-content: flex-start;
  }
}

.inbox-group {
  margin-bottom: 20px;
}

.inbox-group-head {
  margin: 12px 0 6px 0;
}

.inbox-group-count {
  color: var(--muted);
}

.inbox-done > summary {
  cursor: pointer;
}

/* 1.1.0 (inbox #80): the Deputy's proposed change, shown beside the chore
   edit form it would be applied through (never auto-applied). */
.deputy-apply-proposal {
  margin: 12px 0;
  padding: 10px 12px;
  border: 1px solid var(--brass);
  border-radius: 6px;
  background: color-mix(in srgb, var(--brass) 8%, var(--card-bg));
}

.deputy-apply-proposal .deputy-apply-changes {
  white-space: pre-wrap;
}
