/* ============================================================================
   UrbanMiner — design tokens
   The look is a workshop ledger: warm paper, deep forest ink, a single gold
   accent. Everything else is greyscale so money (green/red) is the only other
   colour that ever carries meaning.
   ========================================================================= */
:root {
  /* Surfaces */
  --bg: #f2f0ea;
  --card: #ffffff;
  --card-2: #fbfaf7; /* nested panels, table stripes */
  --line: #e4e1d8;
  --line-strong: #d3cfc2;

  /* Text */
  --ink: #1a1f1c;
  --ink-2: #454b47; /* body copy that isn't a heading */
  --muted: #6b716c;
  --eyebrow: #767d76; /* small uppercase section labels */

  /* Brand */
  --brand: #2f6b4f;
  --brand-hi: #3b8462;
  --brand-ink: #ffffff;
  --brand-wash: #eaf2ed; /* tinted fill behind brand-coloured things */
  --accent: #d99e2b;
  --bar: #1f2a24;
  --bar-ink: #f3f5f2;
  /* The headline tile. In light mode it is the darkest thing on the page; in dark mode it
     has to go the other way, or an "elevated" card ends up darker than the page behind it. */
  --hero-bg: #1f2a24;
  --hero-edge: #1f2a24;
  --hero-ink: #f3f5f2;
  --hero-gain: #6fd6a0;
  --hero-loss: #ff8b80;

  /* Money */
  --gain: #1c7a4b;
  --loss: #b0342c;
  --warn: #9c6310;

  /* Status */
  --s-acquired: #56658a;
  --s-rehab: #9c6310;
  --s-listed: #2b649e;
  --s-sold: #1c7a4b;
  --s-scrapped: #695586;
  --s-loss: #b0342c;
  --s-kept: #6b716c;

  /* Shape */
  --radius: 14px;
  --radius-sm: 10px;
  --radius-lg: 20px;
  --radius-pill: 999px;

  /* Elevation — barely there; the hairline does most of the work. */
  --shadow-1: 0 1px 2px rgb(26 31 28 / 0.05), 0 1px 3px rgb(26 31 28 / 0.05);
  --shadow-2: 0 2px 4px rgb(26 31 28 / 0.06), 0 8px 20px rgb(26 31 28 / 0.09);
  --shadow-3:
    0 -2px 8px rgb(26 31 28 / 0.06), 0 -16px 40px rgb(26 31 28 / 0.14);
  --ring: 0 0 0 3px color-mix(in srgb, var(--brand) 32%, transparent);

  /* Rhythm */
  --gap: 12px;
  --pad: 16px;
  --tab-h: 62px;
  --tap: 44px; /* minimum comfortable touch target */
  --safe-b: env(safe-area-inset-bottom, 0px);
  --rail-w: 232px;

}

/* Dark mode is an attribute, not a media query, so it can be chosen as well as followed:
   the inline script in index.html resolves "auto" against the system setting before first
   paint and keeps it in step, and the Appearance picker can pin light or dark. */
:root {
  color-scheme: light;
}
:root[data-mode="dark"] {
  color-scheme: dark;
  --bg: #101311;
  --card: #191d1b;
  --card-2: #1f2421;
  --line: #2b322e;
  --line-strong: #3a423d;

  --ink: #e9ece9;
  --ink-2: #c3c9c4;
  --muted: #949c96;
  --eyebrow: #8b938c;

  --brand: #56a97e;
  --brand-hi: #66bc8f;
  --brand-ink: #08130d;
  --brand-wash: #17251e;
  --accent: #e0ad45;
  --bar: #0b0e0c;
  --bar-ink: #e9ece9;
  --hero-bg: #223029;
  --hero-edge: #33473b;
  --hero-ink: #f1f5f2;
  --hero-gain: #79e0ad;
  --hero-loss: #ff9a90;

  --gain: #55c58c;
  --loss: #f0736a;
  --warn: #d3a04a;

  --s-acquired: #7d8db4;
  --s-rehab: #c08b3a;
  --s-listed: #5a95cd;
  --s-sold: #3f9e6c;
  --s-scrapped: #907cad;
  --s-loss: #c9524a;
  --s-kept: #7e857f;

  --shadow-1: 0 1px 2px rgb(0 0 0 / 0.3);
  --shadow-2: 0 2px 6px rgb(0 0 0 / 0.4), 0 12px 28px rgb(0 0 0 / 0.4);
  --shadow-3: 0 -12px 40px rgb(0 0 0 / 0.5);
}

/* ============================================================================
   Palettes. Each one re-voices the brand, the accent and the chrome only: money
   (gain/loss) and the status colours keep their meaning in every theme, so a
   red number never has to be re-learned.
   ========================================================================= */
/* Harbor — slate blue and a coral accent. */
:root[data-theme="harbor"]:not([data-mode="dark"]) {
  --bg: #eef1f2;
  --card-2: #f7f9f9;
  --line: #dde3e5;
  --line-strong: #c9d1d4;
  --brand: #1f6472;
  --brand-hi: #287b8b;
  --brand-wash: #e4f0f2;
  --accent: #e0774f;
  --bar: #172a31;
  --hero-bg: #172a31;
  --hero-edge: #172a31;
}
:root[data-theme="harbor"][data-mode="dark"] {
  --bg: #0e1416;
  --card: #161e21;
  --card-2: #1b2528;
  --line: #26343a;
  --line-strong: #34454b;
  --brand: #4fb0c0;
  --brand-hi: #66c3d2;
  --brand-ink: #061417;
  --brand-wash: #142429;
  --accent: #f08d66;
  --bar: #0a1012;
  --hero-bg: #1a2c32;
  --hero-edge: #2b4750;
}
/* Workshop — charcoal steel and a safety-orange accent. */
:root[data-theme="workshop"]:not([data-mode="dark"]) {
  --bg: #efefed;
  --card-2: #f8f8f6;
  --line: #e0e0dc;
  --line-strong: #cbcbc5;
  --brand: #37475a;
  --brand-hi: #465a72;
  --brand-wash: #e9edf2;
  --accent: #ec8522;
  --bar: #1d232b;
  --hero-bg: #1d232b;
  --hero-edge: #1d232b;
}
:root[data-theme="workshop"][data-mode="dark"] {
  --bg: #111315;
  --card: #1a1d20;
  --card-2: #202428;
  --line: #2c3136;
  --line-strong: #3b4248;
  --brand: #8fa7c2;
  --brand-hi: #a6bcd4;
  --brand-ink: #0b0f14;
  --brand-wash: #1c232b;
  --accent: #f59a3c;
  --bar: #0b0d0f;
  --hero-bg: #232a32;
  --hero-edge: #36414d;
}
/* Plum — a deep aubergine with a brass accent. */
:root[data-theme="plum"]:not([data-mode="dark"]) {
  --bg: #f2eff2;
  --card-2: #faf8fa;
  --line: #e5dfe5;
  --line-strong: #d3cad3;
  --brand: #5d3a6e;
  --brand-hi: #714a85;
  --brand-wash: #f0e8f3;
  --accent: #c9972e;
  --bar: #251a2b;
  --hero-bg: #251a2b;
  --hero-edge: #251a2b;
}
:root[data-theme="plum"][data-mode="dark"] {
  --bg: #121014;
  --card: #1b181e;
  --card-2: #221e26;
  --line: #2f2934;
  --line-strong: #3f3746;
  --brand: #bb93d0;
  --brand-hi: #cba8de;
  --brand-ink: #150d19;
  --brand-wash: #251d2b;
  --accent: #e0b04c;
  --bar: #0d0b0f;
  --hero-bg: #2a2130;
  --hero-edge: #43354c;
}

/* ============================================================================
   Neutral — between light and dark. Mid-tone paper with softened cards: less glare
   than Light, still dark text on light-ish surfaces so nothing has to be re-learned.
   Money and warn colours are deepened a step so they keep their contrast on the dimmer
   cards. Each palette re-tints the surfaces toward its own hue.
   ========================================================================= */
:root[data-mode="neutral"] {
  color-scheme: light;
  --bg: #bdb9ae;
  --card: #d8d5cc;
  --card-2: #cfcbc1;
  --line: #aaa69b;
  --line-strong: #979387;
  --ink: #141815;
  --ink-2: #2c312d;
  --muted: #464b47;
  --eyebrow: #4a504b;
  --brand: #224f3a;
  --brand-hi: #2a5f46;
  --brand-wash: #c6cfc6;
  --gain: #146238;
  --loss: #9a2a23;
  --warn: #7a4c08;
  --shadow-1: 0 1px 2px rgb(20 24 21 / 0.1), 0 1px 3px rgb(20 24 21 / 0.08);
  --shadow-2: 0 2px 4px rgb(20 24 21 / 0.12), 0 8px 20px rgb(20 24 21 / 0.14);
}
:root[data-theme="harbor"][data-mode="neutral"] {
  --bg: #b2bdc0;
  --card: #d0d8da;
  --card-2: #c6cfd2;
  --line: #9eaaae;
  --line-strong: #8b989c;
  --brand: #154754;
  --brand-hi: #1a5664;
  --brand-wash: #bcccd0;
  --accent: #c9623c;
}
:root[data-theme="workshop"][data-mode="neutral"] {
  --bg: #b7b8b6;
  --card: #d4d5d2;
  --card-2: #cacbc8;
  --line: #a3a4a1;
  --line-strong: #90918e;
  --brand: #2f3d4e;
  --brand-hi: #37475a;
  --brand-wash: #c3c9d0;
  --accent: #d4731a;
}
:root[data-theme="plum"][data-mode="neutral"] {
  --bg: #c1b9c3;
  --card: #d9d1da;
  --card-2: #cfc6d0;
  --line: #a99fab;
  --line-strong: #968b98;
  --brand: #4f2f5e;
  --brand-hi: #5d3a6e;
  --brand-wash: #cdbfd3;
  --accent: #b5841f;
}

/* ============================================================================
   Base
   ========================================================================= */
* {
  box-sizing: border-box;
}
[hidden] {
  display: none !important;
}

html {
  -webkit-text-size-adjust: 100%;
}
html,
body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font:
    16px/1.45 system-ui,
    -apple-system,
    "Segoe UI",
    Roboto,
    sans-serif;
  -webkit-tap-highlight-color: transparent;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overscroll-behavior-y: none;
}
body {
  min-height: 100dvh;
}

