/* A&G Garden — local management UI.

   Light only, on purpose. An OS-driven dark theme made a garden app feel grim,
   and dark mode is worth having only as a deliberate toggle rather than
   something the operating system imposes. If it comes back it should be a
   selected set of steps against a dark surface, not an inversion of this.

   Brand colours sampled from the logo:
     #23673e  wordmark green  6.7:1 on the card — safe for text, links, fills
     #74a33d  leaf green      2.9:1 — DECORATIVE ONLY. Never text, never behind
                              white, and never beside a status chip: it sits
                              1.13:1 from the status "good" green, so the two
                              are indistinguishable by lightness.

   Status colours are the reserved four and are never themed or softened. Each
   renders as tint + coloured dot + icon + text label, so meaning never rides
   on hue alone — which is what lets green chrome sit beside a green "fine". */

:root {
  color-scheme: light;

  /* Warm green-cream. Cards sit *lighter* than the page so they lift off it. */
  --paper: #f2f7e9;
  --surface-solid: #fdfefa;             /* sticky headers, mark rings */
  --surface-1: rgba(253, 254, 250, 0.86);
  --surface-2: rgba(234, 243, 220, 0.92);  /* zone bands, hovers */

  --brand: #23673e;
  --brand-hover: #1a5030;
  --leaf: #74a33d;

  --text-primary: #20291f;   /* 14.8:1 — warm, not near-black */
  --text-secondary: #4b5747; /*  7.5:1 */
  --text-muted: #78856f;     /*  3.9:1 — UI and large text only */
  --gridline: #e2ebd3;
  --baseline: #cbd8bb;
  --border: rgba(32, 41, 31, 0.09);

  --series-1: #2a78d6;
  --series-2: #eb6834;

  --good: #0ca30c;
  --warning: #fab219;
  --serious: #ec835a;
  --critical: #d03b3b;

  /* Soft tints for status chips, so a chip reads as a friendly pill rather
     than a warning light. */
  --tint-good: #e8f6e4;
  --tint-warning: #fdf2d8;
  --tint-serious: #fceee6;
  --tint-critical: #fbe6e5;
  --tint-muted: #eef0e9;

  --radius: 16px;
  --radius-sm: 11px;
  --row-h: 104px;
  --shadow: 0 1px 2px rgba(32, 41, 31, 0.04), 0 6px 18px rgba(32, 41, 31, 0.05);
}

* { box-sizing: border-box; }
html, body { height: 100%; }