a {
  color: var(--brand);
  text-underline-offset: 2px;
}
button,
input,
select,
textarea {
  font: inherit;
  color: inherit;
}
button {
  touch-action: manipulation;
}
h1,
h2,
h3 {
  letter-spacing: -0.011em;
}
p {
  color: var(--ink-2);
}
hr {
  border: 0;
  border-top: 1px solid var(--line);
  margin: 14px 0;
}

/* Icons live at the text's own size and take its colour. */
.ico {
  width: 1.35em;
  height: 1.35em;
  display: block;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}
/* The duotone layer: a faint fill under the outline, which is what makes these read as
   UrbanMiner's own set rather than a stock line-icon pack. */
.ico .d {
  fill: currentColor;
  stroke: none;
  opacity: 0.16;
}
/* An icon sitting in a line of text or a button, where an emoji used to be. */
.ico.inline {
  display: inline-block;
  width: 1.2em;
  height: 1.2em;
  vertical-align: -0.24em;
  flex: none;
}
.svg-defs {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

:focus-visible {
  outline: none;
  box-shadow: var(--ring);
  border-radius: var(--radius-sm);
}

/* ============================================================================
   Top bar
   ========================================================================= */
.topbar {
  position: sticky;
  top: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: calc(env(safe-area-inset-top, 0px) + 12px) var(--pad) 12px;
  background: var(--bar);
  color: var(--bar-ink);
}
.topbar h1 {
  font-size: 17px;
  font-weight: 650;
  margin: 0;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.topbar-spacer {
  flex: 1;
}
.icon-btn {
  display: grid;
  place-items: center;
  flex: none;
  width: 36px;
  height: 36px;
  margin-left: -8px;
  background: none;
  border: 0;
  color: inherit;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background 0.12s ease;
}
.icon-btn:active {
  background: rgb(255 255 255 / 0.12);
}
.icon-btn .ico {
  width: 24px;
  height: 24px;
  stroke-width: 2;
}

.ai-pill {
  flex: none;
  border: 0;
  border-radius: var(--radius-pill);
  padding: 7px 13px;
  font-size: 13px;
  font-weight: 600;
  background: rgb(255 255 255 / 0.13);
  color: var(--bar-ink);
  cursor: pointer;
  white-space: nowrap;
  transition:
    background 0.15s ease,
    transform 0.15s ease;
}
.ai-pill:active {
  transform: scale(0.97);
}
/* Keeps beating until the result is actually opened. Three fixed pulses meant that if she
   happened to be looking at the list rather than the header for those five seconds, the one
   thing she was waiting for announced itself and then went quiet. Slower than the old pulse,
   because something that repeats forever has to be calmer than something that repeats three
   times. Reduced motion stops it entirely, via the global rule. */
.ai-pill.ready {
  background: var(--accent);
  color: #1c211e;
  animation: pulse 2.4s ease-in-out infinite;
}
@keyframes pulse {
  50% {
    transform: scale(1.06);
  }
}

/* ============================================================================
   Page shell
   ========================================================================= */
main {
  max-width: 760px;
  margin: 0 auto;
  padding: var(--pad) var(--pad) calc(var(--tab-h) + var(--safe-b) + 32px);
}

/* ============================================================================
   Bottom tab bar (mobile) — becomes a side rail on wide screens, further down.
   ========================================================================= */
.tabbar {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 30;
  display: flex;
  justify-content: space-around;
  align-items: center;
  height: calc(var(--tab-h) + var(--safe-b));
  padding-bottom: var(--safe-b);
  background: color-mix(in srgb, var(--card) 88%, transparent);
  backdrop-filter: saturate(180%) blur(14px);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
  border-top: 1px solid var(--line);
}
.tabbar a {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  min-height: var(--tap);
  justify-content: center;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.005em;
  color: var(--muted);
  text-decoration: none;
  transition: color 0.15s ease;
}
.tabbar a span {
  display: grid;
  place-items: center;
}
.tabbar a .ico {
  width: 23px;
  height: 23px;
}
.tabbar a.active {
  color: var(--brand);
  font-weight: 650;
}
.tabbar a.active .ico {
  stroke-width: 2;
}


.tabbar .tab-add {
  flex: none;
}
.tabbar .tab-add span {
  width: 54px;
  height: 54px;
  border-radius: 50%;
  margin-top: -20px;
  background: var(--brand);
  color: var(--brand-ink);
  box-shadow: 0 6px 16px color-mix(in srgb, var(--brand) 42%, transparent);
  transition:
    transform 0.15s ease,
    box-shadow 0.15s ease;
}
.tabbar .tab-add .ico {
  width: 26px;
  height: 26px;
  stroke-width: 2.2;
}
.tabbar .tab-add:active span {
  transform: scale(0.94);
}
.tabbar .tab-add.active span {
  background: var(--brand-hi);
}

/* ============================================================================
   Cards & section headings
   ========================================================================= */
.card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 14px;
  margin-bottom: var(--gap);
  box-shadow: var(--shadow-1);
}
/* Three weights, not one. A screen that stacks nine identical cards has no focal point, so
   supporting detail drops to `.quiet` — no border, no shadow, sitting straight on the page —
   and the one thing worth reading first can take `.hero`. */
.card.quiet {
  background: none;
  border-color: transparent;
  box-shadow: none;
  padding-inline: 0;
  border-bottom: 1px solid var(--line);
  border-radius: 0;
}
.card.hero {
  background: var(--hero-bg);
  border-color: var(--hero-edge);
  color: var(--hero-ink);
}
.card.hero h2 {
  color: color-mix(in srgb, var(--hero-ink) 62%, transparent);
}

/* Status is the whole model of this app and it lived only inside a 10.5px badge. A spine on
   the item's header card says which state it is in before anything has been read. */
.card[data-status] {
  border-left: 3px solid var(--s-kept);
}
.card[data-status="acquired"] {
  border-left-color: var(--s-acquired);
}
.card[data-status="rehab"] {
  border-left-color: var(--s-rehab);
}
.card[data-status="listed"] {
  border-left-color: var(--s-listed);
}
.card[data-status="sold"] {
  border-left-color: var(--s-sold);
}
.card[data-status="scrapped"] {
  border-left-color: var(--s-scrapped);
}
.card[data-status="loss"] {
  border-left-color: var(--s-loss);
}

/* The one card on the dashboard that is a to-do list rather than a report. The accent is
   defined for exactly this kind of thing and was, until now, spent in a single place. */
.card.attention {
  border-left: 3px solid var(--accent);
}
.card.attention h2 {
  color: color-mix(in srgb, var(--accent) 72%, var(--ink));
}

.card > :first-child {
  margin-top: 0;
}
.card > :last-child {
  margin-bottom: 0;
}
.card h2 {
  font-size: 11.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.085em;
  color: var(--eyebrow);
  margin: 0 0 10px;
  display: flex;
  align-items: center;
  gap: 6px;
}
.card h3 {
  font-size: 15px;
  font-weight: 650;
  margin: 0 0 8px;
}
/* Some panels save on change and structurally identical ones want a Save button. Until they
   agree, the ones that save themselves say so. */
.saves-itself {
  margin: 10px 0 0;
  font-size: 12px;
  color: var(--muted);
}
.saves-itself::before {
  content: "✓ ";
  color: var(--gain);
  font-weight: 700;
}

/* Settings was eleven accordions in one undifferentiated run. These are the four groups. */
.settings-group {
  font-size: 11.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.085em;
  color: var(--eyebrow);
  margin: 22px 0 8px 2px;
}
.settings-group:first-child {
  margin-top: 4px;
}

/* Says a card ignores the dashboard's date range. Without it, switching the range to "30
   days" changed some numbers on the page and left others alone with no explanation. */
.eyebrow-note {
  margin-left: auto;
  font-weight: 500;
  text-transform: none;
  letter-spacing: 0;
  font-size: 11px;
  color: var(--muted);
}

/* ============================================================================
   Layout helpers
   ========================================================================= */
.row {
  display: flex;
  gap: 10px;
  align-items: center;
}
.row.between {
  justify-content: space-between;
}
.wrap {
  flex-wrap: wrap;
}
.grow {
  flex: 1;
  min-width: 0;
}
.muted {
  color: var(--muted);
}
.small {
  font-size: 13px;
}
.gain {
  color: var(--gain);
}
.loss {
  color: var(--loss);
}
.num {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}
.center {
  text-align: center;
}
.stack > * + * {
  margin-top: 10px;
}

/* ============================================================================
   KPIs
   ========================================================================= */
/* The headline number takes the full first row; the three supporting metrics share the
   second one. Each tile is a column with the value pinned to the bottom, so the numbers
   line up across the row even when a label wraps to two lines. */
.kpis {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--gap);
  margin-bottom: var(--gap);
}
.kpi {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 6px;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 12px 14px;
  box-shadow: var(--shadow-1);
}
.kpi .label {
  font-size: 11.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--eyebrow);
}
.kpi .value {
  font-size: 19px;
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -0.025em;
  font-variant-numeric: tabular-nums;
}
.kpi .value.blank {
  color: var(--muted);
  font-weight: 500;
}
/* The headline number gets the full row and a size that says "read me first". */
.kpi.hero {
  grid-column: 1 / -1;
  background: var(--hero-bg);
  border-color: var(--hero-edge);
  color: var(--hero-ink);
}
.kpi.hero .label {
  color: color-mix(in srgb, var(--hero-ink) 62%, transparent);
}
.kpi.hero .value {
  font-size: 36px;
  letter-spacing: -0.03em;
}
.kpi.hero .value .gain {
  color: var(--hero-gain);
}
.kpi.hero .value .loss {
  color: var(--hero-loss);
}

/* ============================================================================
   Chips
   ========================================================================= */
.chips {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  padding-bottom: 4px;
  margin-bottom: var(--gap);
  scrollbar-width: none;
  scroll-snap-type: x proximity;
  -webkit-overflow-scrolling: touch;
}
.chips::-webkit-scrollbar {
  display: none;
}
.chips > * {
  scroll-snap-align: start;
}
.chip {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: 38px;
  padding: 8px 14px;
  border: 1px solid var(--line-strong);
  background: var(--card);
  border-radius: var(--radius-pill);
  font-size: 14px;
  font-weight: 550;
  color: var(--ink);
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
  transition:
    background 0.14s ease,
    color 0.14s ease,
    border-color 0.14s ease;
}
.chip:active {
  transform: scale(0.98);
}
.chip.active {
  background: var(--ink);
  color: var(--bg);
  border-color: var(--ink);
  box-shadow: inset 0 -2px 0 color-mix(in srgb, var(--accent) 70%, transparent);
}
/* How many items sit behind a filter. Quiet by design: it's a hint that there's something
   to look at elsewhere, never the thing the eye lands on first. */
.chip-count {
  min-width: 17px;
  padding: 0 5px;
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--ink) 9%, transparent);
  color: var(--muted);
  font-size: 11.5px;
  font-weight: 650;
  line-height: 17px;
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.chip.active .chip-count {
  background: color-mix(in srgb, var(--bg) 28%, transparent);
  color: var(--bg);
}
a.chip {
  text-decoration: none;
}

/* Three equal columns so the choices read as one control instead of wrapping the last
   option onto a line of its own. */
.goal-chips {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  margin: 4px 0;
}
.goal-field {
  margin-bottom: var(--gap);
}
.goal-field > span {
  display: block;
  font-size: 13px;
  font-weight: 550;
  color: var(--muted);
  margin-bottom: 6px;
}
.goal-chips .chip {
  position: relative;
  min-height: var(--tap);
  padding: 10px 8px;
  font-size: 13.5px;
}
.goal-chips input[type="radio"] {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  pointer-events: none;
}
.goal-chips .chip:has(input:checked) {
  background: var(--ink);
  color: var(--bg);
  border-color: var(--ink);
}
.goal-chips .chip:has(input:focus-visible) {
  box-shadow: var(--ring);
}

/* Selling sites as a list: the site toggle on the left, the day it went up there on the
   right. Rows that aren't picked stay quiet so the plan reads at a glance. */
.plat-head,
.plat-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 11.5em;
  align-items: center;
  gap: 10px;
}
.plat-head {
  padding: 0 2px 4px;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.plat-head span:last-child {
  padding-left: 2px;
}
.plat-list {
  list-style: none;
  margin: 0;
  padding: 0;
  border-top: 1px solid var(--line);
}
.plat-row {
  padding: 6px 2px;
  border-bottom: 1px solid var(--line);
}
/* Long site names ("Facebook Marketplace (shipping)") wrap inside the pill rather than
   being cut off, so every row still says which site it is. */
.plat-row .chip {
  justify-self: start;
  max-width: 100%;
  white-space: normal;
  text-align: left;
  justify-content: flex-start;
  line-height: 1.25;
  border-radius: 18px;
}
.plat-row input[type="date"] {
  height: 40px;
  padding: 6px 10px;
  font-size: 15px;
}
.plat-row [data-plat-off] {
  padding-left: 2px;
}
@media (max-width: 360px) {
  .plat-head,
  .plat-row {
    grid-template-columns: minmax(0, 1fr) 10em;
  }
}

/* The extra analyses an AI lookup can include. Off by default, so they read as optional
   add-ons beside the button rather than part of it. */
.ai-extras {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin: 8px 0;
}
.ai-extras > span {
  font-size: 13px;
  font-weight: 550;
  color: var(--muted);
  margin-right: 2px;
}
.ai-extras .chip {
  position: relative;
  min-height: 34px;
  padding: 6px 12px;
  font-size: 13px;
}
.ai-extras input[type="checkbox"] {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}
.ai-extras .chip:has(input:checked) {
  background: var(--ink);
  color: var(--bg);
  border-color: var(--ink);
}
.ai-extras .chip:has(input:focus-visible) {
  box-shadow: var(--ring);
}

/* ============================================================================
   List toolbar (search + filters + sort)
   ========================================================================= */
.toolbar {
  margin-bottom: var(--gap);
}
.search-field {
  position: relative;
  margin-bottom: 10px;
}
.search-field .ico {
  position: absolute;
  left: 13px;
  top: 50%;
  margin-top: -10px;
  width: 19px;
  height: 19px;
  color: var(--muted);
  pointer-events: none;
}
.search-field input {
  padding-left: 40px;
}
.search-field input::-webkit-search-cancel-button {
  -webkit-appearance: none;
}

.filter-row {
  margin-bottom: 8px;
  padding-bottom: 6px;
}
/* A <select> dressed as a chip: same pill, same height, plus room for the arrow. */
.select-chip {
  width: auto;
  height: 38px;
  min-height: 38px;
  flex: none;
  padding: 0 32px 0 14px;
  font-size: 14px;
  font-weight: 550;
  border-radius: var(--radius-pill);
  border-color: var(--line-strong);
  background-position:
    calc(100% - 16px) 16px,
    calc(100% - 11px) 16px;
}
.select-chip.active {
  background-color: var(--ink);
  color: var(--bg);
  border-color: var(--ink);
}

/* The count and the sort share a line when there's room and stack when there isn't —
   sharing it regardless is what squeezed the sort box until its text was cut off. */
.list-meta {
  gap: 6px 10px;
  flex-wrap: wrap;
}
.sort-field {
  display: inline-flex;
  flex: none;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
}
/* Sized to its longest option and never allowed to shrink below it: every select gets
   min-width: 0 so form fields can share a grid row, and in a flex row that let this one
   collapse onto its own text. The arrow is centred rather than pinned 14px down, because
   on a touch screen the box grows to 44px and a pinned arrow sat high. */
.sort-field select {
  flex: none;
  width: auto;
  min-width: max-content;
  height: 34px;
  min-height: 34px;
  padding: 0 30px 0 10px;
  font-size: 13px;
  line-height: normal;
  border-radius: 8px;
  color: var(--ink);
  background-position:
    calc(100% - 15px) calc(50% - 1px),
    calc(100% - 10px) calc(50% - 1px);
}

.empty-mark {
  font-size: 44px;
  line-height: 1;
  opacity: 0.85;
}
.empty .btn {
  margin-top: 4px;
}

/* A small two-way switch, for when a chip row would cost a whole line of a sheet. */
.seg {
  display: inline-flex;
  flex: none;
  gap: 2px;
  padding: 2px;
  background: var(--card-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
}
.seg button {
  border: 0;
  background: none;
  padding: 6px 13px;
  min-height: 32px;
  border-radius: var(--radius-pill);
  font-size: 13px;
  font-weight: 600;
  color: var(--muted);
  cursor: pointer;
  white-space: nowrap;
  transition:
    background 0.14s ease,
    color 0.14s ease;
}
.seg button.active {
  background: var(--card);
  color: var(--ink);
  box-shadow: var(--shadow-1);
}

/* ============================================================================
   Badges & pills
   ========================================================================= */
.badge {
  display: inline-block;
  font-size: 10.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 3px 8px;
  border-radius: var(--radius-pill);
  color: #fff;
  background: var(--s-kept);
  vertical-align: 1px;
}
.badge.acquired {
  background: var(--s-acquired);
}
.badge.rehab {
  background: var(--s-rehab);
}
.badge.listed {
  background: var(--s-listed);
}
.badge.sold {
  background: var(--s-sold);
}
.badge.scrapped {
  background: var(--s-scrapped);
}
.badge.loss {
  background: var(--s-loss);
}

.pill {
  display: inline-block;
  font-size: 11.5px;
  font-weight: 650;
  padding: 3px 9px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--line-strong);
  color: var(--muted);
  text-transform: capitalize;
}
.pill.high {
  color: var(--gain);
  border-color: currentColor;
  background: color-mix(in srgb, var(--gain) 8%, transparent);
}
.pill.medium {
  color: var(--warn);
  border-color: currentColor;
  background: color-mix(in srgb, var(--warn) 8%, transparent);
}
.pill.low {
  color: var(--loss);
  border-color: currentColor;
  background: color-mix(in srgb, var(--loss) 8%, transparent);
}

/* ============================================================================
   Lists
   ========================================================================= */
.list {
  list-style: none;
  margin: 0;
  padding: 0;
}
.list-item {
  display: flex;
  gap: 12px;
  align-items: center;
  padding: 11px 0;
  border-bottom: 1px solid var(--line);
  color: inherit;
  text-decoration: none;
}
.list-item:last-child {
  border-bottom: 0;
}
/* A tappable row lights up and nudges its chevron, so it reads as a link without underlines. */
a.list-item {
  margin: 0 -6px;
  padding-inline: 6px;
  border-radius: var(--radius-sm);
  transition: background 0.14s ease;
}
a.list-item:active {
  background: var(--card-2);
}
.list li:last-child .list-item {
  border-bottom: 0;
}

.thumb {
  width: 56px;
  height: 56px;
  border-radius: 11px;
  object-fit: cover;
  background: var(--card-2);
  border: 1px solid var(--line);
  flex: none;
  display: grid;
  place-items: center;
  color: var(--muted);
  font-size: 22px;
}
.title-line {
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Item names are long and specific ("Milwaukee M18 Fuel Hammer Drill 2804-20"); one
   ellipsised line hides the part that distinguishes two similar items, so list rows
   get two. */
.title-line.clamp-2 {
  white-space: normal;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
}
/* Notes keep their line breaks, but a pasted link is one unbroken word — without
   overflow-wrap it stretches the card and the whole page scrolls sideways. */
.notes-text {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* ============================================================================
   Forms
   ========================================================================= */
label.field {
  display: block;
  margin-bottom: var(--gap);
}
label.field > span {
  display: block;
  font-size: 13px;
  font-weight: 550;
  color: var(--muted);
  margin-bottom: 5px;
}
input,
select,
textarea {
  width: 100%;
  padding: 12px;
  font-size: 16px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--card);
  color: var(--ink);
  transition:
    border-color 0.14s ease,
    box-shadow 0.14s ease;
}
input::placeholder,
textarea::placeholder {
  color: color-mix(in srgb, var(--muted) 76%, transparent);
}
textarea {
  min-height: 84px;
  resize: vertical;
  line-height: 1.5;
}
input:not([type="checkbox"]):not([type="file"]),
select {
  height: 48px;
  min-width: 0;
}
select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 36px;
  cursor: pointer;
  background-image:
    linear-gradient(45deg, transparent 50%, currentColor 50%),
    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position:
    calc(100% - 18px) 21px,
    calc(100% - 13px) 21px;
  background-size:
    5px 5px,
    5px 5px;
  background-repeat: no-repeat;
}
/* Checkboxes sit next to their label instead of taking the full row. */
input[type="checkbox"] {
  width: 20px;
  height: 20px;
  padding: 0;
  flex: none;
  accent-color: var(--brand);
  cursor: pointer;
}
input[type="date"] {
  -webkit-appearance: none;
  appearance: none;
  display: block;
}
input[type="search"] {
  -webkit-appearance: none;
  appearance: none;
}
input:focus,
select:focus,
textarea:focus {
  outline: none;
  border-color: var(--brand);
  box-shadow: var(--ring);
}
/* align-items: start matters: a grid item stretches to the tallest cell by default, so
   when the field beside it grew (a revealed "Other..." box) the shorter label stretched
   too and the $ pinned to its bottom edge slid away from its input. */
.grid2 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0 10px;
  align-items: start;
}
.grid2 > * {
  min-width: 0;
}
/* Box length × width × height on the List on eBay page. */
.grid3 {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 0 8px;
  align-items: start;
}
.grid3 > * {
  min-width: 0;
}
/* Photos left out of an eBay listing fade back. */
.ebay-list [data-pid] {
  cursor: pointer;
  transition: opacity 0.15s;
}