body {
  margin: 0;
  color: var(--text-primary);
  font: 15.5px/1.6 system-ui, -apple-system, "Segoe UI", sans-serif;
  -webkit-font-smoothing: antialiased;
  /* A little daylight from the top, over the cream. */
  background:
    radial-gradient(1200px 460px at 50% -180px, #ffffff 0%, rgba(255,255,255,0) 70%),
    var(--paper);
  background-attachment: fixed;
}

/* Logo watermark: centred and fully visible. Fixed, faint, behind everything
   and non-interactive, so it never affects the contrast of anything on top. */
body::before {
  content: "";
  position: fixed;
  top: 50%; left: 50%;
  width: min(64vw, 62vh, 660px);
  aspect-ratio: 1;
  transform: translate(-50%, -50%);
  background: url("/static/mark.png") no-repeat center / contain;
  opacity: 0.07;
  pointer-events: none;
  z-index: 0;
}

.page { position: relative; z-index: 1; }

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

/* --- header --- */
header.bar {
  display: flex; align-items: center; gap: 18px; flex-wrap: wrap;
  padding: 15px 28px;
  background: var(--surface-solid);
  border-bottom: 1px solid var(--gridline);
  box-shadow: inset 0 -3px 0 0 var(--leaf), var(--shadow);
}
header.bar .logo { display: flex; align-items: center; gap: 13px; }
header.bar img.mark { width: 44px; height: 44px; object-fit: contain; }
/* Height-driven, so the 3.36:1 artwork scales without being squashed. */
header.bar img.wordmark { height: 40px; width: auto; display: block; }
@media (max-width: 620px) { header.bar img.wordmark { height: 32px; } }
header.bar nav { display: flex; gap: 6px; align-items: center; }
header.bar nav a {
  padding: 6px 13px; border-radius: 999px; font-size: 14px; font-weight: 600;
  color: var(--text-secondary);
}
header.bar nav a:hover {
  background: var(--surface-2); color: var(--brand); text-decoration: none;
}
header.bar .meta {
  margin-left: auto; color: var(--text-muted); font-size: 13px; text-align: right;
}
header.bar .logout {
  margin: 0; display: flex; align-items: center; gap: 8px;
  color: var(--text-muted); font-size: 13px;
}
header.bar .logout form { margin: 0; }

/* --- login --- */
.login-wrap {
  min-height: 60vh; display: flex; align-items: center; justify-content: center;
}
.login-card { width: 100%; max-width: 360px; text-align: center; }
.login-card img.mark { width: 56px; height: 56px; object-fit: contain; margin-bottom: 10px; }
.login-card h2 { margin-bottom: 4px; }
.login-card p.hint { margin-bottom: 20px; }
.login-card form { display: grid; gap: 12px; text-align: left; }
.login-card .error {
  color: var(--critical); background: var(--tint-critical);
  border-radius: 10px; padding: 9px 12px; font-size: 14px; font-weight: 600;
  text-align: center;
}

main { padding: 24px 28px 76px; }
/* max-width alone pins the column to the left edge — it needs auto margins to
   actually sit in the middle of the window. */
main.narrow { max-width: 860px; margin-inline: auto; }
/* overflow-x is the safety net under the narrower breakpoint below: whatever
   this table cannot be shrunk enough to fit (an unusually long name, a phone
   narrower than tested) scrolls within the table's own box instead of
   dragging the whole page sideways and losing the header off-screen too. */
.bleed { margin: 0 -28px; overflow-x: auto; }

/* --- progressive disclosure: keep the page to one screen, without hiding
       anything permanently. Native <details>, so no JavaScript. --- */
details.fold {
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  margin-bottom: 14px;
}
details.fold > summary {
  cursor: pointer; list-style: none;
  padding: 14px 20px;
  font-size: 15px; font-weight: 650; color: var(--text-primary);
  display: flex; align-items: center; gap: 10px;
  border-radius: var(--radius);
}
details.fold > summary::-webkit-details-marker { display: none; }
details.fold > summary::after {
  content: "›";
  margin-left: auto;
  color: var(--text-muted); font-size: 20px; line-height: 1;
  transition: transform 140ms ease;
}
details.fold[open] > summary::after { transform: rotate(90deg); }
details.fold > summary:hover { background: var(--surface-2); }
details.fold > summary .count {
  font-weight: 400; font-size: 13.5px; color: var(--text-muted);
}
details.fold .fold-body { padding: 2px 20px 20px; }
details.fold[open] > summary {
  border-bottom: 1px solid var(--gridline);
  border-radius: var(--radius) var(--radius) 0 0;
}

h2 { font-size: 17px; font-weight: 650; margin: 0 0 12px; letter-spacing: -0.01em; }
h3 { font-size: 14px; font-weight: 650; margin: 24px 0 8px; color: var(--text-secondary); }
p.hint { color: var(--text-muted); font-size: 13.5px; margin: 6px 0 0; }

.card {
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 20px 22px;
  margin-bottom: 18px;
}

/* --- toolbar --- */
.toolbar { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin-bottom: 16px; }
.toolbar input[type="text"] { max-width: 280px; }
.toolbar .spacer { margin-left: auto; }
.toolbar .summary { color: var(--text-secondary); font-size: 14px; }

/* --- the plants table --- */
table.plants {
  width: 100%; border-collapse: collapse;
  background: var(--surface-1);
  border-top: 1px solid var(--gridline);
  border-bottom: 1px solid var(--gridline);
}
table.plants thead th {
  position: sticky; top: 0; z-index: 2;
  background: var(--surface-solid);
  border-bottom: 1px solid var(--baseline);
  color: var(--text-muted);
  font-size: 13px; font-weight: 600; text-align: left;
  padding: 11px 16px; white-space: nowrap;
}
table.plants tbody td {
  border-bottom: 1px solid var(--gridline);
  padding: 12px 16px; vertical-align: middle;
}
table.plants tbody tr:hover td { background: var(--surface-2); }
table.plants tbody tr:last-child td { border-bottom: none; }

table.plants td.pic { width: calc(var(--row-h) + 32px); padding: 10px 16px; }
table.plants img.photo {
  width: var(--row-h); height: var(--row-h);
  object-fit: cover; display: block;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--surface-2);
  box-shadow: var(--shadow);
}
table.plants .photo.empty {
  display: grid; place-items: center; color: var(--leaf); font-size: 30px;
}
table.plants .pname { font-size: 16.5px; font-weight: 640; letter-spacing: -0.01em; }
table.plants .pname a { color: var(--text-primary); }
table.plants .pname a:hover { color: var(--brand); }
table.plants .pspecies {
  color: var(--text-secondary); font-size: 13.5px; font-style: italic; margin-top: 1px;
}
table.plants .pspot { color: var(--text-muted); font-size: 13px; margin-top: 2px; }
table.plants td.num { white-space: nowrap; }
table.plants td.act { text-align: right; white-space: nowrap; }
table.plants tr.zone-band td {
  background: var(--surface-2);
  font-size: 14px; font-weight: 650; color: var(--brand);
  padding: 9px 16px;
  border-bottom: 1px solid var(--gridline);
}
table.plants tr.zone-band .sun { font-weight: 400; color: var(--text-muted); }
table.plants tr.archived td { opacity: 0.6; }