/* A money input carries its own $ so an amount never reads as a bare number. */
label.field.cash {
  position: relative;
}
label.field.cash > input {
  padding-left: 27px;
}
label.field.cash::after {
  content: "$";
  position: absolute;
  left: 12px;
  bottom: 0;
  height: 48px;
  display: grid;
  place-items: center;
  color: var(--muted);
  pointer-events: none;
}
/* Belt and braces for the same problem: the label never grows past its own content, so
   the $ cannot drift from the input it belongs to. */
label.field.cash {
  align-self: start;
}
/* Reveal a password. Sits inside the field rather than beside it, so the form's rhythm is
   unchanged and the control is where the thumb already is. */
label.field.has-reveal {
  position: relative;
}
label.field.has-reveal > input {
  padding-right: 62px;
}
.reveal-btn {
  position: absolute;
  right: 5px;
  bottom: 5px;
  height: 38px;
  padding: 0 11px;
  border: 0;
  background: none;
  color: var(--brand);
  font-size: 13px;
  font-weight: 650;
  cursor: pointer;
  border-radius: 8px;
}
@media (hover: hover) and (pointer: fine) {
  .reveal-btn:hover {
    background: var(--card-2);
  }
}

/* The one box that says where an "Other..." item came from. */
[data-source-detail].needs-detail > input {
  border-color: color-mix(in srgb, var(--loss) 45%, var(--line-strong));
}
[data-source-detail].needs-detail > input:focus {
  border-color: var(--brand);
}

/* ============================================================================
   Buttons
   ========================================================================= */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  min-height: var(--tap);
  padding: 11px 15px;
  border: 1px solid var(--line-strong);
  background: var(--card);
  color: var(--ink);
  border-radius: var(--radius-sm);
  font-size: 15px;
  font-weight: 600;
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
  /* Buttons are keys, not flat labels: a slightly heavier bottom edge that a press takes
     away, and 1px of travel. Quiet in a dense row, but it reads as a tool, not a form. */
  box-shadow:
    inset 0 -2px 0 color-mix(in srgb, var(--ink) 6%, transparent),
    0 1px 1px color-mix(in srgb, var(--ink) 5%, transparent);
  transition:
    background 0.14s ease,
    border-color 0.14s ease,
    box-shadow 0.1s ease,
    transform 0.1s ease,
    opacity 0.14s ease;
}
.btn:active {
  transform: translateY(1px);
  background: var(--card-2);
  box-shadow: inset 0 1px 2px color-mix(in srgb, var(--ink) 10%, transparent);
}
.btn .ico {
  width: 1.2em;
  height: 1.2em;
  flex: none;
}
.btn.primary {
  background: linear-gradient(
    180deg,
    color-mix(in srgb, var(--brand-hi) 70%, var(--brand)),
    var(--brand)
  );
  border-color: color-mix(in srgb, var(--brand) 80%, #000);
  color: var(--brand-ink);
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / 0.16),
    inset 0 -2px 0 rgb(0 0 0 / 0.16),
    0 2px 6px color-mix(in srgb, var(--brand) 26%, transparent);
}
.btn.primary:active {
  background: var(--brand);
  box-shadow: inset 0 2px 3px rgb(0 0 0 / 0.2);
}
/* AI actions. The accent marks them wherever they appear, so "this one runs a research
   lookup" is recognisable before the label is read. */
.btn.accent {
  background: linear-gradient(
    135deg,
    var(--brand-wash),
    color-mix(in srgb, var(--accent) 13%, var(--card))
  );
  color: var(--brand);
  border-color: color-mix(in srgb, var(--accent) 45%, var(--line-strong));
}
.btn.accent .ico {
  color: var(--accent);
}
.btn.accent:active {
  background: color-mix(in srgb, var(--accent) 16%, var(--card));
}
.btn.danger {
  color: var(--loss);
  border-color: color-mix(in srgb, var(--loss) 34%, var(--line-strong));
}
/* A delete inside a row is a last resort, not the point of the row: keep it quiet until
   it is actually touched, so three stacked expenses don't read as three warnings. */
.list-item > .btn.danger,
.comp > .btn.danger {
  border-color: transparent;
  background: none;
  color: var(--muted);
  min-height: 36px;
  padding: 6px 8px;
}
.list-item > .btn.danger:active,
.comp > .btn.danger:active {
  color: var(--loss);
  background: color-mix(in srgb, var(--loss) 10%, transparent);
}
.btn.danger:active {
  background: color-mix(in srgb, var(--loss) 8%, transparent);
}
.btn.block {
  width: 100%;
}
.btn.sm {
  min-height: 34px;
  padding: 6px 11px;
  font-size: 13.5px;
  border-radius: 8px;
}
.btn:disabled {
  opacity: 0.45;
  cursor: default;
}
.btn:disabled:active {
  transform: none;
}
.btn-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(104px, 1fr));
  gap: 8px;
}
/* Site names vary a lot in length ("eBay" vs "Facebook Marketplace"); inside a grid the
   cell width is fixed, so the label has to be allowed to wrap instead of overflowing. */
.btn-grid .btn {
  min-width: 0;
  white-space: normal;
  text-align: center;
  line-height: 1.25;
  padding-inline: 8px;
}

/* An action that reads as a link inside a line of text ("use details"), used where a
   full button would crowd the row. */
.link-btn {
  border: 0;
  background: none;
  padding: 0;
  width: auto;
  min-height: 0;
  font: inherit;
  font-size: inherit;
  color: var(--brand);
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}

/* ============================================================================
   Photos
   ========================================================================= */
.photos {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  margin: -2px 0 var(--gap);
}
.photos img {
  height: 160px;
  border-radius: 12px;
  flex: none;
}
.photo-add {
  height: 160px;
  width: 120px;
  flex: none;
  border: 1.5px dashed var(--line-strong);
  border-radius: 12px;
  display: grid;
  place-items: center;
  color: var(--muted);
  cursor: pointer;
  text-align: center;
  font-size: 14px;
}
.photo-strip {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  margin-bottom: var(--gap);
  align-items: center;
  scrollbar-width: none;
}
.photo-strip::-webkit-scrollbar {
  display: none;
}
.photo-strip img {
  height: 96px;
  width: 96px;
  object-fit: cover;
  border-radius: 12px;
  flex: none;
  border: 1px solid var(--line);
  cursor: pointer;
  background: var(--card-2);
}
.photo-strip .photo-add.sm {
  height: 96px;
  width: 96px;
  font-size: 13px;
}

.photo-sources {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  margin-bottom: 6px;
}
.photo-sources .photo-src {
  flex-direction: column;
  gap: 3px;
  min-height: 76px;
  border-style: dashed;
  border-color: var(--line-strong);
  background: var(--card-2);
  color: var(--muted);
  font-weight: 550;
  font-size: 20px;
}
.photo-sources .photo-src span {
  font-size: 13px;
}
.photo-sources.compact {
  margin-bottom: var(--gap);
}
.photo-sources.compact .photo-src {
  min-height: var(--tap);
  flex-direction: row;
  gap: 6px;
  font-size: 16px;
}
.photo-src-hint {
  margin: 0 0 var(--gap);
  text-align: center;
}
.paste-zone {
  grid-column: 1 / -1;
  min-height: 76px;
  padding: 12px;
  border: 1.5px dashed var(--line-strong);
  border-radius: 12px;
  color: var(--ink);
  font-size: 16px;
  overflow: hidden;
  outline: none;
  -webkit-user-select: text;
  user-select: text;
}
.paste-zone:focus {
  border-color: var(--brand);
  box-shadow: var(--ring);
}
.paste-zone:empty::before {
  content: "Long-press here and tap Paste";
  color: var(--muted);
  pointer-events: none;
}
.paste-zone[hidden] {
  display: none;
}
.btn.photo-btn {
  width: 100%;
  border-style: dashed;
  color: var(--muted);
  font-weight: 550;
  margin-bottom: var(--gap);
}

.lookup-photo {
  width: 64px;
  height: 64px;
  object-fit: cover;
  border-radius: 10px;
  flex: none;
}
.lookup-thumb {
  width: 44px;
  height: 44px;
  object-fit: cover;
  border-radius: 8px;
  flex: none;
}

/* ============================================================================
   Money tables & bar charts
   ========================================================================= */
.money {
  width: 100%;
  border-collapse: collapse;
}
.money td {
  padding: 5px 0;
  color: var(--ink-2);
}
.money td:last-child {
  text-align: right;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  white-space: nowrap;
}
.money tr.total td {
  border-top: 1px solid var(--line);
  font-weight: 700;
  padding-top: 9px;
  color: var(--ink);
}

.bars .bar-row {
  display: grid;
  grid-template-columns: minmax(0, 132px) minmax(48px, 1fr) 76px;
  gap: 9px;
  align-items: center;
  font-size: 13px;
  margin: 8px 0;
}
/* The labels this carries are the point of the chart ("2nd & Elm St. estate sale"), and a
   fixed column plus nowrap ellipsised exactly the part that distinguishes them. Two lines. */