@media (max-width: 720px) {
  :root { --row-h: 68px; }
  main, header.bar { padding-left: 14px; padding-right: 14px; }
  .bleed { margin: 0 -14px; }
  table.plants .hide-sm { display: none; }
}

/* A real phone, not just a narrow desktop window. Measured (390px, real
   device metrics): even with .hide-sm gone, the row was 497px in a 390px
   viewport — the Watered button's own column was exactly the 100px
   difference, pushed off-screen with nothing to scroll but the whole page.
   Shrinking the two widest fixed things (the thumbnail and the button) is
   what actually closes that gap; .bleed's overflow-x above is the fallback
   for whatever this still doesn't cover. */
@media (max-width: 480px) {
  :root { --row-h: 44px; }
  table.plants tbody td { padding: 10px 8px; }
  table.plants td.pic { padding: 8px 10px; }
  button.watered { padding: 3px 4px; }
  button.watered svg { width: 38px; height: 34px; }
}

/* --- status chip: tint + face + label ---
   The face carries the meaning: 🥵 reads as "go and water that one" faster than
   any shade of red does. The tint stays, so colour still groups the list at a
   glance, and the label stays because status must never be colour-alone — or
   emoji-alone, which fails the same way when a font renders 🥵 as a blank box.

   No coloured disc behind the face: an emoji is already full-colour artwork and
   sitting it on a red circle just muddies both. The dot rule is kept for
   non-emoji glyphs. */
.chip {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 13px; font-weight: 600; white-space: nowrap;
  padding: 4px 12px 4px 9px; border-radius: 999px;
  border: 1px solid var(--border);
  color: var(--text-primary);
}
.chip .face {
  font-size: 15px; line-height: 1;
  /* Emoji sit high in the line box; nudge them onto the text baseline. */
  transform: translateY(0.5px);
}
.chip .dot {
  display: inline-grid; place-items: center;
  width: 16px; height: 16px; border-radius: 50%;
  font-size: 10px; font-weight: 700; color: #fff;
  box-shadow: 0 0 0 2px var(--surface-solid);
}
.chip.critical { background: var(--tint-critical); }
.chip.warning  { background: var(--tint-warning); }
.chip.good     { background: var(--tint-good); }
.chip.serious  { background: var(--tint-serious); }
.chip.muted    { background: var(--tint-muted); }
.chip.critical .dot { background: var(--critical); }
.chip.warning  .dot { background: var(--warning); color: #20291f; }
.chip.good     .dot { background: var(--good); }
.chip.serious  .dot { background: var(--serious); color: #20291f; }
.chip.muted    .dot { background: var(--baseline); color: #20291f; }

/* --- forms --- */
form.grid { display: grid; gap: 15px; grid-template-columns: repeat(2, minmax(0, 1fr)); }
form.grid .full { grid-column: 1 / -1; }
label { display: block; font-size: 13.5px; font-weight: 600;
        color: var(--text-secondary); margin-bottom: 5px; }
input[type="text"], input[type="password"], input[type="number"], input[type="date"], select, textarea {
  width: 100%; padding: 9px 12px; font: inherit;
  color: var(--text-primary); background: var(--surface-solid);
  border: 1px solid var(--baseline); border-radius: var(--radius-sm);
}
input:focus, select:focus, textarea:focus {
  outline: 2px solid var(--leaf); outline-offset: 1px; border-color: var(--brand);
}
textarea { min-height: 80px; resize: vertical; }

button {
  font: inherit; font-weight: 600; cursor: pointer;
  padding: 9px 16px; border-radius: 999px;
  border: 1px solid var(--baseline);
  background: var(--surface-solid); color: var(--text-primary);
  transition: background 120ms ease, border-color 120ms ease;
}
button:hover { background: var(--surface-2); border-color: var(--leaf); }
button.primary { background: var(--brand); border-color: var(--brand); color: #fff; }
button.primary:hover { background: var(--brand-hover); border-color: var(--brand-hover); }
button.small { padding: 6px 13px; font-size: 13px; }
button.danger { color: var(--critical); }
.actions { display: flex; gap: 10px; align-items: center; margin-top: 18px; }
.actions .spacer { margin-left: auto; }

/* --- plain tables --- */
table.data { width: 100%; border-collapse: collapse; font-size: 14px; }
table.data th, table.data td {
  text-align: left; padding: 8px 11px; border-bottom: 1px solid var(--gridline);
}
table.data th { color: var(--text-muted); font-weight: 600; font-size: 13px; }
table.data tr:last-child td { border-bottom: none; }

/* Row-level destructive action: present but recessive, so it never competes
   with the content of the row it can delete. */
table.data td.rowact { text-align: right; width: 1%; white-space: nowrap; }
table.data td.rowact form { margin: 0; }
button.linkish {
  border: none; background: none; padding: 2px 4px;
  font-size: 13px; font-weight: 600; color: var(--text-muted);
  border-radius: 6px;
}
button.linkish:hover { color: var(--critical); background: var(--tint-critical); }

/* --- The "watered" button -------------------------------------------------
   An icon rather than the word, because this is the most-pressed control in the
   app and it repeats down every row: the can reads faster than a label and
   costs a third of the width. Hovering tips it and it pours.

   Pure CSS, like everything else here — a flourish is a poor reason to take on
   JavaScript. The accessible name is on the <button>, so nothing at all depends
   on reading the picture, and the tap target stays finger-sized on a phone
   where there is no hover to discover.

   Resets the shared pill button: no border, no fill, no shadow. */
button.watered {
  border: none; background: none; box-shadow: none;
  padding: 5px 7px; border-radius: 10px; line-height: 0;
  color: var(--brand);
  transition: background 120ms ease, color 120ms ease;
}
button.watered:hover { background: var(--surface-2); color: var(--brand-hover); }
/* The viewBox keeps a third of its height free below the can for the drops to
   fall into, so the visible can is about 40px of this 54px box. */
button.watered svg { display: block; width: 54px; height: 49px; }

/* transform-box defaults to view-box on SVG children, so the origin below is in
   viewBox units. It pivots near the top of the body, where a hand would hold
   it, so the spout swings down and the body swings back — tipping about the
   base instead just slides the whole can out of frame. */
.can-body {
  transform-origin: 12px 11px;
  transition: transform 320ms cubic-bezier(0.34, 1.3, 0.64, 1);
}
.can-drop { opacity: 0; }

button.watered:hover .can-body,
button.watered:focus-visible .can-body { transform: rotate(48deg); }

button.watered:hover .can-drop,
button.watered:focus-visible .can-drop { animation: pour 1s ease-in infinite; }
.can-drop:nth-child(2) { animation-delay: 180ms; }
.can-drop:nth-child(3) { animation-delay: 360ms; }

@keyframes pour {
  0%   { opacity: 0; transform: translateY(-3px) scale(0.5); }
  30%  { opacity: 1; transform: translateY(0) scale(1); }
  100% { opacity: 0; transform: translateY(9px) scale(0.7); }
}

/* Tipping and pouring are decoration; the button must still say what it does
   without them. */
@media (prefers-reduced-motion: reduce) {
  .can-body { transition: none; }
  button.watered:hover .can-body,
  button.watered:focus-visible .can-body { transform: none; }
  button.watered:hover .can-drop,
  button.watered:focus-visible .can-drop { animation: none; opacity: 1; }
}

.kind { display: inline-flex; align-items: center; gap: 7px; }
.kind::before {
  content: ""; width: 9px; height: 9px; border-radius: 50%;
  background: var(--text-muted); flex: none;
  box-shadow: 0 0 0 2px var(--surface-solid);
}
.kind.watered::before    { background: var(--series-1); }
.kind.fertilized::before { background: var(--series-2); }

.photo-strip { display: grid; grid-template-columns: repeat(auto-fill, minmax(158px, 1fr)); gap: 13px; }
.photo-strip figure { margin: 0; }
.photo-strip img {
  width: 100%; aspect-ratio: 1; object-fit: cover;
  border-radius: var(--radius-sm); border: 1px solid var(--border);
  box-shadow: var(--shadow);
}
.photo-strip figcaption { font-size: 12.5px; color: var(--text-muted); margin-top: 6px; }

/* A fold nested inside a form grid: flatter, no card of its own. */
details.fold.sub {
  background: none; border: none; box-shadow: none; margin: 0;
  border-top: 1px solid var(--gridline); border-radius: 0;
}
details.fold.sub > summary {
  padding: 12px 0 10px; font-size: 14px; color: var(--text-secondary);
  border-radius: 0;
}
details.fold.sub > summary:hover { background: none; color: var(--brand); }
details.fold.sub[open] > summary { border-bottom: none; }
details.fold.sub .fold-body { padding: 0 0 4px; }
.subgrid { display: grid; gap: 14px; grid-template-columns: repeat(2, minmax(0, 1fr)); }
.subgrid .full { grid-column: 1 / -1; }
@media (max-width: 620px) { .subgrid { grid-template-columns: 1fr; } }

/* --- plant summary header --- */
.crumb { margin: 0 0 14px; font-size: 14px; }
.card.summary { display: flex; gap: 20px; align-items: center; }
.summary-pic { flex: none; }
.summary-pic img, .summary-pic.empty {
  width: 108px; height: 108px; object-fit: cover; display: block;
  border-radius: var(--radius-sm); border: 1px solid var(--border);
  box-shadow: var(--shadow); background: var(--surface-2);
}
.summary-pic.empty { display: grid; place-items: center; color: var(--leaf); font-size: 40px; }
.summary-main { min-width: 0; }
.summary-main h2 { margin: 0 0 3px; font-size: 22px; }
.summary-sub { margin: 0 0 10px; color: var(--text-secondary); font-size: 14.5px; }
.summary-state { margin: 0; display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
@media (max-width: 620px) {
  .card.summary { flex-direction: column; align-items: flex-start; }
}

/* --- one-line care logger --- */
.logrow { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.logrow select { width: auto; min-width: 132px; }
.logrow input[type="text"] { flex: 1; min-width: 180px; }
.logrow input[type="date"] { width: auto; }

.empty-state { color: var(--text-muted); text-align: center; padding: 44px 10px; }
.readout { color: var(--text-secondary); font-size: 13.5px; }
.readout strong { color: var(--text-primary); }

/* --- weather: a hero figure plus stat tiles --- */
.wx {
  display: flex; align-items: stretch; gap: 28px; flex-wrap: wrap;
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 20px 24px;
  margin-bottom: 10px;
}
.wx-hero { min-width: 186px; }
.wx-label { margin: 0; font-size: 13.5px; font-weight: 650; color: var(--brand); }
.wx-cond {
  margin: 6px 0 0; display: flex; align-items: center; gap: 8px;
  font-size: 15px; color: var(--text-secondary);
}
.wx-icon { font-size: 30px; line-height: 1; }
.fc-icon { font-size: 17px; line-height: 1.1; }

/* Plain-language verdict. This is the line to read; the interval is the
   supporting detail, so the verdict gets the weight. */
.advice { margin-top: 4px; font-size: 13px; color: var(--text-secondary); }
.advice-big { font-size: 14.5px; font-weight: 600; color: var(--text-primary); }
.wx-temp {
  margin: 4px 0 0; font-size: 54px; line-height: 1.02; font-weight: 300;
  letter-spacing: -0.035em; color: var(--text-primary);
}
.wx-temp .wx-lo { font-size: 25px; font-weight: 400; color: var(--text-muted); }
.wx-sub { margin: 5px 0 0; font-size: 13.5px; color: var(--text-secondary); }

.wx-tiles {
  display: grid; gap: 12px 32px; flex: 1;
  grid-template-columns: repeat(auto-fit, minmax(132px, 1fr));
  align-content: center; padding-left: 28px;
  border-left: 1px solid var(--gridline);
}
.tile .t-label { margin: 0; font-size: 13px; font-weight: 600; color: var(--text-muted); }
.tile .t-value {
  margin: 3px 0 0; font-size: 26px; line-height: 1.15; font-weight: 500;
  letter-spacing: -0.025em; color: var(--text-primary);
}
.tile .t-value .unit { font-size: 14px; font-weight: 400; color: var(--text-muted); }
.tile .t-note { margin: 1px 0 0; font-size: 12.5px; color: var(--text-muted); }

.t-forecast { display: flex; gap: 15px; margin: 6px 0 0; }
.fc { display: grid; gap: 1px; }
.fc-day { font-size: 12px; color: var(--text-muted); }
.fc-t { font-size: 18px; font-weight: 500; }
.fc-r { font-size: 12px; color: var(--text-muted); }

.wx.wx-off { display: block; }
.wx-foot { margin: 0 0 18px; max-width: 78ch; }

@media (max-width: 720px) {
  .wx-tiles { padding-left: 0; border-left: none;
              border-top: 1px solid var(--gridline); padding-top: 15px; }
  .wx-temp { font-size: 44px; }
}

/* --- segmented control --- */
.segmented {
  display: inline-flex; border: 1px solid var(--baseline);
  border-radius: 999px; overflow: hidden; background: var(--surface-solid);
}
.segmented a {
  padding: 7px 15px; font-size: 13.5px; font-weight: 600;
  color: var(--text-secondary); text-decoration: none;
}
.segmented a:hover { background: var(--surface-2); text-decoration: none; }
.segmented a.on { background: var(--brand); color: #fff; }

/* --- chat --- */
.thread { display: grid; gap: 13px; margin-bottom: 18px; }
.msg { display: grid; gap: 3px; max-width: 78%; }
.msg.user { justify-self: end; justify-items: end; }
.msg.assistant { justify-self: start; }
.bubble {
  padding: 11px 16px; border-radius: 18px;
  border: 1px solid var(--border);
  background: var(--surface-solid);
  box-shadow: var(--shadow);
  white-space: pre-wrap; overflow-wrap: anywhere;
}
.msg.user .bubble {
  background: var(--brand); border-color: var(--brand); color: #fff;
  border-bottom-right-radius: 6px;
}
.msg.assistant .bubble { border-bottom-left-radius: 6px; }
.stamp { font-size: 11.5px; color: var(--text-muted); }

/* Field and Send share a line, the button flush right against the field. stretch
   rather than center so the button is exactly as tall as the input beside it. */
.composer-top {
  display: flex; align-items: stretch; gap: 10px; margin-bottom: 11px;
}
.composer-top input[type="text"] { flex: 1; min-width: 0; }
.composer-top button[type="submit"] { flex: none; min-width: 112px; }

/* --- "Thinking" ----------------------------------------------------------
   Three dots in an assistant bubble while the turn runs. The page reloads every
   two seconds during that time, so this animation restarts each reload — which
   is fine at this duration and is the price of having no JavaScript. */
.bubble.typing { display: inline-flex; align-items: center; gap: 5px;
                 padding: 15px 17px; }
.bubble.typing span {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--text-muted);
  animation: blink 1.25s ease-in-out infinite;
}
.bubble.typing span:nth-child(2) { animation-delay: 0.18s; }
.bubble.typing span:nth-child(3) { animation-delay: 0.36s; }
@keyframes blink {
  0%, 70%, 100% { opacity: 0.28; transform: translateY(0); }
  35%           { opacity: 1;    transform: translateY(-2.5px); }
}
@media (prefers-reduced-motion: reduce) {
  .bubble.typing span { animation: none; opacity: 0.55; }
}

/* Every control in the composer goes flat and unclickable while waiting, so the
   state is obvious rather than something you discover by clicking. */
.composer :disabled { opacity: 0.5; cursor: not-allowed; }
.composer button:disabled:hover {
  background: var(--brand); border-color: var(--brand);
}
/* Wraps rather than overflows. Without this the row's contents cannot fit on a
   narrow card and the send button slides out past the card's edge instead. */
.composer-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.composer-row .spacer { margin-left: auto; }

/* --- File pickers --------------------------------------------------------
   The native control restyled, not replaced. The usual trick — a hidden input
   behind a pretty label — throws away the browser's readout of which file you
   picked, and with no JavaScript here there is nothing to put it back with.
   ::file-selector-button makes the button ours and keeps that readout. */
input[type="file"] {
  font: inherit; font-size: 13px; color: var(--text-secondary);
  width: auto; max-width: 100%;
  padding: 0; border: none; background: none;
}
input[type="file"]::file-selector-button {
  font: inherit; font-weight: 600; font-size: 13px;
  margin: 0 10px 0 0; padding: 7px 15px;
  border-radius: 999px; border: 1px solid var(--baseline);
  background: var(--surface-solid); color: var(--brand);
  cursor: pointer;
  transition: background 120ms ease, border-color 120ms ease, color 120ms ease;
}
input[type="file"]::file-selector-button:hover {
  background: var(--surface-2); border-color: var(--leaf);
  color: var(--brand-hover);
}

/* Compact: sits on the chat composer's one row. */
.filepick { display: inline-flex; align-items: center; gap: 7px; margin: 0;
            font-size: 13.5px; color: var(--text-secondary); font-weight: 600; }
.filepick-icon { font-size: 15px; line-height: 1; }

/* Roomy: a tinted dashed panel on the add-plant form, so it reads as a place to
   put something rather than one more control lost among the text fields. */
.uploader {
  display: flex; gap: 13px; align-items: flex-start;
  padding: 14px 16px; border-radius: 12px;
  border: 1.5px dashed var(--baseline);
  background: var(--surface-2);
}
.uploader-icon { font-size: 21px; line-height: 1.15; }
.uploader-body { min-width: 0; }
.uploader-body label { display: block; margin-bottom: 8px; }
.uploader-body .hint { margin-top: 8px; }

/* The plant picker. Sits with the photo control on the composer row, and wraps
   to its own line on a phone rather than squeezing the select down to nothing. */
.pickplant { display: inline-flex; align-items: center; gap: 8px; margin: 0;
             font-size: 13.5px; color: var(--text-secondary); font-weight: 600; }
/* min-width:0 lets the select compress when the row is tight, instead of holding
   its intrinsic width and pushing its neighbours out of the card. */
.pickplant select { width: auto; max-width: 260px; min-width: 0; font-size: 13.5px;
                    padding: 6px 8px; font-weight: 500; }
@media (max-width: 560px) {
  .pickplant { width: 100%; }
  .pickplant select { flex: 1; max-width: none; }
}

/* The standing "we are talking about X" banner. Tinted rather than coloured
   text: it is a state, not a warning, and it must not read as a status chip. */
.pinned {
  display: flex; align-items: center; flex-wrap: wrap; gap: 8px;
  margin: 12px 0 0; padding: 9px 12px;
  border: 1px solid var(--border); border-radius: 10px;
  background: var(--surface-solid);
  font-size: 14px;
}
.pinned form { display: inline; margin-left: auto; }

/* --- Destructive actions -------------------------------------------------
   Delete sits behind the "Edit this plant" fold and then behind a dialog,
   because there is no undo for it — Archive is the reversible neighbour.

   The dialog is a :target overlay, so it is a genuine popup with no
   JavaScript. This app has none anywhere, and a destructive action is a poor
   place to start depending on it: if the script failed to load, an onclick
   confirm() would fail *open* and delete without asking.

   Contrast measured, not guessed:
     #d03b3b + white text ......... 4.80:1  (button fill)
     #d03b3b on --surface-solid ... 4.74:1  (link text)
   Both clear 4.5:1, so the red carries text rather than only decorating. */
.danger-zone {
  margin: 18px 0 0;
  padding-top: 14px;
  border-top: 1px solid var(--gridline);
}
a.danger-link {
  color: var(--critical); font-weight: 600; text-decoration: none;
  border-bottom: 1px solid var(--tint-critical);
}
a.danger-link:hover { border-bottom-color: var(--critical); }

.modal { display: none; }
.modal:target {
  display: flex;
  position: fixed; inset: 0; z-index: 60;
  align-items: center; justify-content: center;
  padding: 24px;
}
/* Full-bleed cancel target behind the panel: clicking away closes it, the same
   as any dialog. Not tab-reachable — Cancel is the keyboard route. */
.modal-veil {
  position: absolute; inset: 0;
  background: rgba(32, 41, 31, 0.44);
}
.modal-panel {
  position: relative;
  width: 100%; max-width: 460px;
  padding: 22px 24px 20px;
  background: var(--surface-solid);   /* opaque: the watermark must not read
                                         through a confirmation */
  border-radius: var(--radius);
  border-top: 3px solid var(--critical);
  box-shadow: 0 12px 44px rgba(32, 41, 31, 0.3);
}
.modal-panel h2 { margin: 0 0 10px; font-size: 19px; }
.modal-panel p { margin: 0 0 12px; color: var(--text-secondary); }
.modal-panel p:last-of-type { margin-bottom: 0; }
.modal-actions {
  display: flex; align-items: center; justify-content: flex-end;
  gap: 10px; margin-top: 18px;
}
.modal-actions form { margin: 0; }
a.btnlike {
  padding: 9px 16px; border-radius: 999px;
  border: 1px solid var(--baseline);
  background: var(--surface-solid); color: var(--text-primary);
  font: inherit; font-weight: 600; font-size: 14.5px; text-decoration: none;
}
a.btnlike:hover { background: var(--surface-2); border-color: var(--leaf); }
button.danger.solid {
  background: var(--critical); border-color: var(--critical); color: #fff;
}
button.danger.solid:hover { background: #b02f2f; border-color: #b02f2f; }

/* Confirmation that a delete happened — the plant is gone from the table, so
   without this the page just silently has one fewer row. */
.flash {
  display: flex; align-items: center; gap: 10px;
  margin: 0 0 14px; padding: 11px 16px;
  background: var(--tint-muted);
  border: 1px solid var(--baseline);
  border-radius: 999px;
  color: var(--text-secondary); font-size: 14.5px;
}

/* --- Photo lightbox ------------------------------------------------------
   Same :target machinery as the confirm dialog, so viewing a photo costs no
   request, hijacks no tab, and closes with the Back button. Everything a photo
   might need fixing is in here, because both faults share a cause: a picture
   that came through Telegram can be under the wrong plant, and its date is only
   ever when it arrived unless somebody says otherwise. */
.modal-panel.wide {
  max-width: min(760px, 94vw);
  border-top-color: var(--brand);
  padding: 18px;
}
.lightbox .modal-panel img {
  display: block;
  width: 100%;
  max-height: 62vh;
  object-fit: contain;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
}
.lightbox-close {
  position: absolute; top: 8px; right: 12px; z-index: 1;
  width: 32px; height: 32px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%;
  background: var(--surface-solid);
  border: 1px solid var(--baseline);
  color: var(--text-secondary);
  font-size: 20px; line-height: 1; text-decoration: none;
}
.lightbox-close:hover { color: var(--text-primary); border-color: var(--leaf); }
.lightbox-meta { margin: 12px 2px 14px; }
.lightbox-edit {
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: 12px;
  padding-top: 14px; border-top: 1px solid var(--gridline);
}
.lightbox-edit label {
  display: flex; flex-direction: column; gap: 5px;
  font-size: 13px; font-weight: 600; color: var(--text-secondary);
}
.lightbox-edit select, .lightbox-edit input[type="date"] { width: auto; min-width: 168px; }
/* The caption takes the slack, so the two fixed-width controls keep their size
   and the row still wraps cleanly on a narrow window. */
.lightbox-edit label.grow { flex: 1 1 220px; }
.lightbox-edit label.grow input { width: 100%; }
.lightbox-edit button { margin-left: auto; }
.lightbox-actions {
  display: flex; align-items: center; gap: 18px;
  margin: 14px 2px 0;
}
/* --text-muted would be 3.85:1 here, under the 4.5:1 this size needs. */
a.quiet-link { color: var(--text-secondary); font-size: 13.5px; margin-left: auto; }

/* A photo whose date is only its arrival time. Marked rather than silently
   shown as fact, since the date is what orders a growth timeline. */
.guess {
  display: inline-flex; align-items: center; justify-content: center;
  width: 15px; height: 15px; margin-left: 4px;
  border-radius: 50%;
  background: var(--tint-warning);
  border: 1px solid #e0a51a;
  color: #6b4c05;
  font-size: 10.5px; font-weight: 700;
  cursor: help;
}