.bars .bar-label {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  line-height: 1.3;
}
.bars .bar-value {
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.bar-track {
  position: relative;
  height: 8px;
  background: var(--line);
  border-radius: var(--radius-pill);
  overflow: hidden;
}
/* The zero line, drawn only when some row in the chart is actually negative. */
.bar-track.diverge::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--line-strong);
  z-index: 1;
}
.bar-fill {
  position: absolute;
  top: 0;
  bottom: 0;
  background: var(--gain);
  border-radius: var(--radius-pill);
  transition: width 0.4s cubic-bezier(0.2, 0.8, 0.3, 1);
}
/* Hue alone would be red against green. The stripes carry the same meaning without it. */
.bar-fill.neg {
  background:
    repeating-linear-gradient(
      -45deg,
      transparent 0 3px,
      color-mix(in srgb, var(--bg) 45%, transparent) 3px 6px
    ),
    var(--loss);
}

.stats-range {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  text-align: center;
  gap: 6px;
  margin: 8px 0;
}
.stats-range .value {
  font-size: 21px;
  font-weight: 700;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}

/* The outcome of a closed item, stated once and large, above the breakdown that
   explains it. */
.outcome-hero {
  margin: 10px 0 0;
  padding: 12px 14px;
  background: var(--card-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
}
.outcome-hero .label {
  font-size: 11.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--eyebrow);
}
.outcome-hero .value {
  font-size: 30px;
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
  margin: 2px 0;
}

/* ============================================================================
   Comps & research rows
   ========================================================================= */
.comp {
  display: flex;
  gap: 10px;
  align-items: center;
  padding: 10px 0;
  border-bottom: 1px solid var(--line);
  font-size: 14px;
}
.comp:last-child {
  border-bottom: 0;
}
.comp img {
  width: 46px;
  height: 46px;
  object-fit: cover;
  border-radius: 8px;
  flex: none;
  background: var(--card-2);
}
.comp.picked {
  background: color-mix(in srgb, var(--gain) 9%, transparent);
}
.comp.comp-all {
  border-bottom-width: 2px;
}
.comp input[type="checkbox"] {
  width: 22px;
  height: 22px;
  flex: none;
}
/* The tappable part of a recent search. It sits inside a .comp row next to the ✕, so the
   row (not the button) owns the divider and padding. */
.recent-search {
  flex: 1;
  min-width: 0;
  background: none;
  border: 0;
  padding: 0;
  text-align: left;
  cursor: pointer;
  font: inherit;
  color: inherit;
  min-height: var(--tap);
}

/* How well a listing matches the item. The dots read at a glance; the percentage and the
   matched words are there when a price looks wrong and she wants to know why. */
.match-line {
  display: flex;
  gap: 6px;
  align-items: baseline;
  flex-wrap: wrap;
}
.match-dots {
  letter-spacing: 1px;
  color: var(--gain);
  flex: none;
}

.rehab-list td:last-child {
  white-space: nowrap;
}
.rehab-verdict {
  margin: 0 0 10px;
}
.sup-row {
  display: grid;
  grid-template-columns: 1fr 96px var(--tap);
  gap: 6px;
  padding: 8px 0;
  border-bottom: 1px solid var(--line);
}
.sup-row .sup-item {
  grid-column: 1 / -1;
}
.sup-row .btn {
  height: 48px;
}

/* ============================================================================
   Bottom sheet
   ========================================================================= */
/* A <dialog>, so the UA gives us Escape, the focus trap and inertness. Its own defaults
   (auto width, a border, a white background, centring) all have to be stripped first. */
.sheet {
  position: fixed;
  inset: 0;
  z-index: 50;
  width: 100%;
  max-width: none;
  height: 100%;
  max-height: none;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  overflow: visible;
}
.sheet:not([open]) {
  display: none;
}
/* The dim is .sheet-backdrop, an ordinary element, so it animates in step with the panel
   and keeps working as the click-to-close target. ::backdrop would double it. */
.sheet::backdrop {
  background: none;
}
/* showModal() does not stop the page behind from scrolling in every browser. */
html:has(.sheet[open]) {
  overflow: hidden;
}
.sheet-backdrop {
  position: absolute;
  inset: 0;
  background: rgb(10 14 12 / 0.5);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
  animation: fade 0.18s ease-out;
}
.sheet-panel {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  max-height: 92dvh;
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--bg);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  padding: 22px var(--pad) calc(18px + var(--safe-b));
  max-width: 760px;
  margin: 0 auto;
  box-shadow: var(--shadow-3);
  animation: up 0.22s cubic-bezier(0.2, 0.9, 0.3, 1);
}
/* The grab handle is decoration, so it is drawn rather than marked up. */
.sheet-panel::before {
  content: "";
  position: absolute;
  top: 9px;
  left: 50%;
  transform: translateX(-50%);
  width: 38px;
  height: 4px;
  border-radius: var(--radius-pill);
  background: var(--line-strong);
}
.sheet-panel h3 {
  margin: 0 0 12px;
  font-size: 18px;
  font-weight: 650;
}
@keyframes up {
  from {
    transform: translateY(24px);
    opacity: 0;
  }
}
@keyframes fade {
  from {
    opacity: 0;
  }
}

/* ============================================================================
   Collapsible sections (AI report, settings, forms)
   ========================================================================= */
details.sec {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  margin-bottom: 8px;
  box-shadow: var(--shadow-1);
}
details.sec > summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 13px 14px;
  font-weight: 600;
  min-height: var(--tap);
  border-radius: var(--radius);
}
details.sec > summary::-webkit-details-marker {
  display: none;
}
details.sec > summary::after {
  content: "";
  flex: none;
  width: 7px;
  height: 7px;
  margin-left: 2px;
  border-right: 2px solid var(--muted);
  border-bottom: 2px solid var(--muted);
  border-radius: 1px;
  transform: rotate(-45deg);
  transition: transform 0.18s ease;
}
details.sec[open] > summary::after {
  transform: rotate(45deg);
}
details.sec > summary .t {
  flex: 1;
  min-width: 0;
}
details.sec > summary .meta {
  font-weight: 450;
  color: var(--muted);
  font-size: 13px;
  text-align: right;
}
details.sec > .sec-body {
  padding: 2px 14px 14px;
  font-size: 14px;
}
details.sec > .sec-body > :first-child {
  margin-top: 0;
}
/* Consecutive sections read as one list instead of a stack of loose slabs: they share
   their hairlines and only the group gets the rounded corners and the shadow. */
.sec-group {
  border-radius: var(--radius);
  box-shadow: var(--shadow-1);
  margin-bottom: var(--gap);
  overflow: hidden;
}
.sec-group > details.sec {
  margin: 0;
  border-radius: 0;
  border-bottom: 0;
  box-shadow: none;
}
.sec-group > details.sec:last-child {
  border-bottom: 1px solid var(--line);
}
.sec-group > details.sec[open] {
  background: var(--card);
}
details.sec.plain {
  background: none;
  border-style: dashed;
  box-shadow: none;
}

/* This summary carries a long note ("11 sites · best Facebook Marketplace +$141.99").
   Without this the title is the thing that gets squeezed and wraps a word per line, so
   the title holds its width and the note wraps instead. */
details.sec.fee-compare > summary .t {
  flex: none;
  white-space: nowrap;
}
details.sec.fee-compare > summary .meta {
  flex: 1;
}
/* The count is context, not the answer, so it drops to its own quieter line rather than
   pushing the platform name into a third one. */
details.sec > summary .meta .meta-2 {
  display: block;
  font-size: 12px;
  opacity: 0.8;
}

/* ============================================================================
   Sticky action bars: bottom of a sheet, or above the tab bar on a page
   ========================================================================= */
.sheet-actions {
  position: sticky;
  bottom: calc(-18px - var(--safe-b));
  margin: 16px calc(var(--pad) * -1) calc(-18px - var(--safe-b));
  padding: 12px var(--pad) calc(12px + var(--safe-b));
  background: color-mix(in srgb, var(--bg) 92%, transparent);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border-top: 1px solid var(--line);
  display: grid;
  gap: 8px;
  z-index: 2;
}
.sheet-actions .row {
  flex-wrap: wrap;
}
.sheet-actions .row .btn {
  flex: 1 1 30%;
  white-space: nowrap;
}
/* Full-bleed with a hairline, so where it overlaps the content below it reads as a pinned
   toolbar rather than a card colliding with the page. */
.page-actions {
  position: sticky;
  bottom: calc(var(--tab-h) + var(--safe-b));
  z-index: 5;
  margin: 16px calc(var(--pad) * -1) 0;
  padding: 10px var(--pad);
  background: color-mix(in srgb, var(--bg) 94%, transparent);
  backdrop-filter: saturate(180%) blur(12px);
  -webkit-backdrop-filter: saturate(180%) blur(12px);
  border-top: 1px solid var(--line);
}

/* ============================================================================
   Decision summary at the top of an AI report
   ========================================================================= */
.decision {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 14px;
  margin: 10px 0;
}
.decision .big {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}
.decision .big .label {
  font-size: 11.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--eyebrow);
}
.decision .big .value {
  font-size: 27px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
  letter-spacing: -0.025em;
}
.decision .lines {
  margin-top: 12px;
  display: grid;
  gap: 5px;
  font-size: 14px;
}

.callout {
  border-left: 3px solid var(--warn);
  padding: 9px 11px;
  background: color-mix(in srgb, var(--warn) 9%, transparent);
  border-radius: 0 8px 8px 0;
  font-size: 13px;
  color: var(--ink-2);
}

.outcomes {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 10px;
}
.outcomes .btn {
  flex: 1 1 auto;
}
.tips {
  margin: 0;
  padding-left: 18px;
  color: var(--ink-2);
}
.tips li + li {
  margin-top: 5px;
}

/* The install nudge. Icon and text on one row, the action beneath, and the dismiss
   tucked into the corner — at 390px a single row put all four in a column of wrapped
   fragments. */
.install-banner {
  position: relative;
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: center;
  gap: 2px 12px;
  padding: 13px 40px 13px 14px;
  background: var(--brand-wash);
  border-color: color-mix(in srgb, var(--brand) 26%, var(--line));
}
.install-banner img {
  grid-row: span 2;
  border-radius: 10px;
  flex: none;
}
.install-banner .ib-title {
  font-weight: 600;
}
.install-banner .ib-go {
  grid-column: 2;
  justify-self: start;
  margin-top: 8px;
}
.install-banner .ib-close {
  position: absolute;
  top: 7px;
  right: 7px;
  min-height: 32px;
  padding: 5px 9px;
  border-color: transparent;
  background: none;
  color: var(--muted);
}

/* "never signed in" on a user row: worth noticing, not worth alarming about. */
.warn-text {
  color: var(--warn);
  font-weight: 600;
}

/* ============================================================================
   Toast, empty states, auth
   ========================================================================= */
.toast {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(var(--tab-h) + var(--safe-b) + 18px);
  z-index: 60;
  background: var(--ink);
  color: var(--bg);
  padding: 11px 18px;
  border-radius: var(--radius-pill);
  font-size: 14px;
  font-weight: 550;
  box-shadow: var(--shadow-2);
  max-width: calc(100vw - 32px);
  text-align: center;
  animation: toast-in 0.22s cubic-bezier(0.2, 0.9, 0.3, 1);
}
@keyframes toast-in {
  from {
    opacity: 0;
    transform: translate(-50%, 10px);
  }
}

/* ============================================================================
   Offline bar, skeletons, error state
   ========================================================================= */
/* Sits under the topbar rather than over the content, so nothing is covered. */
.offline-bar {
  position: sticky;
  top: 0;
  z-index: 25;
  padding: 8px var(--pad);
  background: color-mix(in srgb, var(--warn) 16%, var(--card));
  border-bottom: 1px solid color-mix(in srgb, var(--warn) 40%, var(--line));
  color: var(--ink);
  font-size: 13px;
  font-weight: 550;
  text-align: center;
}

/* A grey block standing in for content that has not arrived. The sweep is a background
   position animation rather than a moving element, so it costs one composited layer. */
.skel {
  display: block;
  height: 12px;
  border-radius: 6px;
  background: linear-gradient(
      90deg,
      transparent 25%,
      color-mix(in srgb, var(--card) 70%, transparent) 50%,
      transparent 75%
    )
    var(--line);
  background-size: 200% 100%;
  animation: skel-sweep 1.4s ease-in-out infinite;
}
@keyframes skel-sweep {
  from {
    background-position: 150% 0;
  }
  to {
    background-position: -50% 0;
  }
}
.skel-lines {
  display: grid;
  gap: 8px;
  flex: 1;
  min-width: 0;
}
/* The second line is short, the way a real secondary line usually is. */
.skel-lines .skel:last-child {
  width: 55%;
  height: 10px;
}
.skel-card {
  /* Matches the real card's two-line body, so nothing jumps when the data lands. */
  min-height: 62px;
}
.skel-row {
  display: flex;
  gap: 12px;
  align-items: center;
  padding: 11px 0;
  border-bottom: 1px solid var(--line);
}
.skel-row:last-child {
  border-bottom: 0;
}
.skel-thumb {
  width: 56px;
  height: 56px;
  border-radius: 11px;
  flex: none;
}
/* A skeleton is decoration: it should not crawl for someone who asked for less motion. */
@media (prefers-reduced-motion: reduce) {
  .skel {
    animation: none;
  }
}

.error-state .btn {
  margin-top: 4px;
}

.empty {
  text-align: center;
  padding: 48px 16px;
  color: var(--muted);
}
/* An empty state can land inside a .card (the items list) or straight on the page (the
   dashboard). Inside a card it was inheriting the uppercase, letter-spaced eyebrow from
   `.card h2`, so it has to reset those explicitly rather than only setting size/colour. */
.empty h2 {
  font-size: 19px;
  font-weight: 650;
  text-transform: none;
  letter-spacing: -0.015em;
  color: var(--ink);
  margin: 0 0 6px;
  display: block;
}
.empty p {
  max-width: 34ch;
  margin-inline: auto;
}

.login {
  max-width: 380px;
  margin: 12vh auto;
  padding: 22px;
}
.login h2 {
  font-size: 21px;
  font-weight: 650;
  margin: 0 0 4px;
  text-transform: none;
  letter-spacing: -0.02em;
  color: var(--ink);
}
.google-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  text-decoration: none;
}
.google-g {
  display: inline-grid;
  place-items: center;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: #fff;
  color: #4285f4;
  font-weight: 700;
  font-size: 14px;
  border: 1px solid var(--line);
}
.or-rule {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 2px 0;
}
.or-rule::before,
.or-rule::after {
  content: "";
  flex: 1;
  border-top: 1px solid var(--line);
}

/* ============================================================================
   Tablet — more room, so grids widen before the rail appears.
   ========================================================================= */
@media (min-width: 600px) {
  :root {
    --pad: 20px;
  }
  .kpis {
    grid-template-columns: repeat(6, 1fr);
  }
  .kpi .value {
    font-size: 23px;
  }
  .kpi.hero {
    grid-column: span 3;
  }
  .photo-sources {
    grid-template-columns: repeat(3, 1fr);
    max-width: 460px;
  }
  .decision .big .value {
    font-size: 30px;
  }
}

/* ============================================================================
   Desktop — the bottom tab bar becomes a side rail, and the dashboard opens
   into two columns. Below 960px nothing here applies, so the phone layout is
   untouched.
   ========================================================================= */
@media (min-width: 960px) {
  body {
    padding-left: var(--rail-w);
  }

  .tabbar {
    top: 0;
    bottom: 0;
    right: auto;
    width: var(--rail-w);
    height: 100dvh;
    flex-direction: column;
    justify-content: flex-start;
    align-items: stretch;
    gap: 2px;
    padding: 16px 12px calc(16px + var(--safe-b));
    background: var(--card);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    border-top: 0;
    border-right: 1px solid var(--line);
  }
  /* A wordmark above the rail, so the app is named even though the header scrolls. */
  .tabbar::before {
    content: "UrbanMiner";
    display: block;
    order: -2;
    padding: 6px 12px 18px;
    font-size: 17px;
    font-weight: 700;
    letter-spacing: -0.02em;
    color: var(--ink);
  }
  .tabbar a {
    flex: none;
    flex-direction: row;
    justify-content: flex-start;
    gap: 12px;
    padding: 10px 12px;
    border-radius: var(--radius-sm);
    font-size: 15px;
    font-weight: 550;
    min-height: 42px;
    transition:
      background 0.14s ease,
      color 0.14s ease;
  }
  .tabbar a:hover {
    background: var(--card-2);
    color: var(--ink);
  }
  .tabbar a.active {
    background: var(--brand-wash);
    color: var(--brand);
  }
  .tabbar a .ico {
    width: 21px;
    height: 21px;
  }

  /* The floating + becomes an ordinary primary action at the top of the rail. */
  .tabbar .tab-add {
    order: -1;
    margin-bottom: 10px;
    background: var(--brand);
    color: var(--brand-ink);
    box-shadow: 0 1px 2px color-mix(in srgb, var(--brand) 30%, transparent);
  }
  .tabbar .tab-add::after {
    content: "Add an item";
    font-weight: 650;
  }
  .tabbar .tab-add:hover,
  .tabbar .tab-add.active {
    background: var(--brand-hi);
    color: var(--brand-ink);
  }
  .tabbar .tab-add span {
    width: 21px;
    height: 21px;
    margin: 0;
    border-radius: 0;
    background: none;
    color: inherit;
    box-shadow: none;
  }
  .tabbar .tab-add .ico {
    width: 21px;
    height: 21px;
  }

  .topbar {
    padding-top: 14px;
    padding-bottom: 14px;
    background: color-mix(in srgb, var(--bg) 88%, transparent);
    backdrop-filter: saturate(180%) blur(14px);
    -webkit-backdrop-filter: saturate(180%) blur(14px);
    color: var(--ink);
    border-bottom: 1px solid var(--line);
  }
  .topbar h1 {
    font-size: 22px;
    font-weight: 700;
    letter-spacing: -0.022em;
  }
  .icon-btn:active {
    background: var(--card-2);
  }
  .ai-pill {
    background: var(--ink);
    color: var(--bg);
  }

  main {
    max-width: 880px;
    padding: 24px var(--pad) 64px;
  }
  .toast {
    left: calc(50% + var(--rail-w) / 2);
    bottom: 26px;
  }
  .page-actions {
    bottom: 18px;
  }
  /* The dim covers the rail too, the way a modal should. It used to start past the rail so
     the bottom sheet would not sit on top of it; the panel is centred now, so instead the
     panel alone is nudged right by half the rail to sit centred in the content area. */
  .sheet {
    left: 0;
    width: 100%;
  }

  /* Full-bleed buttons look stranded at desktop widths; cap them and let them sit left. */
  .card > .btn.block,
  .page-actions > .btn.block {
    max-width: 420px;
  }

  /* Dashboard: headline strip across the top, then two columns of cards. */
  main[data-page="home"] {
    max-width: 1080px;
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0 var(--gap);
    align-items: start;
  }
  main[data-page="home"] .kpi.hero .value {
    font-size: 32px;
  }
  main[data-page="home"] > .kpis,
  main[data-page="home"] > .range-row,
  main[data-page="home"] > .card.full {
    grid-column: 1 / -1;
  }

  /* Settings read as one column; a full-width accordion looks unfinished. */
  main[data-page="more"] {
    max-width: 720px;
  }

  /* A bottom sheet is a phone idiom. With a mouse, a dialog belongs in the middle of the
     screen, and it should not be as wide as the page it covers. */
  .sheet-panel {
    top: 50%;
    bottom: auto;
    left: calc(50% + var(--rail-w) / 2);
    right: auto;
    transform: translate(-50%, -50%);
    width: min(640px, calc(100vw - var(--rail-w) - 80px));
    max-height: 82dvh;
    border-radius: var(--radius-lg);
    padding-bottom: 18px;
    animation: pop 0.16s cubic-bezier(0.2, 0.9, 0.3, 1);
  }
  /* The drag handle means nothing without a drag. */
  .sheet-panel::before {
    display: none;
  }
  .sheet-panel > h3:first-child {
    margin-top: 0;
  }
  /* The sticky footer's negative margins were cut for a full-bleed sheet. */
  .sheet-actions {
    bottom: -18px;
    margin-bottom: -18px;
    padding-bottom: 12px;
  }
}
@keyframes pop {
  from {
    transform: translate(-50%, -48%) scale(0.98);
    opacity: 0;
  }
}

/* ============================================================================
   Wide desktop — past here the extra width buys columns instead of margin. Below
   1100px every rule in this block is inert and the phone layout is untouched.
   ========================================================================= */
@media (min-width: 1100px) {
  main {
    max-width: 1040px;
  }

  /* The item page is the app's tallest screen: up to nine cards in one column. The two
     wrappers drawItem() emits become the columns; everywhere else they are `contents`. */
  main[data-page="item"] {
    max-width: 1180px;
  }
  main[data-page="item"] > .item-body {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 0 var(--gap);
    align-items: start;
  }

  /* One list, two columns of rows. The rows keep their narrow measure — the stylesheet's
     point about name and price drifting apart still holds — there are just two of them. */
  main[data-page="items"] #results > .list {
    display: grid;
    grid-template-columns: 1fr 1fr;
    column-gap: var(--gap);
  }
  main[data-page="items"] #results > .list > li:nth-last-child(2):nth-child(odd) .list-item {
    border-bottom: 0;
  }

  /* Settings: a nav down the side rather than eleven panels to scroll past. The stylesheet's
     earlier note that settings "read as one column" still holds for the panels themselves —
     they keep their measure; the extra width goes to the index, not to the content. */
  main[data-page="more"] {
    max-width: 1000px;
    display: grid;
    grid-template-columns: 216px minmax(0, 1fr);
    gap: 0 28px;
    align-items: start;
  }
  main[data-page="more"] > * {
    grid-column: 2;
  }
  main[data-page="more"] > .settings-nav {
    grid-column: 1;
    grid-row: 1;
    display: flex;
    flex-direction: column;
    gap: 1px;
    position: sticky;
    top: 78px;
  }
  .settings-nav a {
    padding: 7px 10px;
    border-radius: var(--radius-sm);
    color: var(--ink-2);
    text-decoration: none;
    font-size: 14px;
    font-weight: 550;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .settings-nav a.current {
    background: var(--brand-wash);
    color: var(--brand);
  }
  /* The headings are the nav's job now; inside the panels they would say it twice. */
  main[data-page="more"] > .settings-group:first-of-type {
    margin-top: 0;
  }

}

/* ============================================================================
   Touch targets. --tap is declared as the minimum comfortable target, and several
   frequently-tapped controls sat under it: chips at 38px, the sort select at 34px, .btn.sm
   at 34px, inline deletes at 36px. On a coarse pointer the chips and selects simply grow;
   the compact buttons keep their size and take a full-height hit area instead, so the
   visual rhythm of a dense row is unchanged.
   ========================================================================= */
@media (pointer: coarse) {
  .chip,
  .select-chip,
  .sort-field select {
    min-height: var(--tap);
    height: auto;
  }
  .select-chip,
  .sort-field select {
    height: var(--tap);
  }

  .btn.sm,
  .list-item > .btn.danger,
  .comp > .btn.danger {
    position: relative;
  }
  .btn.sm::after,
  .list-item > .btn.danger::after,
  .comp > .btn.danger::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: 50%;
    height: var(--tap);
    transform: translateY(-50%);
  }
}

/* ============================================================================
   Hover — keyed on the pointer, not the viewport. These used to live inside the 960px
   block, which meant a laptop with a narrow window got no hover feedback at all while a
   wide touchscreen got hover states that fire on tap.
   ========================================================================= */
@media (hover: hover) and (pointer: fine) {
  .btn:hover {
    border-color: var(--line-strong);
    background: var(--card-2);
  }
  .btn.primary:hover {
    background: var(--brand-hi);
    border-color: var(--brand-hi);
  }
  .btn.danger:hover {
    background: color-mix(in srgb, var(--loss) 8%, transparent);
  }
  .btn.accent:hover {
    background: color-mix(in srgb, var(--brand) 14%, var(--card));
    border-color: var(--brand);
  }
  .list-item > .btn.danger:hover,
  .comp > .btn.danger:hover {
    color: var(--loss);
    background: color-mix(in srgb, var(--loss) 10%, transparent);
  }
  a.list-item:hover {
    background: var(--card-2);
  }
  .chip:hover {
    border-color: var(--ink);
  }
  .chip.active:hover {
    background: var(--ink);
  }
  details.sec > summary:hover {
    background: var(--card-2);
  }
  .icon-btn:hover {
    background: rgb(255 255 255 / 0.12);
  }
}

/* The item page's columns only exist on a wide screen. `display: contents` everywhere else
   is what keeps the phone's card order exactly as it was. The rail is markup the phone
   never shows at all. */
.item-body,
.col-a,
.col-b {
  display: contents;
}
.item-rail,
.settings-nav {
  display: none;
}

@media (min-width: 1100px) {
  main[data-page="item"] > .item-body {
    display: grid;
  }
  main[data-page="item"] .col-a {
    grid-column: 1;
  }
  main[data-page="item"] .col-b {
    grid-column: 2;
  }
  main[data-page="item"] .col-a,
  main[data-page="item"] .col-b {
    display: block;
    min-width: 0;
  }
}

/* ============================================================================
   Item page with the list beside it. Browsing item to item is the app's core loop, and it
   meant going back to the list every time. Three tiers: no room for a rail; rail plus one
   column of cards; rail plus both columns.
   ========================================================================= */
@media (min-width: 1280px) {
  main[data-page="item"] {
    max-width: none;
    display: grid;
    grid-template-columns: 300px minmax(0, 1fr);
    gap: 0 24px;
    align-items: start;
  }
  .item-rail {
    display: block;
    position: sticky;
    top: 78px;
    max-height: calc(100dvh - 100px);
    overflow-y: auto;
    overscroll-behavior: contain;
    padding-right: 4px;
  }
  .rail-head {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    font-size: 12px;
    font-weight: 650;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    margin-bottom: 6px;
  }
  .rail-head a {
    text-decoration: none;
  }
  .item-rail .thumb {
    width: 40px;
    height: 40px;
    border-radius: 8px;
    font-size: 17px;
  }
  .item-rail .list-item {
    padding: 8px 6px;
    gap: 9px;
    font-size: 14px;
  }
  .item-rail .list-item.current {
    background: var(--brand-wash);
  }
  .item-rail .title-line {
    font-weight: 550;
  }
  /* The rail takes the width the second card column had, so the cards stack again. */
  main[data-page="item"] > .item-body {
    grid-template-columns: minmax(0, 1fr);
    max-width: 760px;
  }
  main[data-page="item"] .col-a,
  main[data-page="item"] .col-b {
    grid-column: 1;
  }
}

/* Wide enough for the rail and both card columns. */
@media (min-width: 1700px) {
  main[data-page="item"] > .item-body {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    max-width: 1180px;
  }
  main[data-page="item"] .col-b {
    grid-column: 2;
  }
}

/* A single list reads worst when it is widest: the name and the price drift so far apart
   that the eye loses the row. It stays at the default measure on every screen. */

/* ============================================================================
   View transitions — the SPA swaps main's innerHTML wholesale, which reads as a flicker.
   This costs nothing where it isn't supported, and the reduced-motion block below turns
   it off along with everything else.
   ========================================================================= */
::view-transition-old(root) {
  animation: vt-out 0.14s ease both;
}
::view-transition-new(root) {
  animation: vt-in 0.18s ease both;
}
@keyframes vt-out {
  to {
    opacity: 0;
  }
}
@keyframes vt-in {
  from {
    opacity: 0;
    transform: translateY(4px);
  }
}

/* ============================================================================
   Motion & accessibility
   ========================================================================= */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
@media (prefers-contrast: more) {
  :root {
    --line: #b9b5a8;
    --line-strong: #8c8879;
    --muted: #4e544f;
    --eyebrow: #4e544f;
  }
}
@media print {
  .tabbar,
  .topbar,
  .page-actions,
  .sheet,
  .toast {
    display: none !important;
  }
  body {
    padding: 0;
  }
  main {
    max-width: none;
    padding: 0;
  }
  .card {
    break-inside: avoid;
    box-shadow: none;
  }
}

/* ============================================================================
   Appearance picker
   ========================================================================= */
.theme-btn {
  margin-left: 0;
  margin-right: -8px;
}
.mode-seg {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: 4px;
  padding: 4px;
  background: var(--card-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
}
.mode-seg button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: 40px;
  border: 0;
  border-radius: 7px;
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
}
.mode-seg button .ico {
  width: 18px;
  height: 18px;
}
.mode-seg button[aria-pressed="true"] {
  background: var(--card);
  color: var(--ink);
  box-shadow:
    var(--shadow-1),
    inset 0 -2px 0 var(--accent);
}
.swatches {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(128px, 1fr));
  gap: 8px;
  margin-top: 12px;
}
.swatch {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 10px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--card);
  color: var(--ink);
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}
.swatch[aria-pressed="true"] {
  border-color: var(--brand);
  box-shadow: var(--ring);
}
.swatch .chipset {
  display: flex;
  height: 30px;
  border-radius: 7px;
  overflow: hidden;
  border: 1px solid color-mix(in srgb, var(--ink) 10%, transparent);
}
.swatch .chipset i {
  flex: 1;
}
.swatch .chipset i:first-child {
  flex: 2;
}
.swatch small {
  color: var(--muted);
  font-weight: 500;
}

/* ============================================================================
   Assisting another account
   ========================================================================= */
.assist-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px var(--pad);
  background: var(--accent);
  color: #1c1a14;
  font-size: 14px;
  font-weight: 600;
}
.assist-bar .ico {
  width: 20px;
  height: 20px;
  flex: none;
}
.assist-bar .grow {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.assist-bar .btn {
  min-height: 34px;
  padding: 5px 12px;
  background: #1c1a14;
  border-color: #1c1a14;
  color: #fff;
  font-size: 13.5px;
  box-shadow: none;
}
/* The bar scrolls away with the page; these edges don't, so whose inventory is on screen
   stays obvious at any scroll position. */
body[data-assisting] .topbar {
  box-shadow: inset 0 -3px 0 var(--accent);
}
body[data-assisting] .tabbar {
  border-top: 3px solid var(--accent);
}
.assist-chip {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  max-width: 40vw;
  padding: 4px 10px 4px 4px;
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--accent);
  color: #1c1a14;
  font: inherit;
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
}
.assist-chip span:last-child {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.assist-chip .avatar {
  width: 24px;
  height: 24px;
  font-size: 11px;
  background: #1c1a14;
  color: var(--accent);
}
.assist-list {
  display: grid;
  gap: 6px;
}
.assist-list button {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--card);
  color: var(--ink);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.assist-list button:active {
  background: var(--card-2);
}
.assist-list button[aria-current="true"] {
  border-color: var(--accent);
  box-shadow: inset 4px 0 0 var(--accent);
}
.assist-list .grow {
  min-width: 0;
}
.avatar {
  flex: none;
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--brand-wash);
  color: var(--brand);
  font-weight: 700;
  font-size: 14px;
}

/* ============================================================================
   Drafts: what she typed is kept until she saves or discards it
   ========================================================================= */
.draft-note {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 10px;
  margin: 0 0 12px;
  padding: 10px 12px;
  border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--line));
  border-left: 4px solid var(--accent);
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--accent) 9%, var(--card));
  font-size: 14px;
}
.draft-note .grow {
  flex: 1 1 160px;
  min-width: 0;
}
.draft-note > .ico {
  color: var(--accent);
  width: 22px;
  height: 22px;
  flex: none;
}
.draft-status {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 12.5px;
  color: var(--muted);
}
.draft-status .ico {
  width: 16px;
  height: 16px;
}
.draft-status[data-state="saved"] .ico {
  color: var(--gain);
}
/* A dot on the + tab while an unfinished item is waiting. */
.tabbar .tab-add span {
  position: relative;
}
.tabbar .tab-add[data-draft] span::after {
  content: "";
  position: absolute;
  top: 2px;
  right: 2px;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--accent);
  border: 2px solid var(--card);
}

/* ============================================================================
   Research dock: comps and AI right under "What is it?"
   ========================================================================= */
.research-dock {
  margin: 0 0 14px;
}
.research-dock > label.field {
  margin-bottom: 0;
}
/* Find comps rides inside the name box at its right end, like a search field's icon. */
label.field.has-find {
  position: relative;
}
label.field.has-find > input {
  padding-right: 54px;
}
.find-btn {
  position: absolute;
  right: 4px;
  bottom: 4px;
  width: 44px;
  height: 40px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: calc(var(--radius-sm) - 2px);
  background: none;
  color: var(--brand);
  cursor: pointer;
}
.find-btn:hover {
  background: var(--brand-wash);
}
.find-btn:focus-visible {
  outline: none;
  box-shadow: var(--ring);
}
.find-btn[aria-expanded="true"] {
  background: var(--brand);
  color: var(--brand-ink);
}
.research-dock .dock-panel {
  margin-top: 8px;
  padding: 4px 12px 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--card);
}
.research-dock > .dock-summary,
.dock-ai .dock-summary {
  margin-top: 8px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.dock-ai {
  margin-top: 8px;
}
.dock-ai .ai-extras {
  margin: 8px 0 0;
}

/* Use AI? — a switch rather than a checkbox, since it changes the whole form at once. */
.switch-row {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin-top: 12px;
  min-height: 32px;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  position: relative;
}
.switch-row input {
  position: absolute;
  opacity: 0;
  width: 44px;
  height: 26px;
  margin: 0;
  cursor: pointer;
}
.switch {
  position: relative;
  flex: none;
  width: 44px;
  height: 26px;
  border-radius: 13px;
  background: var(--line-strong);
  transition: background 0.16s ease;
}
.switch::after {
  content: "";
  position: absolute;
  top: 3px;
  left: 3px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 2px rgb(0 0 0 / 0.25);
  transition: transform 0.16s ease;
}
.switch-row input:checked + .switch {
  background: var(--brand);
}
.switch-row input:checked + .switch::after {
  transform: translateX(18px);
}
.switch-row input:focus-visible + .switch {
  box-shadow: var(--ring);
}
@media (prefers-reduced-motion: reduce) {
  .switch,
  .switch::after {
    transition: none;
  }
}
/* AI off: its buttons, options and hints step aside; plain stand-ins show instead. */
.ai-off [data-ai-only],
form:not(.ai-off) [data-ai-hide] {
  display: none !important;
}
/* The one-line answer: what it's worth, and a button that puts that in the form. */
.dock-summary {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px 10px;
  padding: 8px 12px;
  border-top: 1px solid var(--line);
  background: var(--card);
  font-size: 14px;
}
.dock-summary .src {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  color: var(--muted);
  font-size: 12px;
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.dock-summary .src .ico {
  width: 16px;
  height: 16px;
}
.dock-summary .val {
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.dock-summary .grow {
  flex: 1;
}

/* Save bar on the Add screen: the draft state on the left, the one action on the right. */
.page-actions.save-row {
  display: flex;
  align-items: center;
  gap: 12px;
}
.page-actions.save-row .btn {
  flex: 1 1 60%;
}
.page-actions.save-row .draft-status:empty {
  display: none;
}
/* A value the dock just put in the form, so the eye finds what changed. */
input.flash {
  animation: flash 0.9s ease-out;
}
@keyframes flash {
  from {
    background: color-mix(in srgb, var(--accent) 35%, var(--card));
  }
}
.item-body > .draft-note {
  grid-column: 1 / -1;
}
main[data-page="home"] > .unfinished {
  grid-column: 1 / -1;
}
.unfinished {
  border-left: 4px solid var(--accent);
}
.dock-query {
  margin-top: 10px;
  color: var(--muted);
  overflow-wrap: anywhere;
}
.dock-query b {
  color: var(--ink);
}
/* The comps answer can be two lines (sold, then for sale), each with its own Use button. */
.dock-summary .dock-sum-line {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px 10px;
  width: 100%;
}
.dock-summary .dock-sum-line + .dock-sum-line {
  padding-top: 6px;
  border-top: 1px dashed var(--line);
}
.dock-group {
  margin-top: 10px;
}
.dock-group .comp {
  cursor: pointer;
}
/* Unticked listings step back, so what's being counted is what stands out. */
.dock-group .comp[data-row]:not(.picked) {
  opacity: 0.6;
}
.dock-group .comp[data-row]:not(.picked) .num {
  text-decoration: line-through;
}

/* Anything that opens a draft when tapped: the card with one draft, each row, a note. */
.draft-row,
.unfinished.opens,
.draft-note.opens {
  cursor: pointer;
}
.draft-row {
  border-radius: var(--radius-sm);
  transition: background 0.12s ease;
}
.draft-row:active,
.draft-note.opens:active {
  background: color-mix(in srgb, var(--accent) 12%, var(--card));
}
@media (hover: hover) and (pointer: fine) {
  .draft-row:hover {
    background: color-mix(in srgb, var(--accent) 8%, var(--card));
  }
  .draft-note.opens:hover {
    border-color: var(--accent);
  }
}
.draft-row .go {
  flex: none;
  color: var(--brand);
  font-size: 13.5px;
  font-weight: 650;
  white-space: nowrap;
}

/* ============================================================================
   Settings panel — slides in from the left over the current screen
   ========================================================================= */
.drawer {
  position: fixed;
  inset: 0;
  z-index: 60;
  width: 100%;
  max-width: none;
  height: 100%;
  max-height: none;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  overflow: visible;
}
.drawer:not([open]) {
  display: none;
}
.drawer::backdrop {
  background: none;
}
html:has(.drawer[open]) {
  overflow: hidden;
}
.drawer-backdrop {
  position: absolute;
  inset: 0;
  background: rgb(10 14 12 / 0.45);
  animation: drawer-fade 0.2s ease-out;
}
.drawer-panel {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  display: flex;
  flex-direction: column;
  width: min(440px, 90vw);
  background: var(--bg);
  box-shadow: 8px 0 40px rgb(0 0 0 / 0.25);
  animation: drawer-in 0.24s cubic-bezier(0.2, 0.8, 0.2, 1);
  padding-left: env(safe-area-inset-left, 0px);
}
.drawer-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: calc(env(safe-area-inset-top, 0px) + 12px) var(--pad) 12px;
  background: var(--bar);
  color: var(--bar-ink);
}
.drawer-head h2 {
  flex: 1;
  margin: 0;
  font-size: 17px;
  font-weight: 650;
}
.drawer-head .icon-btn {
  margin: 0 -8px 0 0;
}
.drawer-body {
  flex: 1;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--pad) var(--pad) calc(var(--safe-b) + 32px);
}
.drawer-body > .settings-group:first-child {
  margin-top: 0;
}
@keyframes drawer-in {
  from {
    transform: translateX(-100%);
  }
}
@keyframes drawer-fade {
  from {
    opacity: 0;
  }
}
@media (prefers-reduced-motion: reduce) {
  .drawer-panel,
  .drawer-backdrop {
    animation: none;
  }
}
/* On a wide screen the panel opens beside the rail, which is dimmed with the page: a tap on
   either closes it, rather than the rail looking live while the modal ignores it. */
@media (min-width: 960px) {
  .drawer-panel {
    left: var(--rail-w);
  }
}
.menu-btn[aria-expanded="true"] {
  background: rgb(255 255 255 / 0.12);
}

/* ‹ Run 2 of 3 › at the top of an AI report researched more than once. */
.run-pager {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin: 0 0 10px;
  padding: 4px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--card-2);
}
.run-pager .btn {
  min-width: 44px;
  font-size: 18px;
  line-height: 1;
}

/* A listing's own model number on its row, so a variant stands out before it's used. */
.listing-model {
  color: var(--ink);
  font-weight: 650;
  letter-spacing: 0.01em;
}
/* "Yours: DCD771 · keep yours" under a field the listing would change. */
.field-was {
  margin: -6px 0 10px;
}
/* Under a Find comps listing whose brand or model differs from what's in the form. */
.dock-diff {
  margin: 0 0 8px;
  padding: 8px 10px;
  border-left: 3px solid var(--accent);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  background: color-mix(in srgb, var(--accent) 9%, var(--card));
}
.dock-diff > div + div {
  margin-top: 4px;
}
/* Four modes on a narrow phone: icon over label, so "Neutral" never truncates. */
@media (max-width: 380px) {
  .mode-seg button {
    flex-direction: column;
    gap: 2px;
    min-height: 50px;
    font-size: 12.5px;
  }
}

/* ============================================================================
   Edit item page
   ========================================================================= */
.edit-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
  margin-bottom: 10px;
}
.edit-foot {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  margin-top: 14px;
}
.edit-foot .danger {
  border: 0;
  background: none;
}
