:root {
  --bg: #f5f6fb;
  --panel: #ffffff;
  --border: #e2e4e9;
  --text: #1f2430;
  --muted: #737a8c;
  --accent: #7536ff;
  --accent-soft: #ecedfa;
  --green: #0f9d6a;
  --green-soft: #e1f9f0;
  --danger: #e0546a;
  /* The two tinted states - overdue and due - as tokens rather than literals,
     so the dark block below can flip a surface without hunting the hex down in
     fifteen places. */
  --danger-soft: #fbe9ec;
  --danger-line: #f3c9d1;
  --danger-ink: #a12b41;
  --warn: #eda100;
  --warn-soft: #fdf0d8;
  --warn-line: #f0d49a;
  --warn-ink: #8a5500;
  --radius: 10px;
  --series-made: #8c52ff;
  --series-done: #00d68f;
  --series-over: #f5a623;
  /* The header ribbon. Every ribbon colour is one of these six tokens, so a
     theme is a six-line override and nothing else in the page moves. */
  --header-bg: #10281d;
  --header-bg-2: #1f4a34;
  --header-text: #ffffff;
  --header-dim: rgba(255, 255, 255, 0.65);
  --header-line: rgba(255, 255, 255, 0.15);
  --header-chip: rgba(255, 255, 255, 0.1);
  --header-chip-hover: rgba(255, 255, 255, 0.18);
  --header-chip-line: rgba(255, 255, 255, 0.2);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

* { box-sizing: border-box; }

/* ---------------------------------------------------------------------------
   Dark mode. A separate switch from the header colour: every ribbon but Paper
   is already dark, and people pick those to sit on a light page, so the two
   choices are not the same choice. Nothing here but the page tokens - the
   ribbon keeps whatever theme is set, and the garden keeps its own daylight,
   because a dimmed lawn just looks broken.
   --------------------------------------------------------------------------- */

html[data-dark] {
  --bg: #12141a;
  --panel: #1a1d25;
  --border: #2b3040;
  --text: #e6e8ee;
  --muted: #98a0b3;
  /* Lifted off the light-mode accent: #7536ff has too little contrast against
     a near-black panel to read as a link. */
  --accent: #9b6bff;
  --accent-soft: #262040;
  --green: #2fd39b;
  --green-soft: #14322a;
  --danger: #ff7d90;
  --danger-soft: #3a2028;
  --danger-line: #5a2c38;
  --danger-ink: #ffb3c0;
  --warn: #f0b429;
  --warn-soft: #3a2e12;
  --warn-line: #5c4715;
  --warn-ink: #f4cf7a;
  color-scheme: dark;
}


/* A display rule of ours will otherwise beat the browser's own [hidden] rule.
   Stated once here so every hideable element behaves. */
[hidden] { display: none !important; }

/* No double-tap zoom anywhere. On html so it covers the strip below body too;
   pinch-zoom still works. */
html { touch-action: manipulation; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  min-height: 100vh;
}

header {
  background: linear-gradient(120deg, var(--header-bg) 0%, var(--header-bg-2) 100%);
  padding: 0;
  /* Installed on a phone the app draws under the status bar, so the header
     carries the notch inset itself. Zero on every other device. */
  padding-top: env(safe-area-inset-top, 0px);
}

.header-inner {
  max-width: 1440px;
  margin: 0 auto;
  padding: 16px 32px 0;
}

.brand-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 16px;
}

.brand-row .brand-mark {
  height: 26px;
  width: 26px;
  flex: none;
  border-radius: 7px;
}

.brand-name {
  color: var(--header-text);
  font-weight: 700;
  font-size: 0.95rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

h1 {
  margin: 0 0 4px;
  font-size: 1.6rem;
  color: var(--header-text);
}

.subtitle {
  color: var(--header-dim);
  margin: 0 0 20px;
  font-size: 0.9rem;
}

nav.tabs {
  display: flex;
  gap: 4px;
  border-bottom: 1px solid var(--header-line);
}

nav.tabs button {
  background: none;
  border: none;
  padding: 10px 18px;
  font-size: 0.95rem;
  color: var(--header-dim);
  cursor: pointer;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
}

nav.tabs button:hover {
  color: var(--header-text);
}

nav.tabs button.active {
  color: var(--header-text);
  border-bottom-color: var(--series-made);
  font-weight: 600;
}

/* Undo sits with the tabs, beside the garden switch, and greys out when
   there is nothing to take back. */
#undo-btn { margin-left: auto; }
#undo-btn:disabled { opacity: 0.4; }
#undo-btn:not(:disabled) { cursor: pointer; }
#undo-btn + .garden-toggle-btn { margin-left: 8px; }

.garden-toggle-btn {
  margin-left: 12px;
  align-self: center;
  background: var(--header-chip);
  border: 1px solid var(--header-chip-line);
  color: var(--header-text);
  padding: 6px 12px;
  border-radius: 6px;
  font-size: 0.8rem;
  cursor: pointer;
}

.garden-toggle-btn:hover {
  background: var(--header-chip-hover);
  color: var(--header-text);
}

main {
  max-width: 1440px;
  margin: 0 auto;
  padding: 24px 32px 60px;
}

/* The garden beside the tickets. There used to be a third column - a sidebar
   carrying the New Task button and the due-today card - but on a narrow window
   with the garden open it wrapped under the task list, which left the button
   you press most at the very bottom of the page. Both moved into the header
   corner, where they are on screen at every width, and the column went with
   them. */
.app-layout {
  display: grid;
  grid-template-columns: 324px minmax(0, 1fr);
  grid-template-areas: "garden content";
  gap: 24px;
  align-items: start;
}

.app-layout > .garden-col { grid-area: garden; }
.app-layout > .content-col { grid-area: content; }

/* Garden hidden: give its track back instead of leaving a 324px hole. */
.app-layout.no-garden {
  grid-template-columns: minmax(0, 1fr);
  grid-template-areas: "content";
}

/* With no garden there is room for two columns well below 1275px, so the
   no-garden layout above holds until the phone breakpoint takes over. */

.content-col {
  min-width: 0;
}

.garden-col h2 {
  font-size: 0.95rem;
  margin: 0 0 14px;
}

.chart-legend {
  display: flex;
  gap: 16px;
  font-size: 0.75rem;
  color: var(--muted);
  margin-bottom: 14px;
}

.chart-legend .swatch {
  width: 8px;
  height: 8px;
  border-radius: 2px;
  display: inline-block;
  margin-right: 5px;
}

.chart-legend .swatch.made { background: var(--series-made); }
.chart-legend .swatch.done { background: var(--series-done); }
.chart-legend .swatch.over { background: var(--series-over); }

.chart-group {
  margin-bottom: 18px;
}

.chart-group:last-child { margin-bottom: 0; }

.chart-group-label {
  font-size: 0.85rem;
  color: var(--muted);
  margin-bottom: 7px;
}

.chart-metric-row {
  display: flex;
  align-items: center;
  gap: 10px;
  height: 20px;
}

.chart-metric-row + .chart-metric-row {
  margin-top: 6px;
}

.chart-bar-track {
  flex: 1;
  min-width: 0;
}

.chart-bar {
  height: 12px;
  border-radius: 5px;
  min-width: 4px;
  transition: width 0.2s ease;
}

.chart-bar.made { background: var(--series-made); }
.chart-bar.done { background: var(--series-done); }

.chart-metric-val {
  width: 24px;
  flex-shrink: 0;
  text-align: right;
  font-size: 0.9rem;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}

.chart-circles {
  display: flex;
  gap: 18px;
}

.chart-circle {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
}

.chart-circle svg {
  display: block;
}

.chart-circle-frac {
  font-size: 0.9rem;
  font-weight: 700;
  fill: var(--text);
  font-variant-numeric: tabular-nums;
}

.chart-circle-frac.over { fill: var(--series-over); }

.chart-circle-cap {
  font-size: 0.7rem;
  color: var(--muted);
}

.chart-circle-cap.over {
  color: var(--series-over);
  font-weight: 700;
}

.summary-headline {
  display: flex;
  align-items: baseline;
  gap: 8px;
}

.summary-num {
  font-size: 1.7rem;
  font-weight: 700;
  color: var(--text);
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

.summary-headline.completed .summary-num {
  color: var(--series-done);
}

.summary-label {
  font-size: 0.85rem;
  color: var(--muted);
}

.summary-breakdown {
  list-style: none;
  margin: 10px 0 18px;
  padding: 0 0 0 4px;
  border-left: 2px solid var(--border);
}

.summary-breakdown li {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 4px 0 4px 10px;
  font-size: 0.9rem;
}

.summary-cat {
  color: var(--text);
  font-weight: 600;
}

.summary-cat.priority { color: var(--warn); }
.summary-cat.active { color: var(--accent); }
.summary-cat.archived { color: #2aa7b8; }

.summary-catnum {
  font-weight: 700;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

/* A sticky column that has wrapped onto its own row slides up and hovers over
   the content above it as you scroll, so sticky is only on while the column
   genuinely sits beside the content. */
@media (max-width: 1275px) {
  .garden-col { position: static; }
  /* Stacked under the content, not stuck beside it - so it is part of the
     page again and must not be a scrolling pane of its own. */
  .garden-col { max-height: none; overflow-y: visible; }
}

@media (max-width: 600px) {
  main { padding: 16px 16px 40px; }
  .header-inner { padding: 12px 16px 0; }
}

.view { display: none; }
.view.active { display: block; }

.panel {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 20px;
  margin-bottom: 20px;
}

.field-label {
  font-size: 0.8rem;
  color: var(--muted);
  margin-bottom: 4px;
  display: block;
}

.field-row {
  display: flex;
  flex-direction: column;
}

.field-row-inline {
  display: flex;
  gap: 8px;
}

.field-row-inline > * { flex: 1; }

.checkbox-row {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.9rem;
  color: var(--text);
  cursor: pointer;
}

.checkbox-row input[type="checkbox"] {
  width: 16px;
  height: 16px;
  cursor: pointer;
}

.new-task-btn-row {
  display: flex;
  justify-content: flex-end;
  margin-bottom: 12px;
}

.overview-panel {
  margin-top: 8px;
}

.new-task-btn {
  background: var(--accent);
  color: white;
  border: none;
  padding: 10px 18px;
  border-radius: 8px;
  font-size: 0.9rem;
  cursor: pointer;
  font-weight: 600;
  white-space: nowrap;
}

.new-task-btn:hover { opacity: 0.9; }

section.list-section h2 {
  font-size: 1rem;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  margin: 28px 0 10px;
}

.active-priority-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 0 24px;
}

.active-priority-grid .list-section h2 {
  margin-top: 10px;
}

ul.task-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.completed-day-group {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.completed-day-group + .completed-day-group {
  margin-top: 14px;
}

.completed-day-heading {
  font-size: 0.75rem;
  font-weight: 700;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

li.task {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-left: 4px solid var(--border);
  border-radius: 8px;
  padding: 12px 14px;
}

li.task input[type="checkbox"] {
  margin-top: 3px;
  width: 18px;
  height: 18px;
  cursor: pointer;
  flex-shrink: 0;
}

.task-body { flex: 1; min-width: 0; }

.task-title {
  font-size: 0.98rem;
  word-break: break-word;
}

li.task.done .task-title {
  text-decoration: line-through;
  color: var(--muted);
}

/* The whole row opens the ticket, so it should look like it will. */
.task { cursor: pointer; }
.task input,
.task button { cursor: pointer; }

.task-notes {
  font-size: 0.85rem;
  color: var(--muted);
  margin-top: 4px;
  white-space: pre-wrap;
  word-break: break-word;
}

.task-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 6px;
}

.tag {
  font-size: 0.75rem;
  background: var(--bg);
  color: var(--text);
  padding: 2px 8px;
  border-radius: 999px;
  display: inline-flex;
  align-items: center;
}

.tag.overdue {
  background: var(--danger-soft);
  color: var(--danger);
}

/* The category bubble. Same shape and weight as the steps counter next to it,
   so a ticket's tags read as one row of chips rather than a highlighter mark
   and a chip. The three colours come from the category itself, inline. */
.tag.category-tag {
  border: 1px solid transparent;
  border-radius: 999px;
  padding: 2px 9px 2px 7px;
  font-size: 0.72rem;
  font-weight: 600;
  gap: 5px;
  white-space: nowrap;
}

.category-emoji {
  font-size: 0.85em;
  line-height: 1;
  flex: none;
}

/* In the sidebar list the bubble sits on its own line with the remove button. */
.key-item .tag.category-tag { max-width: 100%; overflow: hidden; text-overflow: ellipsis; }

.key-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  font-size: 0.82rem;
  color: var(--text);
}

.key-item {
  display: flex;
  align-items: center;
}

.shop-coins {
  font-size: 0.8rem;
  font-weight: 700;
  color: #a97a45;
  margin-bottom: 8px;
}

.shop-columns {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0 10px;
}

.shop-column {
  min-width: 0;
}

.shop-section-title {
  font-size: 0.6rem;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  margin: 10px 0 4px;
}

.shop-column .shop-section-title:first-child {
  margin-top: 0;
}

.shop-info {
  font-size: 0.62rem;
  color: var(--muted);
  margin-bottom: 4px;
}

.shop-grid {
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.shop-tile {
  display: flex;
  align-items: center;
  gap: 4px;
  width: 100%;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 3px 5px;
  text-align: left;
  font-family: inherit;
}

/* The picture-and-name half only describes; the price half buys. */
.shop-tile-info,
.shop-tile-action {
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  font: inherit;
  cursor: pointer;
}
.shop-tile-info {
  display: flex;
  align-items: center;
  gap: 4px;
  flex: 1;
  min-width: 0;
  text-align: inherit;
  color: inherit;
}

.shop-tile:hover {
  border-color: var(--accent);
}

.shop-tile.off {
  opacity: 0.5;
}

.shop-tile.picked {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent);
}

.shop-desc {
  font-size: 0.62rem;
  color: var(--muted);
  line-height: 1.3;
  margin: 4px 0 6px;
  min-height: 2.6em;
}

.shop-sell-btn {
  width: 100%;
  margin: 4px 0 2px;
  padding: 5px 8px;
  border: none;
  border-radius: 8px;
  background: var(--green);
  color: #fff;
  font: inherit;
  font-size: 0.7rem;
  font-weight: 700;
  cursor: pointer;
}
.shop-sell-btn .coin-icon { width: 1.1em; height: 1.1em; vertical-align: -0.2em; }

.shop-tile.equipped {
  border-color: var(--green);
}

.shop-tile-icon {
  font-size: 0.72rem;
  line-height: 1;
  flex-shrink: 0;
}

.shop-tile-label {
  font-size: 0.56rem;
  color: var(--text);
  line-height: 1.15;
  flex: 1;
  min-width: 0;
}

.shop-tile-action {
  font-size: 0.54rem;
  font-weight: 700;
  color: var(--accent);
  flex-shrink: 0;
  white-space: nowrap;
}

.shop-tile.equipped .shop-tile-action {
  color: var(--green);
}

.shop-empty {
  font-size: 0.76rem;
  color: var(--muted);
}

/* Search sits on the header's top line beside the account chip, so it costs
   the ticket list no height at all. It is narrow until you use it.

   The auto margin is for the phone's wrapped row; on the top line the account
   chip is already holding that end of the bar, and a second auto margin here
   would split the gap between them. */
.nav-search {
  margin-left: auto;
  align-self: center;
  display: flex;
  align-items: center;
  gap: 6px;
  background: var(--header-chip);
  border: 1px solid var(--header-chip-line);
  border-radius: 999px;
  padding: 0 10px;
  height: 30px;
  transition: background 0.15s ease, border-color 0.15s ease;
}

.nav-search:focus-within {
  background: var(--header-chip-hover);
  border-color: var(--header-text);
}

.nav-search-icon {
  font-size: 0.8rem;
  opacity: 0.75;
  line-height: 1;
  flex: none;
  border: none;
  background: none;
  padding: 0;
  color: inherit;
  cursor: pointer;
}

.search-input {
  width: 130px;
  border: none;
  background: none;
  color: var(--header-text);
  font-size: 0.82rem;
  font-family: inherit;
  padding: 0;
  transition: width 0.18s ease;
  min-width: 0;
}

.search-input::placeholder { color: var(--header-dim); }
.search-input:focus { outline: none; }
.nav-search:focus-within .search-input,
.nav-search.has-text .search-input { width: 200px; }

/* Chrome draws its own clear button on type=search; ours matches the ribbon. */
.search-input::-webkit-search-decoration,
.search-input::-webkit-search-cancel-button { -webkit-appearance: none; }

.nav-search-clear {
  border: none;
  background: none;
  color: var(--header-dim);
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  padding: 0 1px;
  flex: none;
}

.nav-search-clear:hover { color: var(--header-text); }

/* Nothing to search on the calendar. */
.nav-search[hidden] { display: none !important; }

/* Garden */
.garden-col {
  min-width: 0;
  max-width: 100%;
  position: sticky;
  top: 24px;
  /* The column is a pane of its own: as tall as the screen and no taller, with
     its own scroll. Show a hundred completed tasks and the page gets long, and
     the garden used to stick to the top of it with the shop stranded below the
     fold - the only way down to it was to scroll the whole page to the bottom.
     Now the wheel does whatever is under the pointer: over the tasks the page
     moves and the garden holds still, over the garden the garden moves and the
     tasks hold still. Scroll chaining is left on, so running out of garden
     carries on down the page rather than stopping dead. */
  max-height: calc(100vh - 48px);
  overflow-y: auto;
  /* No bar beside it. It is a garden, not a document. */
  scrollbar-width: none;
  -ms-overflow-style: none;
  /* A sticky box that scrolls needs a scroll padding of its own or the last
     thing in it sits under the bottom edge. */
  padding-bottom: 2px;
}

.garden-col::-webkit-scrollbar { width: 0; height: 0; }

.garden-gardener-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 10px;
}

.garden-gardener-row svg {
  image-rendering: pixelated;
  flex-shrink: 0;
}

.garden-status {
  font-size: 0.76rem;
  color: var(--muted);
  line-height: 1.3;
}

/* The collection counter, sitting after the status sentence in a quieter
   weight so the sentence still reads first. */
.garden-found {
  color: var(--text);
  font-weight: 600;
  white-space: nowrap;
}

.garden-coins {
  position: relative;
  font-size: 0.76rem;
  font-weight: 700;
  color: #a97a45;
  white-space: nowrap;
  margin-left: auto;
}

.shop-coins {
  position: relative;
}

@keyframes coin-bump-anim {
  0% { transform: scale(1); }
  35% { transform: scale(1.3); }
  100% { transform: scale(1); }
}

.coin-bump {
  animation: coin-bump-anim 0.3s ease;
}

.coin-popup {
  position: absolute;
  top: -2px;
  left: 100%;
  margin-left: 4px;
  font-size: 0.72rem;
  color: #2fb380;
  font-weight: 700;
  white-space: nowrap;
  animation: coin-float-up 0.7s ease-out forwards;
  pointer-events: none;
}

@keyframes coin-float-up {
  0% { opacity: 1; transform: translateY(0); }
  100% { opacity: 0; transform: translateY(-14px); }
}

/* The plot and its controls.

   On a desktop there are no buttons - the two actions are one key - so the rail
   is a quiet strip UNDER the plot holding what you are carrying and the keys
   that do it. Beside the plot it would not fit: the garden column is about
   360px and the plot alone is nearly 300 of them, so a side rail there simply
   got painted over by the plot overflowing its wrap.

   In phone view it turns into a real rail down the right-hand side, because
   that is where the thumb is. It sits OUTSIDE the scrolling wrap on purpose:
   the plot is wider than a phone, and a rail inside the wrap would scroll away
   exactly when it is wanted. */
.garden-stage { display: block; }

.garden-controls {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 8px;
}

/* What the gardener has in their hands. It keeps its size when empty so
   nothing shifts about every time something is picked up or set down. */
.garden-holding {
  flex: none;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg);
  padding: 4px 10px 4px 8px;
  display: flex;
  align-items: center;
  gap: 8px;
}

.garden-holding-label {
  font-size: 0.6rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
}

.garden-holding-art {
  height: 30px;
  display: grid;
  place-items: center;
}

.garden-holding-art svg { image-rendering: pixelated; }

.garden-holding-name {
  font-size: 0.66rem;
  line-height: 1.2;
  color: var(--text);
  word-break: break-word;
}

.garden-holding.empty .garden-holding-name { color: var(--muted); }

/* Buttons are a phone thing. On a desktop the two actions are the same key,
   and a pair of buttons for it is clutter next to "Press E". The one
   exception is Shop: the shop is a sheet on a computer too, and this is the
   way in. Help stays a strip under the plot there. */
.garden-ctl-btns { display: none; }
.garden-top-btns { display: flex; margin-left: auto; flex: none; }
html:not([data-mode="phone"]) #garden-help-btn { display: none; }
html:not([data-mode="phone"]) #garden-shop-btn {
  flex-direction: row;
  gap: 5px;
  width: auto;
  padding: 6px 12px;
  font-size: 0.9rem;
  font-weight: 700;
  color: #fff;
  border: none;
  border-radius: 10px;
  background: linear-gradient(180deg, #f0c649, #e0ab24 62%);
  box-shadow: 0 3px 0 #a8770c;
  text-shadow: 0 1px 0 rgba(0, 0, 0, 0.2);
}
html:not([data-mode="phone"]) #garden-shop-btn span { font-size: 0.8rem; }
html:not([data-mode="phone"]) #garden-shop-btn:active { transform: translateY(2px); box-shadow: 0 1px 0 #a8770c; }

.garden-ctl-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  width: 100%;
  padding: 12px 4px;
  font: inherit;
  font-size: 1.15rem;
  line-height: 1;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 8px;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.garden-ctl-btn span {
  font-size: 0.66rem;
  font-weight: 600;
  color: var(--text);
}

.garden-ctl-btn:active { background: var(--accent-soft); border-color: var(--accent); }
.garden-ctl-btn:disabled { opacity: 0.38; cursor: default; }

/* The key legend is the desktop half of the same idea. */
/* On a phone the controls sit in a row UNDER the plot. They were tried down
   the right-hand side first, which cost about 40px of a garden that only just
   fit a 390px screen - the last column went off the edge. Under it, the plot
   keeps its full width and the buttons get to be thumb-sized. */
html[data-mode="phone"] .garden-controls {
  gap: 8px;
  margin-top: 8px;
  /* The plot is taller than a phone screen, so a row that simply followed it
     would sit below the fold - which is the one place controls must never be.
     Sticky keeps it just above the bottom bar while the plot is in view, and
     lets it settle back into the page once you scroll past the garden. */
  position: sticky;
  bottom: calc(98px + env(safe-area-inset-bottom, 0px));
  z-index: 20;
  padding: 6px;
  border-radius: 12px;
  background: var(--panel);
  box-shadow: 0 4px 16px rgba(20, 24, 40, 0.16);
}

html[data-mode="phone"] .garden-ctl-btns {
  display: flex;
  flex: 1;
  gap: 8px;
}

html[data-mode="phone"] .garden-ctl-btn {
  flex: 1 1 0;
  flex-direction: row;
  justify-content: center;
  gap: 6px;
  padding: 13px 6px;
}

html[data-mode="phone"] .garden-ctl-btn span {
  font-size: 0.76rem;
  /* "Put down" wrapping onto two lines made the row jump a line taller the
     moment anything was picked up. */
  white-space: nowrap;
}

/* The chip gives up whatever room the buttons need. */
html[data-mode="phone"] .garden-holding { padding: 4px 8px 4px 6px; gap: 6px; }
html[data-mode="phone"] .garden-holding-label { font-size: 0.54rem; }
html[data-mode="phone"] .garden-holding-name { font-size: 0.72rem; white-space: nowrap; }


.garden-plot-wrap {
  display: flex;
  align-items: flex-start;
  /* A garden you have to drag left and right to see is not a garden. It is
     scaled down to fit by fitPlot(), so there is never anything to scroll to. */
  overflow-x: hidden;
  /* The plot takes the middle of its wrap. `safe` matters: on a narrow screen
     the plot is wider than the wrap, and a plain centre overflows equally both
     ways, putting the left edge somewhere no scrollbar can reach. Safe centring
     falls back to the left edge instead - which is also what lets the plot pan
     to follow the gardener in phone view. */
  justify-content: center;
  justify-content: safe center;
  gap: 8px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--bg);
  padding: 2px;
  /* The height is set by fitPlot, since a transform does not move layout. */
  overflow-y: hidden;
}

.garden-plot {
  position: relative;
  background: #dcefd7;
  outline: none;
  cursor: pointer;
  flex-shrink: 0;
  margin: 0 12px;
}

.garden-plot:focus {
  box-shadow: inset 0 0 0 2px var(--accent);
}

.garden-plot::before,
.garden-plot::after {
  content: '';
  position: absolute;
  top: 0;
  width: 10px;
  height: 100%;
  background:
    repeating-linear-gradient(to bottom, #a97a45 0 8px, #8a5a2e 8px 10px),
    linear-gradient(to right, rgba(0, 0, 0, 0.18), transparent 40%, transparent 60%, rgba(255, 255, 255, 0.18));
  box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);
  z-index: 3;
}

.garden-plot::before { left: -10px; }
.garden-plot::after { right: -10px; }



.garden-section-band {
  position: absolute;
  left: 0;
  width: 100%;
  box-sizing: border-box;
  overflow: hidden;
}

/* Ground you have not bought yet: drained of colour and under a haze, with a
   dashed edge, so it reads as somewhere you could go rather than a rendering
   fault. */
.garden-section-band.dim-1,
.garden-section-band.dim-2 {
  filter: grayscale(0.85) brightness(1.04) contrast(0.82);
}

.garden-section-band.dim-1::after,
.garden-section-band.dim-2::after {
  content: '';
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(
    45deg,
    rgba(255, 255, 255, 0.55) 0 6px,
    rgba(255, 255, 255, 0.38) 6px 12px);
  border-top: 2px dashed rgba(90, 58, 30, 0.35);
  pointer-events: none;
  z-index: 5;
}

.garden-section-band.dim-2::after {
  background: repeating-linear-gradient(
    45deg,
    rgba(255, 255, 255, 0.72) 0 6px,
    rgba(255, 255, 255, 0.6) 6px 12px);
}

.garden-section-roof {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 8px;
  box-shadow: 0 3px 4px rgba(0, 0, 0, 0.25);
  z-index: 1;
}

.garden-section-roof.glass { background: #6bb6c9; }
.garden-section-roof.wood { background: #8a5a2e; }

.garden-section-wall {
  position: absolute;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 6px;
  box-shadow: 0 2px 3px rgba(0, 0, 0, 0.25);
  z-index: 1;
}

/* ---------------------------------------------------------------------------
   Seasons. Every section you open is the next season along: you start in
   Summer, then buy your way down through Autumn, Winter, Spring and round again.
   The ground colour itself is mixed in JS - the season is a tint over whatever
   the theme already paints, so a greenhouse stays a greenhouse. What is in
   here is the light sitting over that ground, the things falling through it,
   the one prop standing in the corner, and the sign saying where you are.
   --------------------------------------------------------------------------- */

.season-wash {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 2;
}

.season-spring .season-wash {
  background: linear-gradient(180deg, rgba(186, 240, 168, 0.30), rgba(255, 255, 255, 0));
}
.season-summer .season-wash {
  background: linear-gradient(180deg, rgba(255, 250, 220, 0.14), rgba(255, 255, 255, 0));
}
.season-autumn .season-wash {
  background: linear-gradient(180deg, rgba(236, 130, 30, 0.22), rgba(160, 40, 30, 0.12));
}
.season-winter .season-wash {
  background: linear-gradient(180deg, rgba(228, 244, 255, 0.46), rgba(196, 222, 244, 0.16));
}

/* Petals, pollen, leaves, snow. Seven per band, placed and timed from the
   band's own hash so they do not reshuffle on every render, and started at a
   negative delay so a band you scroll to is already mid-fall rather than
   waiting to begin. */
.season-drift {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 3;
}

.season-drift i {
  position: absolute;
  top: -12px;
  width: 5px;
  height: 5px;
  opacity: 0;
  animation-name: season-drift;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}

@keyframes season-drift {
  0%   { opacity: 0; transform: translate(0, 0) rotate(0deg) scale(var(--drift-scale, 1)); }
  12%  { opacity: 0.8; }
  86%  { opacity: 0.8; }
  100% { opacity: 0; transform: translate(26px, 290px) rotate(180deg) scale(var(--drift-scale, 1)); }
}

.season-spring .season-drift i { background: #f7bcd6; border-radius: 60% 40% 60% 40%; }
/* Summer has nothing falling: small pale dots over the first ground read as snow. */
.season-summer .season-drift { display: none; }
.season-autumn .season-drift i { background: #e0722c; border-radius: 40% 60% 40% 60%; width: 7px; height: 5px; }
.season-autumn .season-drift i:nth-child(3n) { background: #b3202f; }
.season-autumn .season-drift i:nth-child(3n+1) { background: #f09a3a; }
.season-winter .season-drift i {
  background: #ffffff;
  border-radius: 50%;
  box-shadow: 0 0 3px rgba(255, 255, 255, 0.9);
}

/* Asked for stillness, a season keeps its colour, and nothing
   falls through it. */
@media (prefers-reduced-motion: reduce) {
  .season-drift { display: none; }
}

.garden-gate {
  position: absolute;
  left: 0;
  width: 100%;
  height: 16px;
  z-index: 4;
  display: flex;
  align-items: center;
}

.garden-gate-post {
  position: absolute;
  top: -4px;
  width: 6px;
  height: 24px;
  background: linear-gradient(to right, #5a3a1e, #8a5a2e 60%, #6b4423);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
}

.garden-gate-post.left { left: 6px; }
.garden-gate-post.right { right: 6px; }

.garden-gate-bar {
  position: absolute;
  left: 10px;
  right: 10px;
  top: 3px;
  height: 10px;
  background: repeating-linear-gradient(90deg, #8a5a2e 0 5px, #6b4423 5px 6px, rgba(0, 0, 0, 0.25) 6px 7px);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.15);
  border-radius: 1px;
}

/* The gate's sign is the button that opens it. */
.garden-gate-count {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background: #a97a45;
  border: 1px solid #5a3a1e;
  color: #2b1a08;
  font: inherit;
  font-size: 0.62rem;
  font-weight: 700;
  padding: 3px 8px;
  border-radius: 4px;
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  gap: 3px;
  z-index: 6;
  pointer-events: auto;
}

.garden-gate-count .coin-icon { width: 0.95em; height: 0.95em; }

.garden-gate-count.can-buy {
  cursor: pointer;
  background: #d3a05c;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
}

.garden-gate-count.can-buy:hover {
  background: #e8b673;
}

.garden-gate-count:disabled {
  cursor: default;
  opacity: 0.85;
}

.garden-cell, .garden-decor, .garden-surface {
  position: absolute;
}

.garden-cell, .garden-decor {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  z-index: 2;
}

.garden-cell svg, .garden-decor svg {
  image-rendering: pixelated;
  position: relative;
  z-index: 1;
}

.garden-surface {
  z-index: 1;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.12), inset 0 2px 4px rgba(0, 0, 0, 0.12);
}

/* One light source, up and to the left, so every shadow falls the same way and
   things sit on the ground rather than hovering over it. */
.sprite-shadow {
  position: absolute;
  bottom: 3px;
  left: 50%;
  transform: translateX(-46%);
  width: 62%;
  height: 5px;
  background: radial-gradient(ellipse at 45% 50%, rgba(24, 32, 22, 0.32), rgba(24, 32, 22, 0.14) 65%, transparent 72%);
  border-radius: 50%;
  z-index: 0;
}

.garden-hero {
  position: absolute;
  top: 0;
  left: 0;
  transition: transform 0.1s ease;
  pointer-events: none;
  z-index: 5;
}

.garden-pet {
  position: absolute;
  top: 0;
  left: 0;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  transition: transform 0.4s ease;
  z-index: 4;
}

.garden-pet svg {
  image-rendering: pixelated;
}

.garden-held-indicator {
  position: absolute;
  top: 2px;
  left: 50%;
  transform: translateX(-50%) scale(0.68);
  transform-origin: top center;
  pointer-events: none;
  z-index: 6;
}

.garden-held-indicator svg {
  image-rendering: pixelated;
  display: block;
}

.garden-sparkle {
  position: absolute;
  pointer-events: none;
  z-index: 7;
  animation: garden-sparkle-pop 0.7s ease-out forwards;
}

@keyframes garden-sparkle-pop {
  0%   { transform: translateY(0) scale(0.6); opacity: 1; }
  60%  { transform: translateY(-14px) scale(1.3); opacity: 1; }
  100% { transform: translateY(-24px) scale(0.8); opacity: 0; }
}

/* Three lines of instructions used to sit under the plot on every device -
   the web page explaining itself. On a desktop they are permanent furniture
   for something you learn once, and the help strip right below them says all
   of it properly and on demand. They are kept for the phone's help sheet,
   which is the one place they are read rather than skipped. */
.garden-hint {
  display: none;
  font-size: 0.7rem;
  color: var(--muted);
  margin-top: 8px;
  line-height: 1.5;
  padding-left: 16px;
}

.garden-hint li {
  margin-bottom: 3px;
}

.garden-help-icons {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 10px;
}

.help-icon-btn {
  width: 30px;
  height: 30px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--bg);
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}

.help-icon-btn:hover {
  border-color: var(--accent);
  background: var(--accent-soft);
}

.help-explanation {
  display: none;
  margin-top: 10px;
  padding: 10px 12px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 0.78rem;
  color: var(--text);
  line-height: 1.5;
}

.help-explanation strong {
  display: block;
  margin-bottom: 4px;
  font-size: 0.82rem;
}

.help-explanation p {
  margin: 0;
  color: var(--muted);
}

/* Star and the checkbox are the two things you press while scanning a list, so
   they stay on the row. Edit, Archive and Delete moved in here, where they get
   words instead of glyphs and Delete is no longer a thumb's width from Edit. */
.row-menu-wrap { position: relative; }

.row-menu-btn {
  border: none;
  background: none;
  color: var(--muted);
  font-size: 1.1rem;
  line-height: 1;
  padding: 4px 7px;
  border-radius: 6px;
  cursor: pointer;
}

.row-menu-btn:hover { background: var(--hover); color: var(--text); }

.row-menu {
  position: absolute;
  right: 0;
  top: calc(100% + 4px);
  z-index: 60;
  min-width: 148px;
  padding: 4px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 10px 26px rgba(20, 24, 40, 0.18);
  display: flex;
  flex-direction: column;
}

.row-menu.drop-up { top: auto; bottom: calc(100% + 4px); }

.row-menu button {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 9px 10px;
  border: none;
  background: none;
  font: inherit;
  font-size: 0.84rem;
  color: var(--text);
  text-align: left;
  border-radius: 7px;
  cursor: pointer;
}

.row-menu button:hover { background: var(--hover); }
.row-menu button.danger { color: #c0272d; }
.row-menu button.danger:hover { background: rgba(208, 53, 58, 0.12); }
.row-menu svg { flex: none; }

/* A coin leaving the checkbox you just ticked, on its way to the counter. */
.coin-fly {
  position: fixed;
  width: 22px;
  height: 22px;
  z-index: 300;
  pointer-events: none;
  transition: transform 0.75s cubic-bezier(0.3, 0.9, 0.4, 1), opacity 0.75s ease;
}

.coin-fly svg { width: 22px; height: 22px; display: block; }

/* The header subtitle is the day's fun fact: a small label, then the fact
   itself in the ordinary subtitle weight so it reads as a sentence rather than
   as another number to act on. */
.subtitle {
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
}

.fact-label {
  flex: none;
  font-size: 0.6rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  padding: 2px 8px;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, 0.32);
  color: rgba(255, 255, 255, 0.9);
}

.fact-text { flex: 1 1 240px; min-width: 0; }

/* Only the top of the band you have not bought yet is shown - enough for its
   gate, not a whole panel of grey. The fade is what says there is more. */
.garden-plot-wrap.has-peek {
  overflow-y: hidden;
  position: relative;
}

.garden-plot-wrap.has-peek::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 46px;
  pointer-events: none;
  background: linear-gradient(to bottom, transparent, var(--bg));
}

.task-actions {
  display: flex;
  gap: 2px;
  flex-shrink: 0;
}

.star-btn,
.archive-btn,
.edit-btn,
.delete-btn {
  background: none;
  border: none;
  color: var(--muted);
  cursor: pointer;
  font-size: 1.1rem;
  line-height: 1;
  padding: 2px 6px;
  border-radius: 6px;
  flex-shrink: 0;
}

.star-btn.active {
  color: var(--warn);
}

.star-btn:hover {
  background: var(--accent-soft);
}

.archive-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.archive-btn svg {
  display: block;
}

.archive-btn.active {
  color: var(--accent);
}

.archive-btn:hover {
  color: var(--accent);
  background: var(--accent-soft);
}

.edit-btn:hover {
  color: var(--accent);
  background: var(--accent-soft);
}

.delete-btn:hover {
  color: var(--danger);
  background: var(--danger-soft);
}

li.task.archived {
  opacity: 0.72;
  border-left-style: dashed;
}

.person-key-item {
  justify-content: space-between;
  gap: 6px;
}

/* The categories are a ranking, so each one gets a handle to drag it by. The
   handle rather than the whole row, so the remove button and the pill's own
   text stay clickable. */
.key-sortable {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* `move`, not `grab`: Chrome draws grab and grabbing as a pale open hand that
   all but disappears on a light panel - the same complaint as the white
   I-beam. The four-way arrow is dark, and says "reorder" just as plainly. */
.key-grip {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  padding: 2px;
  border-radius: 4px;
  color: var(--border);
  cursor: move;
  touch-action: none;
}

.key-grip:hover { color: var(--muted); background: var(--bg); }

/* The pill takes the slack, so the handle sits left and the remove button
   right rather than all three spreading across the row. */
.cat-sortable-item .category-tag { margin-right: auto; }

.cat-sortable-item.dragging {
  opacity: 0.55;
  cursor: move;
}

.person-remove-btn {
  background: none;
  border: none;
  color: var(--muted);
  cursor: pointer;
  font-size: 0.95rem;
  line-height: 1;
  padding: 0 4px;
  border-radius: 4px;
  flex-shrink: 0;
}

.person-remove-btn:hover {
  color: var(--danger);
  background: var(--danger-soft);
}

.person-add-row {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 10px;
}

/* Suggested colours for a new category, in place of the colour input that used
   to sit here. A native picker opens an OS dialog and offers sixteen million
   options for a decision that wants about twelve. */
.swatch-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 8px;
}

.swatch {
  width: 22px;
  height: 22px;
  padding: 0;
  border: 2px solid transparent;
  border-radius: 50%;
  background: var(--swatch);
  cursor: pointer;
  flex: none;
  /* The ring sits outside the colour rather than over it, so the colour is
     never obscured by its own selected state. */
  box-shadow: 0 0 0 1px var(--border);
  transition: transform 0.1s ease;
}

.swatch:hover { transform: scale(1.12); }

.swatch.on {
  border-color: var(--panel);
  box-shadow: 0 0 0 2px var(--swatch);
}

/* Still selectable - two categories may legitimately want similar colours -
   but marked, so an accidental repeat is a choice rather than a surprise. */
.swatch.used { opacity: 0.4; }
.swatch.used.on { opacity: 1; }

/* 22px is a comfortable target for a mouse and a poor one for a thumb. */
html[data-mode="phone"] .swatch-row { gap: 9px; }
html[data-mode="phone"] .swatch { width: 30px; height: 30px; }

.person-add-row input[type="text"] {
  flex: 1;
  min-width: 0;
  padding: 5px 8px;
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: 0.8rem;
  font-family: inherit;
}

.person-add-btn {
  background: var(--accent);
  color: #fff;
  border: none;
  border-radius: 6px;
  padding: 6px 10px;
  font-size: 0.78rem;
  font-weight: 600;
  cursor: pointer;
  flex-shrink: 0;
}

.person-add-btn:hover { opacity: 0.9; }

.section-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin: 28px 0 10px;
}

.section-header h2,
.active-priority-grid .section-header h2 {
  margin: 0;
}

.section-toggle-btn {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 5px 12px;
  font-size: 0.8rem;
  color: var(--accent);
  cursor: pointer;
  font-weight: 600;
  white-space: nowrap;
}

.section-toggle-btn:hover {
  background: var(--accent-soft);
  border-color: var(--accent);
}

.section-toggle-group {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

/* A row is a click target first and a drag handle second, so it shows the
   pointer rather than the pale open hand. Dragging still works. */
li.task[draggable="true"] {
  cursor: pointer;
}

li.task.dragging {
  opacity: 0.55;
  cursor: move;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.14);
}

.confetti-piece {
  position: fixed;
  width: 9px;
  height: 9px;
  border-radius: 2px;
  pointer-events: none;
  z-index: 9999;
  will-change: transform, opacity;
  animation: confetti-fly 0.95s cubic-bezier(0.22, 0.61, 0.36, 1) forwards;
}

@keyframes confetti-fly {
  0% { transform: translate(-50%, -50%) rotate(0deg); opacity: 1; }
  100% { transform: translate(calc(-50% + var(--dx)), calc(-50% + var(--dy) + 140px)) rotate(var(--rot)); opacity: 0; }
}

.empty-note {
  color: var(--muted);
  font-size: 0.9rem;
  padding: 6px 2px;
}

/* Calendar */
.cal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 16px;
}

.cal-header .cal-title {
  font-size: 1.1rem;
  font-weight: 600;
}

.cal-nav { display: flex; gap: 6px; }

.cal-nav button {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 6px 12px;
  cursor: pointer;
  font-size: 0.85rem;
  color: var(--text);
}

.cal-nav button:hover { background: var(--bg); }

.legend {
  display: flex;
  gap: 18px;
  font-size: 0.82rem;
  color: var(--muted);
  margin-bottom: 14px;
}

.legend span { display: flex; align-items: center; gap: 6px; }

.dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  display: inline-block;
}

.dot.created { background: var(--accent); }
.dot.completed { background: var(--green); }

.cal-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 6px;
}

/* Working week: the same cells, five columns wide instead of seven. */
.cal-grid.no-weekends {
  grid-template-columns: repeat(5, 1fr);
}

.cal-toggle-group {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

.weekend-toggle {
  border: 1px solid var(--border);
  background: var(--panel);
  color: var(--muted);
  border-radius: 999px;
  padding: 5px 12px;
  font-size: 0.78rem;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
}

.weekend-toggle:hover { border-color: var(--accent); color: var(--accent); }

.weekend-toggle.on {
  color: var(--text);
}

.cal-grid .dow {
  text-align: center;
  font-size: 0.75rem;
  color: var(--muted);
  padding-bottom: 4px;
  font-weight: 600;
}

.cal-cell {
  /* A fixed height, not a minimum: a busy day must never make its row taller
     than the rest of the month. Anything that does not fit is reachable by
     clicking the day. */
  height: 84px;
  min-width: 0;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--panel);
  padding: 5px;
  display: flex;
  flex-direction: column;
  gap: 3px;
  cursor: pointer;
  position: relative;
  overflow: hidden;
}

.cal-cell.empty {
  background: transparent;
  border: none;
  cursor: default;
  min-height: 0;
}

.cal-cell:hover:not(.empty) {
  border-color: var(--accent);
}

.cal-cell.today {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent);
}

.cal-daynum {
  font-size: 0.75rem;
  color: var(--text);
  flex-shrink: 0;
}

.cal-events {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: 3px;
  flex: 1;
  min-width: 0;
  overflow: hidden;
}

/* Modal */
.modal-backdrop {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(20, 22, 30, 0.4);
  align-items: center;
  justify-content: center;
  z-index: 50;
}

.modal-backdrop.active { display: flex; }

.modal {
  background: var(--panel);
  border-radius: var(--radius);
  padding: 20px;
  width: 360px;
  max-width: calc(100vw - 40px);
  max-height: 70vh;
  overflow-y: auto;
}

.modal h3 {
  margin: 0 0 12px;
  font-size: 1.05rem;
}

.modal .modal-close {
  float: right;
  background: none;
  border: none;
  font-size: 1.1rem;
  cursor: pointer;
  color: var(--muted);
}

.modal-group { margin-bottom: 14px; }

.modal-group h4 {
  font-size: 0.78rem;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 6px;
  letter-spacing: 0.03em;
}

.modal-group ul {
  list-style: none;
  margin: 0;
  padding: 0;
  font-size: 0.9rem;
}

.modal-group li {
  padding: 4px 0;
  border-bottom: 1px solid var(--border);
}

.modal-group li:last-child { border-bottom: none; }

.modal-group li.day-task {
  cursor: pointer;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
}

.modal-group li.day-task::after {
  content: '\203A';
  color: var(--muted);
  font-size: 1.1rem;
  line-height: 1;
  flex-shrink: 0;
}

.modal-group li.day-task:hover {
  color: var(--accent);
}

.modal-group li.day-task:hover::after {
  color: var(--accent);
}

.detail-back-btn {
  background: none;
  border: none;
  color: var(--accent);
  cursor: pointer;
  font-size: 0.82rem;
  font-weight: 600;
  padding: 0 0 12px;
}

.detail-back-btn:hover { text-decoration: underline; }

.task-detail {
  display: flex;
  flex-direction: column;
}

.detail-row {
  display: flex;
  gap: 10px;
  padding: 8px 0;
  border-bottom: 1px solid var(--border);
  font-size: 0.9rem;
}

.detail-row:last-child { border-bottom: none; }

.detail-label {
  flex: 0 0 82px;
  color: var(--muted);
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  padding-top: 2px;
}

.detail-value {
  flex: 1;
  min-width: 0;
  word-break: break-word;
}

.detail-actions {
  margin-top: 16px;
}

.detail-edit-btn {
  background: var(--accent);
  color: #fff;
  border: none;
  border-radius: 8px;
  padding: 8px 16px;
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
}

.detail-edit-btn:hover { opacity: 0.9; }

.modal-form {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.modal-form input[type="text"],
.modal-form input[type="date"],
.modal-form textarea,
.modal-form select {
  width: 100%;
  padding: 9px 10px;
  border-radius: 8px;
  border: 1px solid var(--border);
  font-size: 0.92rem;
  font-family: inherit;
  background: var(--panel);
}

.modal-form textarea {
  resize: vertical;
  min-height: 44px;
}

.modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 4px;
}

/* A long task form scrolls, so the Add / Save row pins to the bottom of the
   sheet instead of hiding below the fold. bottom: -20px eats the modal's
   padding so fields don't peek out beneath it. */
.modal-form .modal-actions {
  position: sticky;
  bottom: -20px;
  margin: 4px -20px -20px;
  padding: 10px 20px 20px;
  background: var(--panel);
  border-top: 1px solid var(--border);
}

.modal-actions button {
  padding: 8px 16px;
  border-radius: 8px;
  font-size: 0.9rem;
  cursor: pointer;
  border: 1px solid var(--border);
  background: var(--panel);
  color: var(--text);
}

.modal-actions button.primary {
  background: var(--accent);
  color: white;
  border-color: var(--accent);
  font-weight: 600;
}

.modal-actions button.primary:hover { opacity: 0.9; }
.modal-actions button:not(.primary):hover { background: var(--bg); }

/* ============================================================
   Tend additions: auth gate, account menu, settings, sync badge
   ============================================================ */

body.gated { overflow: hidden; }

#app-shell[hidden] { display: none !important; }

/* --- Auth gate --- */

.auth-screen {
  position: fixed;
  inset: 0;
  z-index: 500;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: linear-gradient(140deg, var(--header-bg) 0%, var(--header-bg-2) 55%, #3b2d63 100%);
  overflow-y: auto;
}

.auth-screen[hidden] { display: none; }

.auth-card {
  width: 100%;
  max-width: 400px;
  background: var(--panel);
  border-radius: 16px;
  padding: 28px;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.35);
}

.auth-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 4px;
}

.auth-brand .brand-mark { height: 32px; width: 32px; border-radius: 8px; }

.auth-brand-name {
  font-weight: 700;
  font-size: 1.1rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.auth-tagline {
  color: var(--muted);
  font-size: 0.85rem;
  margin: 0 0 22px;
}

.auth-card h2 {
  font-size: 1.05rem;
  margin: 0 0 16px;
}

.auth-form { display: flex; flex-direction: column; gap: 12px; }
/* Turnstile's box, held open so the button below does not jump when it
   arrives. Only drawn at all when a site key is set. */
.auth-captcha { min-height: 65px; }

.auth-form label {
  font-size: 0.78rem;
  color: var(--muted);
  display: block;
  margin-bottom: 5px;
}

.auth-form input {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 0.95rem;
  font-family: inherit;
  background: var(--panel);
  color: var(--text);
}

.auth-form input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

.auth-submit {
  margin-top: 4px;
  padding: 11px 16px;
  border: none;
  border-radius: 8px;
  background: var(--accent);
  color: #fff;
  font-size: 0.95rem;
  font-weight: 600;
  cursor: pointer;
  font-family: inherit;
}

.auth-submit:hover { opacity: 0.92; }
.auth-submit:disabled { opacity: 0.55; cursor: default; }

.auth-alt {
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid var(--border);
  font-size: 0.85rem;
  color: var(--muted);
  text-align: center;
}

.auth-link {
  background: none;
  border: none;
  padding: 0;
  color: var(--accent);
  font: inherit;
  cursor: pointer;
  text-decoration: underline;
}

.auth-msg {
  font-size: 0.85rem;
  padding: 10px 12px;
  border-radius: 8px;
  display: none;
}

.auth-msg.error {
  display: block;
  background: var(--danger-soft);
  color: var(--danger);
}

.auth-msg.info {
  display: block;
  background: var(--accent-soft);
  color: var(--accent);
}

.auth-msg.success {
  display: block;
  background: var(--green-soft);
  color: var(--green);
}

.auth-secondary {
  padding: 10px 16px;
  border: 1.5px solid var(--accent);
  border-radius: 8px;
  background: transparent;
  color: var(--accent);
  font: inherit;
  font-size: 0.9rem;
  font-weight: 600;
  cursor: pointer;
}
.auth-secondary:disabled { opacity: 0.55; cursor: default; }

/* The gardener at the gate: their sprite, and a speech bubble that says what
   this screen is for, coaches each field, and explains anything that goes
   wrong. Real text, so a screen reader hears the same help. */
.gate-guide {
  display: flex;
  align-items: flex-end;
  gap: 12px;
  margin: 0 0 18px;
}
.gate-sprite { flex: none; width: 48px; display: flex; justify-content: center; }
.gate-sprite svg {
  display: block;
  image-rendering: pixelated;
  transform: scale(1.6);
  transform-origin: bottom center;
}
.gate-say { flex: 1; min-width: 0; }
.gate-bubble {
  position: relative;
  margin: 0;
  padding: 9px 12px;
  border-radius: 10px;
  background: var(--accent-soft);
  color: var(--text);
  font-size: 0.86rem;
  line-height: 1.4;
}
.gate-bubble::before {
  content: "";
  position: absolute;
  left: -6px;
  bottom: 10px;
  border: 6px solid transparent;
  border-left: 0;
  border-right-color: var(--accent-soft);
}
.gate-bubble.error { background: var(--danger-soft); color: var(--danger-ink); }
.gate-bubble.error::before { border-right-color: var(--danger-soft); }
.gate-bubble.success { background: var(--green-soft); }
.gate-bubble.success::before { border-right-color: var(--green-soft); }
.gate-fix:not(:empty) { margin-top: 6px; font-size: 0.85rem; }

/* The same gardener, standing in an empty list. */
.gardener-note { display: flex; align-items: flex-end; gap: 10px; }
.gardener-note .gn-sprite { flex: none; }
.gardener-note .gn-sprite svg { display: block; image-rendering: pixelated; }

/* confirmed.html: where the email link lands. */
.confirm-card { text-align: center; }
.confirm-card .auth-brand { justify-content: center; }
.confirm-card .gate-guide { text-align: left; }
.confirm-card .auth-submit { display: block; text-decoration: none; }
.confirm-scene { display: flex; justify-content: center; margin: 4px 0; }
.confirm-scene svg { width: 168px; height: 116px; image-rendering: pixelated; }
.confirm-note { font-size: 0.78rem; color: var(--muted); margin: 12px 0 0; overflow-wrap: anywhere; }

.auth-mode-note {
  margin-top: 16px;
  font-size: 0.75rem;
  color: var(--muted);
  line-height: 1.5;
  background: var(--bg);
  padding: 10px 12px;
  border-radius: 8px;
}

/* Local-mode profile list on the gate */
.profile-list { display: flex; flex-direction: column; gap: 8px; margin-bottom: 16px; }

.profile-row {
  display: flex;
  align-items: center;
  gap: 8px;
}

.profile-pick {
  flex: 1;
  text-align: left;
  padding: 11px 14px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--panel);
  font: inherit;
  color: var(--text);
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 10px;
}

.profile-pick:hover { border-color: var(--accent); background: var(--accent-soft); }

.profile-avatar {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  flex: none;
  display: grid;
  place-items: center;
  color: #fff;
  font-size: 0.75rem;
  font-weight: 700;
}

.profile-count { margin-left: auto; font-size: 0.75rem; color: var(--muted); }

.profile-del {
  border: 1px solid var(--border);
  background: var(--panel);
  color: var(--muted);
  border-radius: 8px;
  width: 34px;
  height: 34px;
  font-size: 1.1rem;
  cursor: pointer;
  flex: none;
}

.profile-del:hover { color: var(--danger); border-color: var(--danger); }

/* --- Header account menu --- */

.header-top-row {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 16px;
}

/* Everything after the brand is a right-hand group. The gap is held open
   here rather than by an auto margin on the chip, because search comes before
   the chip now and only exists on the task list - hang the alignment off it
   and the whole group jumps about as you change tab. */
.header-top-row .brand-row { margin-bottom: 0; margin-right: auto; }

.account-menu { position: relative; }

.header-top-row .nav-search { margin-left: 0; margin-right: 4px; }

/* The section's first line, rather than a sidebar that wraps: the list's
   grouping toggle on the left and Due today / New Task on the right, on one
   line. It is the first child of the content column, so its right edge is the
   page's right edge and its left edge lines up with the task list.

   It wraps rather than squeezing - on a phone the grouping toggle is already
   scrolling sideways to fit four labels, so there is nothing left to give. */
.page-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px 12px;
  margin-bottom: 12px;
}

.page-actions-end {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-left: auto;
}

.page-actions .new-task-btn-row { margin: 0; }
.page-actions .list-toolbar { margin: 0; min-width: 0; }
.page-actions .list-toolbar .cat-toggle { margin-bottom: 0; }

.due-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: var(--panel);
  border: 1px solid var(--border);
  color: var(--text);
  padding: 9px 14px;
  border-radius: 999px;
  font: inherit;
  font-size: 0.85rem;
  cursor: pointer;
  white-space: nowrap;
}

.due-chip:hover { border-color: var(--accent); }

.due-chip-count { font-weight: 700; font-variant-numeric: tabular-nums; }
.due-chip-label { color: var(--muted); }

.due-chip.has-due { border-color: var(--warn-line); background: var(--warn-soft); }
.due-chip.has-due .due-chip-count { color: var(--series-over); }

.due-chip.has-overdue { border-color: var(--danger-line); background: var(--danger-soft); }
.due-chip.has-overdue .due-chip-count { color: var(--danger); }

.due-chip-late {
  color: var(--danger);
  font-weight: 600;
  border-left: 1px solid rgba(31, 36, 48, 0.14);
  padding-left: 8px;
}

.account-btn {
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--header-chip);
  border: 1px solid var(--header-chip-line);
  color: var(--header-text);
  padding: 5px 10px 5px 6px;
  border-radius: 999px;
  font: inherit;
  font-size: 0.82rem;
  cursor: pointer;
}

.account-btn:hover { background: var(--header-chip-hover); }

.account-btn .caret { opacity: 0.7; font-size: 0.65rem; }

.account-dropdown {
  position: absolute;
  right: 0;
  top: calc(100% + 8px);
  width: 250px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 16px 40px rgba(0, 0, 0, 0.18);
  padding: 8px;
  z-index: 60;
}

.account-dropdown[hidden] { display: none; }

.account-dropdown .acct-head {
  padding: 8px 10px 10px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 6px;
}

.account-dropdown .acct-name { font-weight: 600; font-size: 0.9rem; }

.account-dropdown .acct-sub {
  font-size: 0.75rem;
  color: var(--muted);
  word-break: break-all;
  margin-top: 2px;
}

.acct-item {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  padding: 9px 10px;
  border-radius: 7px;
  font: inherit;
  font-size: 0.85rem;
  color: var(--text);
  cursor: pointer;
}

.acct-item:hover { background: var(--bg); }
.acct-item.danger { color: var(--danger); }

/* --- Sync badge --- */

.sync-badge {
  font-size: 0.72rem;
  color: var(--header-dim);
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: 999px;
  background: var(--header-chip);
}

.sync-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--series-done);
}

.sync-badge.saving .sync-dot { background: var(--series-over); }
.sync-badge.offline .sync-dot { background: var(--danger); }
.sync-badge.local .sync-dot { background: var(--header-dim); }

/* --- Settings modal --- */

.settings-section {
  padding: 14px 0;
  border-bottom: 1px solid var(--border);
}

.settings-section:last-child { border-bottom: none; }

.settings-section h4 {
  margin: 0 0 4px;
  font-size: 0.85rem;
}

.settings-section p {
  margin: 0 0 10px;
  font-size: 0.78rem;
  color: var(--muted);
  line-height: 1.5;
}

.settings-row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }

.settings-row input[type="text"] {
  flex: 1 1 160px;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: 7px;
  font: inherit;
  font-size: 0.88rem;
}

.settings-btn {
  padding: 8px 14px;
  border: 1px solid var(--border);
  border-radius: 7px;
  background: var(--panel);
  font: inherit;
  font-size: 0.85rem;
  cursor: pointer;
}

.settings-btn:hover { background: var(--bg); }
.settings-btn.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
.settings-btn.primary:hover { opacity: 0.92; }
.settings-btn.danger { color: var(--danger); border-color: var(--danger-line); }
.settings-btn.danger:hover { background: var(--danger-soft); }

.settings-note {
  font-size: 0.75rem;
  color: var(--muted);
  margin-top: 8px;
}

.settings-note.ok { color: var(--green); }
.settings-note.bad { color: var(--danger); }

/* --- Category key tweaks (was "Raised By") --- */

.category-hint {
  font-size: 0.72rem;
  color: var(--muted);
  margin: -6px 0 10px;
  line-height: 1.45;
}

/* --- Small screens --- */

/* Phones and small tablets: tickets first, garden last, and a floating
   "new ticket" button so adding one never means scrolling. */
@media (max-width: 900px) {
  .app-layout,
  .app-layout.no-garden {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "content" "garden";
  }
  .garden-col { position: static; top: auto; max-height: none; overflow-y: visible; }

  /* The plot is a fixed pixel grid, so let the whole thing scroll sideways
     inside its panel rather than stretching the page. */
  /* Never. The plot is scaled to fit its column instead - see fitPlot(). */
  .garden-plot-wrap { overflow-x: hidden; }

  .new-task-btn-row { position: fixed; right: 16px; bottom: 16px; z-index: 40; margin: 0; }
  .new-task-btn {
    border-radius: 999px;
    padding: 14px 22px;
    font-size: 0.95rem;
    box-shadow: 0 10px 28px rgba(31, 36, 48, 0.32);
  }
}

@media (max-width: 720px) {
  .section-header { flex-direction: column; align-items: flex-start; gap: 8px; }
  .header-inner { padding: 14px 16px 0; }
  main { padding: 16px 16px 72px; }
  h1 { font-size: 1.3rem; }
  .active-priority-grid { grid-template-columns: 1fr; }
  .account-dropdown { width: 230px; }
  .sync-badge { display: none; }
  .modal { max-height: 85vh; }
  nav.tabs button { padding: 10px 12px; font-size: 0.9rem; }
  .garden-toggle-btn { font-size: 0.72rem; padding: 5px 9px; }
  /* On a phone a permanent search bar costs a whole row of the screen, so it
     shrinks to just its magnifier, sitting on the tab line. Tapping it opens
     the field; it closes again when emptied. */
  nav.tabs { flex-wrap: wrap; }
  /* Opened, search takes a full line; it needs somewhere to wrap to. A window
     narrowed this far is usually in phone mode, but the view can be pinned to
     desktop, and then this is the only rule that gives it the room. */
  .header-top-row { flex-wrap: wrap; }

  .nav-search {
    /* Last in the row, so the magnifier sits hard against the right edge
       rather than floating between the tabs and the garden button. */
    order: 5;
    margin: 0 0 0 10px;
    width: auto;
    padding: 0;
    height: auto;
    background: none;
    border: none;
    align-self: center;
  }

  .nav-search .search-input,
  .nav-search .nav-search-clear { display: none; }

  .nav-search-icon {
    width: 32px;
    height: 32px;
    border-radius: 50%;
    background: var(--header-chip);
    border: 1px solid var(--header-chip-line);
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 1;
    font-size: 0.85rem;
  }

  /* Something is filtered but the box is shut - say so on the icon. */
  .nav-search.has-text .nav-search-icon {
    background: var(--series-made);
    border-color: var(--series-made);
  }


  /* Open: the full row, but only while it is being used. */
  .nav-search.open {
    order: 3;
    width: 100%;
    margin: 0 0 8px;
    padding: 0 6px 0 10px;
    height: 34px;
    background: var(--header-chip);
    border: 1px solid var(--header-chip-line);
    border-radius: 999px;
  }

  .nav-search.open .search-input,
  .nav-search.open .nav-search-clear { display: block; }
  .nav-search.open .search-input { width: 100%; flex: 1; }

  .nav-search.open .nav-search-icon {
    width: auto;
    height: auto;
    background: none;
    border: none;
    border-radius: 0;
    opacity: 0.75;
    font-size: 0.8rem;
  }
}

/* ============================================================
   World chooser: sign-up card and settings
   ============================================================ */

.chooser {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: 4px 0 2px;
}

.chooser-row {
  display: flex;
  align-items: center;
  gap: 10px;
}

.chooser-label {
  font-size: 0.75rem;
  color: var(--muted);
  width: 44px;
  flex: none;
}

.choice-group {
  display: flex;
  gap: 6px;
  flex: 1;
}

.choice {
  flex: 1;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--panel);
  font: inherit;
  font-size: 0.85rem;
  color: var(--text);
  cursor: pointer;
  text-align: center;
}

.choice:hover { border-color: var(--accent); }

.choice.on {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent);
  font-weight: 600;
}

.choice-name { display: block; }

.world-preview {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: 10px;
  height: 104px;
  border-radius: 8px;
  border: 1px solid var(--border);
  overflow: hidden;
  padding: 0 10px;
}

.world-preview .wp-item {
  display: flex;
  align-items: flex-end;
  height: 100%;
  padding-bottom: 10px;
}

.world-preview svg {
  display: block;
  image-rendering: pixelated;
  transform: scale(1.6);
  transform-origin: bottom center;
}

.chooser-blurb {
  margin: 0;
  font-size: 0.75rem;
  color: var(--muted);
  line-height: 1.45;
}

#world-settings .chooser { margin-top: 2px; }

/* ============================================================
   Calendar: pick which dates are plotted
   ============================================================ */

.legend-toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--panel);
  font: inherit;
  font-size: 0.8rem;
  color: var(--muted);
  cursor: pointer;
}

.legend-toggle:hover { border-color: var(--accent); }

.legend-toggle.on {
  color: var(--text);
  border-color: var(--border);
  background: var(--bg);
}

/* An off series keeps its colour but drains it, so the row still reads as a key. */
.legend-toggle:not(.on) .dot { opacity: 0.3; }
.legend-toggle:not(.on) { text-decoration: line-through; }

.dot.due { background: var(--series-over); }


.day-task-note {
  font-size: 0.72rem;
  padding: 1px 6px;
  border-radius: 999px;
  margin-left: 6px;
  vertical-align: middle;
}

.day-task-note.overdue { background: var(--danger-soft); color: var(--danger); }
.day-task-note.done { background: var(--green-soft); color: var(--green); }

/* ============================================================
   Calendar cells: labelled counts
   ============================================================ */

/* Bubble and number side by side, sized to fit the narrowest cell the grid
   produces (about 72px of usable width) without wrapping or clipping. */
.cal-stat {
  display: flex;
  align-items: center;
  gap: 4px;
  line-height: 1;
  max-width: 100%;
  white-space: nowrap;
}

/* The word sits in its own tinted bubble with dark text. Coloured words on a
   white cell were the hardest thing on the calendar to read - the number
   carries the colour, the label carries the meaning. */
.cal-stat-label {
  font-size: 0.58rem;
  font-weight: 700;
  line-height: 1;
  padding: 3px 5px;
  border-radius: 999px;
  white-space: nowrap;
}

.cal-stat-num {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
}

/* Every count reads at the same size - the colour and the order carry the
   emphasis, not the type size. */
.cal-stat .cal-stat-num { font-size: 1.05rem; }

/* Numbers keep the series colour; bubbles pair a soft tint with text dark
   enough to read comfortably against it. */
.cal-stat.due .cal-stat-num { color: var(--series-over); }
.cal-stat.due .cal-stat-label { background: var(--warn-soft); color: var(--warn-ink); }

.cal-stat.due.overdue .cal-stat-num { color: var(--danger); }
.cal-stat.due.overdue .cal-stat-label { background: var(--danger-soft); color: var(--danger-ink); }

.cal-stat.completed .cal-stat-num { color: var(--series-done); }
.cal-stat.completed .cal-stat-label { background: var(--green-soft); color: #0b6b4a; }

.cal-stat.created .cal-stat-num { color: var(--series-made); }
.cal-stat.created .cal-stat-label { background: var(--accent-soft); color: #5b28c9; }

/* ============================================================
   Due today button
   ============================================================ */

/* On a phone a calendar cell is about 48px wide, which is not enough for a word
   and a number side by side. The legend chips above carry the meaning, so drop
   the labels and keep the coloured numbers. */
@media (max-width: 640px) {
  .cal-stat-label { display: none; }
  .cal-events { align-items: center; gap: 1px; }
  .cal-stat .cal-stat-num { font-size: 1rem; }
}

/* The counts from the calendar square, repeated at the top of the day it opens. */
.day-summary {
  display: flex;
  align-items: baseline;
  gap: 18px;
  flex-wrap: wrap;
  padding-bottom: 14px;
  margin-bottom: 4px;
  border-bottom: 1px solid var(--border);
}

/* The modal has room a calendar cell does not, so the bubble sits beside its
   number here rather than above it. */
.day-summary .cal-stat {
  flex-direction: row;
  align-items: baseline;
  gap: 8px;
}

.day-summary .cal-stat-label { font-size: 0.75rem; padding: 4px 9px; }

/* ============================================================
   Calendar: agenda view
   ============================================================ */

/* The legend and the view switch share one row; the legend is redrawn on every
   render, so the switch lives beside it rather than inside it. */
.cal-legend-row {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 14px;
}

.cal-legend-row .legend { margin-bottom: 0; }

.cal-view-toggle {
  display: flex;
  gap: 4px;
  margin-left: auto;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 3px;
}

.cal-view-toggle button {
  border: none;
  background: none;
  padding: 5px 12px;
  border-radius: 999px;
  font: inherit;
  font-size: 0.78rem;
  color: var(--muted);
  cursor: pointer;
}

.cal-view-toggle button:hover { color: var(--text); }

.cal-view-toggle button.on {
  background: var(--panel);
  color: var(--text);
  font-weight: 600;
  box-shadow: 0 1px 2px rgba(31, 36, 48, 0.12);
}


/* ===================== Plan My Day =====================

   Two shapes of one list, so they share a row. The running order numbers
   itself with a CSS counter rather than a rendered index: the rows are moved
   about in the DOM while a finger is down, and a counter renumbers as they
   move without anything having to re-render mid-drag.                       */

.plan-head {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  margin-bottom: 14px;
}

.plan-head-text { min-width: 0; }
.plan-head h2 { margin: 0; }

.plan-sub {
  margin: 4px 0 0;
  font-size: 0.8rem;
  color: var(--muted);
}

.plan-head .cal-view-toggle { margin-left: auto; flex: none; }

/* The same hint as the ones under a heading elsewhere, but this one sits under
   the whole head block - title, date and the mode toggle beside them - so it
   does not want the pull-up the others use to close a gap under an h2. */
.plan-hint { margin: 10px 0 12px; }

.plan-list, .plan-slots, .plan-drop {
  list-style: none;
  margin: 0;
  padding: 0;
}

.plan-list.ordered { counter-reset: plan-step; }

.plan-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 6px 10px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--panel);
  margin-bottom: 8px;
  /* The whole row is the handle now. No touch-action here, though - a finger
     has to be able to scroll the page off a row, and the drag takes the
     gesture by cancelling it instead (planBeginDrag in app.js). */
  cursor: move;
  user-select: none;
  -webkit-user-select: none;
  /* Rows displaced by a drag are put back where they looked and then let go
     (planFlip in app.js); this is the going. Transform only - nothing here
     animates a layout property, so it is all on the compositor. */
  transition: transform 0.18s cubic-bezier(0.2, 0.7, 0.3, 1), box-shadow 0.18s ease;
}

.plan-list.ordered > .plan-row::before {
  counter-increment: plan-step;
  content: counter(plan-step);
  flex: none;
  width: 22px;
  text-align: right;
  font-size: 0.78rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
}

.plan-row input[type="checkbox"] {
  flex: none;
  width: 20px;
  height: 20px;
  accent-color: var(--green);
  cursor: pointer;
}

.plan-row-body {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  cursor: pointer;
}

.plan-row-title { font-size: 0.92rem; }

/* The task's steps, on a line of their own under the title. */
.plan-row-body .subtask-list { flex-basis: 100%; margin: 2px 0 0; cursor: default; }


/* What time it is down for, shown in the running order so the two shapes of
   the list never disagree about a task you have already placed. */
.plan-when {
  font-size: 0.7rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--accent);
  background: var(--accent-soft);
  border-radius: 999px;
  padding: 2px 8px;
}

.plan-unstar {
  flex: none;
  border: none;
  background: none;
  color: var(--warn);
  font-size: 1rem;
  line-height: 1;
  padding: 4px;
  cursor: pointer;
}

.plan-unstar:hover { color: var(--danger); }

/* The row is draggable all over, but the grip is the one part of it that says
   so and the one part that picks up straight away under a finger. touch-action:
   none is what buys it that: the browser never claims the gesture as a scroll.
   It stays on the grip alone, so a finger anywhere else can still scroll. */
.plan-grip {
  flex: none;
  border: none;
  background: none;
  color: var(--muted);
  font-size: 0.85rem;
  letter-spacing: -3px;
  padding: 8px 6px;
  cursor: move;
  touch-action: none;
  -webkit-tap-highlight-color: transparent;
}

/* Held. No transition, because it is following a finger and has to be exactly
   where the finger is - the easing goes back on the moment it is let go, which
   is what turns the snap home into a landing. */
.plan-row.dragging {
  transition: none;
  position: relative;
  z-index: 5;
  border-color: var(--accent);
  box-shadow: 0 10px 24px rgba(31, 36, 48, 0.28);
  will-change: transform;
  cursor: move;
}

/* The gap it came out of, so the list does not look like it has a hole in it
   while the row is up in the air. */
.plan-row.dragging + .plan-row { transition-delay: 0.02s; }

.plan-cleared { color: var(--green); font-weight: 600; }

/* --- the clock --- */

.plan-slot {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 3px 0;
  border-top: 1px solid var(--border);
}

.plan-slot:last-child { border-bottom: 1px solid var(--border); }

.plan-slot-time {
  flex: none;
  width: 66px;
  padding-top: 10px;
  font-size: 0.74rem;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
  text-align: right;
}

/* The half hour you are actually in. */
.plan-slot.now { background: var(--accent-soft); border-radius: 8px; }
.plan-slot.now .plan-slot-time { color: var(--accent); font-weight: 700; }

/* An empty half hour still has to be a target a thumb can hit. */
.plan-slot-drop {
  flex: 1;
  min-width: 0;
  min-height: 38px;
  padding: 4px 0;
  border-radius: 8px;
  transition: background 0.15s ease;
}

/* Whatever the finger is over, so it is clear where the row is about to go. */
.plan-slot:hover .plan-slot-drop { background: var(--bg); }

.plan-slot .plan-row { margin-bottom: 4px; }
.plan-slot .plan-row:last-child { margin-bottom: 0; }

.plan-tray { margin-top: 16px; }

.plan-tray h3 {
  margin: 0 0 8px;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
}

.plan-tray-drop {
  min-height: 44px;
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  padding: 6px;
}

/* On a computer the tasks waiting for a time sit beside the clock rather than
   under it, and stay in view while the day scrolls, so a drag is a short hop
   sideways instead of a trip down the page. A phone keeps them stacked. */
html:not([data-mode="phone"]) .plan-times {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr);
  gap: 16px;
  align-items: start;
}
html:not([data-mode="phone"]) .plan-tray {
  order: -1;
  margin-top: 0;
  position: sticky;
  top: 12px;
}

/* On a phone the row is the width of the screen and the clock column costs
   width the titles want, so the time shrinks and the numbers go. */
html[data-mode="phone"] .plan-head { flex-wrap: wrap; }
html[data-mode="phone"] .plan-head .cal-view-toggle { margin-left: 0; width: 100%; }
html[data-mode="phone"] .plan-head .cal-view-toggle button { flex: 1; }
html[data-mode="phone"] .plan-slot-time { width: 52px; }
html[data-mode="phone"] .plan-row { padding: 12px 4px 12px 10px; }
html[data-mode="phone"] .plan-grip { padding: 10px 8px; }

/* Agenda cells are taller than number cells, but still all the same height. */
.cal-grid.agenda .cal-cell { height: 118px; }
.cal-grid.agenda .cal-cell.empty { height: 118px; }

.cal-agenda {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex: 1;
  min-width: 0;
  width: 100%;
  max-width: 100%;
  overflow: hidden;
}

.cal-event {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 2px 5px;
  border-radius: 4px;
  font-size: 0.7rem;
  line-height: 1.25;
  white-space: nowrap;
  overflow: hidden;
  min-width: 0;
  max-width: 100%;
}

.cal-event-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  flex: none;
}

.cal-event-title {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

.cal-event.due { background: var(--warn-soft); color: var(--warn-ink); }
.cal-event.due .cal-event-dot { background: var(--series-over); }

.cal-event.due.overdue { background: var(--danger-soft); color: var(--danger-ink); }
.cal-event.due.overdue .cal-event-dot { background: var(--danger); }

.cal-event.completed { background: var(--green-soft); color: #0b6b4a; }
.cal-event.completed .cal-event-dot { background: var(--series-done); }
.cal-event.completed .cal-event-title { text-decoration: line-through; opacity: 0.75; }

.cal-event.created { background: var(--accent-soft); color: #5b28c9; }
.cal-event.created .cal-event-dot { background: var(--series-made); }

.cal-more {
  font-size: 0.66rem;
  color: var(--muted);
  padding: 1px 5px;
  font-weight: 600;
}

@media (max-width: 640px) {
  .cal-grid.agenda .cal-cell { height: 86px; }
  .cal-grid.agenda .cal-cell.empty { height: 86px; }
  .cal-event { font-size: 0.6rem; padding: 1px 3px; gap: 3px; }
  .cal-view-toggle button { padding: 5px 9px; font-size: 0.72rem; }
}


/* The list view needs room: at a third of the page a cell is 84px wide and every
   ticket title truncates to two words. While it is showing, the calendar takes
   the full width and the garden stacks underneath it. */
.app-layout.cal-wide,
.app-layout.cal-wide.no-garden {
  grid-template-columns: minmax(0, 1fr);
  grid-template-areas: "content" "garden";
}
.app-layout.cal-wide .garden-col { position: static; top: auto; max-height: none; overflow-y: visible; }

/* ============================================================
   Subtasks
   ============================================================ */

.tag.subtask-tag {
  background: var(--bg);
  color: var(--muted);
  border: 1px solid var(--border);
  cursor: pointer;
  font: inherit;
  font-size: 0.72rem;
  padding: 2px 8px;
}

.tag.subtask-tag:hover { border-color: var(--accent); color: var(--accent); }

/* A repeating task, on the row and in the day modal. Quiet - the date beside
   it is the thing being read; this only says the date will come round again. */
.tag.repeat-tag {
  background: var(--accent-soft);
  color: var(--accent);
  font-weight: 600;
}
.tag.subtask-tag.all-done { color: var(--green); border-color: #b8e6d4; background: var(--green-soft); }

.subtask-list {
  list-style: none;
  margin: 6px 0 2px;
  padding: 0 0 0 2px;
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.subtask {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.85rem;
  color: var(--text);
}

.subtask input[type="checkbox"] {
  width: 15px;
  height: 15px;
  flex: none;
  cursor: pointer;
  accent-color: var(--green);
}

.subtask.done span {
  text-decoration: line-through;
  color: var(--muted);
}

/* --- editor inside the new / edit forms --- */

.subtask-editor { display: flex; flex-direction: column; gap: 6px; }

.subtask-edit-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 5px;
}

.subtask-edit-row {
  display: flex;
  align-items: center;
  gap: 7px;
}

.subtask-edit-row input[type="checkbox"] {
  width: 15px;
  height: 15px;
  flex: none;
  accent-color: var(--green);
}

.subtask-edit-row input[type="text"] {
  flex: 1;
  min-width: 0;
  padding: 6px 8px;
  border: 1px solid var(--border);
  border-radius: 6px;
  font: inherit;
  font-size: 0.85rem;
}

.subtask-remove {
  border: 1px solid var(--border);
  background: var(--panel);
  color: var(--muted);
  border-radius: 6px;
  width: 26px;
  height: 26px;
  flex: none;
  cursor: pointer;
  font-size: 1rem;
  line-height: 1;
}

.subtask-remove:hover { color: var(--danger); border-color: var(--danger); }

.subtask-add-row { display: flex; gap: 6px; }

.subtask-add-row input {
  flex: 1;
  min-width: 0;
  padding: 7px 9px;
  border: 1px solid var(--border);
  border-radius: 6px;
  font: inherit;
  font-size: 0.85rem;
}

.detail-subtasks {
  list-style: none;
  margin: 6px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
  font-size: 0.85rem;
}

.detail-subtasks li.done { color: var(--muted); text-decoration: line-through; }


/* ---------------------------------------------------------------------------
   iOS Safari zooms the whole page in whenever a focused field is smaller than
   16px, and it does not zoom back out afterwards. Any font size below 16px on
   a phone therefore leaves the app looking permanently magnified, so on small
   screens every field is 16px regardless of what the desktop rule asked for.
   --------------------------------------------------------------------------- */
@media (max-width: 900px) {
  input:not([type="checkbox"]):not([type="radio"]):not([type="color"]),
  select,
  textarea {
    font-size: 16px !important;
  }
}

/* Keep the page clear of the home indicator when the app runs full-screen
   from the home screen. Zero everywhere else. */
body {
  padding-bottom: env(safe-area-inset-bottom, 0px);
}


/* Who is still coming round to you - shown under the one food tile. */
.pet-roster {
  list-style: none;
  margin: 8px 0 0;
  padding: 0;
  display: grid;
  gap: 3px;
  font-size: 0.76rem;
  color: var(--muted);
}

.pet-roster li {
  display: flex;
  justify-content: space-between;
  gap: 8px;
}


/* The magnifying glass speaks through this: a small bubble that appears over
   the hero's head for a moment and then fades. */
.garden-thought {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 6px);
  transform: translateX(-50%);
  background: var(--panel);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 3px 9px;
  font-size: 0.72rem;
  font-weight: 600;
  white-space: nowrap;
  box-shadow: 0 3px 10px rgba(20, 24, 40, 0.18);
  pointer-events: none;
  z-index: 40;
  animation: thought-pop 0.18s ease-out;
}

/* Facing left, the hero is drawn mirrored - so the words inside the bubble are
   flipped back, and read left to right whichever way the gardener is looking. */
.garden-thought > span {
  display: block;
}

.garden-thought.mirrored > span {
  transform: scaleX(-1);
}

.garden-thought::after {
  content: '';
  position: absolute;
  left: 50%;
  top: 100%;
  width: 5px;
  height: 5px;
  margin-left: -6px;
  border-radius: 50%;
  background: var(--panel);
  border: 1px solid var(--border);
}

@keyframes thought-pop {
  from { opacity: 0; transform: translateX(-50%) translateY(4px); }
  to   { opacity: 1; transform: translateX(-50%) translateY(0); }
}

.shop-tile.owned {
  border-color: var(--green);
  opacity: 1;
}

.shop-tile.owned .shop-tile-action {
  color: var(--green);
}


/* On a touch screen the plot is driven by taps and swipes, so the browser
   must not treat those as scrolling or double-tap zoom. */
.garden-plot {
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}


/* Told, briefly, when your other device changed something. */
.sync-toast {
  position: fixed;
  left: 50%;
  /* At the top, where the eye already is. It used to rise from the bottom,
     which on a phone put every coin and every Undo under the bottom bar, and
     under the thumb that had just tapped something. It drops from above now,
     clear of both. The notch inset is its own because it is fixed to the
     viewport rather than sitting inside the header. */
  top: calc(14px + env(safe-area-inset-top, 0px));
  transform: translate(-50%, -14px);
  background: var(--header-bg);
  /* It lands over a dark header, where a drop shadow says nothing. */
  border: 1px solid rgba(255, 255, 255, 0.22);
  color: #ffffff;
  font-size: 0.82rem;
  font-weight: 600;
  padding: 9px 16px;
  border-radius: 999px;
  box-shadow: 0 6px 20px rgba(20, 24, 40, 0.28);
  opacity: 0;
  /* It has an Undo button in it now, so it has to be clickable while shown -
     and unclickable while hidden, or an invisible pill sits over the page. */
  pointer-events: none;
  transition: opacity 0.2s ease, transform 0.2s ease;
  z-index: 200;
  max-width: calc(100vw - 32px);
  text-align: center;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
}

.sync-toast.show { pointer-events: auto; }

.toast-undo {
  flex: none;
  background: rgba(255, 255, 255, 0.16);
  border: 1px solid rgba(255, 255, 255, 0.35);
  color: #fff;
  font: inherit;
  font-weight: 700;
  font-size: 0.78rem;
  padding: 3px 12px;
  border-radius: 999px;
  cursor: pointer;
}

.toast-undo:hover { background: rgba(255, 255, 255, 0.28); }

.sync-toast.show {
  opacity: 1;
  transform: translate(-50%, 0);
}


/* The install panel: a code to scan and the steps each phone needs. */
.install-qr {
  display: flex;
  justify-content: center;
  padding: 14px;
  background: #ffffff;
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.install-qr svg {
  display: block;
  width: 190px;
  height: 190px;
  max-width: 100%;
}

.install-qr-fallback {
  color: var(--muted);
  font-size: 0.85rem;
  padding: 40px 0;
}

.install-steps {
  margin: 8px 0 0;
  padding-left: 18px;
  font-size: 0.86rem;
  line-height: 1.55;
  color: var(--text);
}

.install-steps li + li { margin-top: 6px; }

.install-done {
  background: var(--green-soft);
  color: var(--green);
  border-radius: var(--radius);
  padding: 8px 12px;
  font-weight: 600;
}

#install-link {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.8rem;
}


/* The gold coin. Sized to the text it sits beside, wherever it appears. */
.coin-icon {
  width: 1.05em;
  height: 1.05em;
  display: inline-block;
  vertical-align: -0.16em;
  flex: none;
}

.garden-coins .coin-icon { width: 1.2em; height: 1.2em; }
.shop-coins .coin-icon { width: 1.15em; height: 1.15em; }
.shop-tile-action .coin-icon { margin-right: 2px; }
.coin-help-btn .coin-icon { width: 1.15em; height: 1.15em; vertical-align: -0.2em; }

/* There is no shovel in the emoji set - the nearest character is a donkey -
   so where a shovel is called for we draw the same little pixel spade the
   gardener carries, sized to sit in a line of text like any other glyph. */
.glyph-icon { width: 1em; height: 1.25em; display: inline-block; vertical-align: -0.15em; }
.garden-coins .coin-count { margin-left: 2px; }


.shop-tile.owned .shop-tile-action .on  { color: var(--green); }
.shop-tile.owned .shop-tile-action .off { color: var(--muted); }
.shop-tile.owned { border-color: var(--border); }
.shop-tile.owned:hover { border-color: var(--accent); }


/* ---------------------------------------------------------------------------
   Header themes. Each one sets the same six tokens and nothing else, so the
   rest of the page is untouched by the choice. `midnight` is the default and
   needs no block. The theme id lands on <html> as data-theme.
   --------------------------------------------------------------------------- */

html[data-theme="midnight"] {
  --header-bg: #1d2333;
  --header-bg-2: #2a3350;
}

html[data-theme="slate"] {
  --header-bg: #21252b;
  --header-bg-2: #3a414b;
}

html[data-theme="plum"] {
  --header-bg: #2a1a45;
  --header-bg-2: #4f2f80;
}

html[data-theme="reef"] {
  --header-bg: #07283a;
  --header-bg-2: #0f5f75;
}

html[data-theme="clay"] {
  --header-bg: #31201a;
  --header-bg-2: #7a422b;
}

html[data-theme="ink"] {
  --header-bg: #0e1014;
  --header-bg-2: #1e222b;
}

/* The one light ribbon. Dark text on off-white, so every token flips. */
html[data-theme="paper"] {
  --header-bg: #f7f5f0;
  --header-bg-2: #e8e4dc;
  --header-text: #1f2430;
  --header-dim: rgba(31, 36, 48, 0.6);
  --header-line: rgba(31, 36, 48, 0.14);
  --header-chip: rgba(31, 36, 48, 0.06);
  --header-chip-hover: rgba(31, 36, 48, 0.12);
  --header-chip-line: rgba(31, 36, 48, 0.16);
}

/* The mark is a coloured tile on a pale ribbon, so it needs edge definition. */
html[data-theme="paper"] .brand-mark { box-shadow: 0 1px 3px rgba(31, 36, 48, 0.18); }

/* --- the picker in Settings --- */

.theme-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
  gap: 10px;
  margin-top: 4px;
}

.theme-tile {
  border: 2px solid var(--border);
  border-radius: var(--radius);
  background: var(--panel);
  padding: 0;
  cursor: pointer;
  overflow: hidden;
  text-align: left;
  font: inherit;
}

.theme-tile:hover { border-color: var(--accent); }
.theme-tile.on { border-color: var(--accent); }

.theme-swatch {
  height: 42px;
  display: block;
  position: relative;
}

/* A hint of the tab underline and a chip, so the swatch reads as the ribbon. */
.theme-swatch::after {
  content: '';
  position: absolute;
  left: 8px;
  bottom: 6px;
  width: 26px;
  height: 3px;
  border-radius: 2px;
  background: var(--series-made);
}

.theme-name {
  display: block;
  padding: 6px 8px 7px;
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--text);
}

.theme-tile.on .theme-name::after {
  content: ' \2713';
  color: var(--accent);
}


/* ---------------------------------------------------------------------------
   Phone view: sections in a bar fixed to the bottom, the garden promoted to a
   section of its own, and the ribbon shrunk to a title bar. Desktop view is
   everything else in this file - tabs at the top and the garden as a column.

   Which one you get is decided at runtime and stamped on <html> as
   data-mode="phone", from the width of the window unless the setting says
   otherwise. A wide window is a desktop whether or not Tend was opened from
   an icon, so an installed copy on a laptop still looks like the website.
   --------------------------------------------------------------------------- */

.bottom-nav { display: none; }

html[data-mode="phone"] .bottom-nav {
  display: flex;
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 150;
  background: var(--panel);
  border-top: 1px solid var(--border);
  padding: 6px 4px calc(6px + env(safe-area-inset-bottom, 0px));
  box-shadow: 0 -2px 14px rgba(20, 24, 40, 0.07);
  /* The raised disc and the New badge both lift above the bar's top edge. */
  overflow: visible;
  align-items: flex-end;
}

.bnav-btn {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  padding: 6px 2px 4px;
  border: none;
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: 0.66rem;
  font-weight: 600;
  cursor: pointer;
  border-radius: 10px;
  -webkit-tap-highlight-color: transparent;
}

.bnav-btn svg {
  width: 23px;
  height: 23px;
  display: block;
}

.bnav-btn.active { color: var(--accent); }

/* The garden is the reason most people open Tend, so it takes the middle seat
   and is drawn as a raised disc rather than a fifth flat icon. The button keeps
   its normal flex share - only the disc grows - so the five stay evenly
   spaced and nothing shifts when it is the active one. */
.bnav-hero { position: relative; }

.bnav-disc {
  display: grid;
  place-items: center;
  width: 46px;
  height: 46px;
  margin-top: -16px;
  border-radius: 50%;
  background: var(--accent);
  color: #fff;
  box-shadow: 0 4px 12px rgba(20, 24, 40, 0.22);
  border: 3px solid var(--panel);
}

.bnav-hero svg { width: 26px; height: 26px; }

/* The disc carries the accent already, so the label does not need to change
   colour when it is active - it just gets a little weight. */
.bnav-hero.active .bnav-label { color: var(--accent); }
.bnav-hero:active .bnav-disc { transform: translateY(1px); }

/* "New" until Friends has been opened once, then it is gone for good. */
.bnav-new {
  position: absolute;
  top: -3px;
  left: 50%;
  transform: translateX(-50%);
  padding: 1px 6px;
  border-radius: 999px;
  background: #2f7ce0;
  color: #fff;
  font-size: 0.56rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  line-height: 1.5;
  pointer-events: none;
}

.bnav-btn { position: relative; }

.bnav-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 100%;
}

/* The bar floats over the page, so the page has to end above it. */
html[data-mode="phone"] main { padding-bottom: 92px; }
/* Clear of the bar, at any width - the width-based rule below only fires
   under 900px, which left it sitting under the bar in a wide app window. */
html[data-mode="phone"] .new-task-btn-row {
  position: fixed;
  right: 16px;
  bottom: calc(88px + env(safe-area-inset-bottom, 0px));
  z-index: 40;
  margin: 0;
}


/* The sections all live in the bottom bar now, so the tab row goes entirely
   and the ribbon shrinks to a title bar. Search is moved up beside the
   account chip by applyAppMode(). */
html[data-mode="phone"] nav.tabs { display: none; }
/* The subtitle used to be a sentence about how the app works and was hidden
   here to save the room. Now that it is the live count, it is the most useful
   line on a phone screen - so it stays, one line, quietly. */
/* A fact is a sentence, so on a phone it wraps rather than being cut off with
   an ellipsis the way the old one-line counts were. Two lines is the cap. */
html[data-mode="phone"] .subtitle {
  margin: 4px 0 0;
  font-size: 0.74rem;
  gap: 8px;
}

html[data-mode="phone"] .fact-text {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

html[data-mode="phone"] .header-inner { padding-bottom: 14px; }
html[data-mode="phone"] h1 { font-size: 1.35rem; }
html[data-mode="phone"] .header-top-row { flex-wrap: wrap; margin-bottom: 12px; }

/* The magnifier goes to the far right, past the account chip, so it is under
   the thumb rather than stranded in the middle of the bar. */
html[data-mode="phone"] .header-top-row .nav-search { order: 5; margin: 0 0 0 4px; }

/* Opened, the box needs the width, so it drops to a line of its own. */
html[data-mode="phone"] .header-top-row .nav-search.open {
  order: 9;
  width: 100%;
  margin: 4px 0 0;
}

/* One column, whatever the window is doing. Without this a wide window in
   phone view keeps the three-column grid and leaves an empty track where the
   garden column used to be, pushing everything to the right. */
html[data-mode="phone"] .app-layout:not(.view-garden),
html[data-mode="phone"] .app-layout.no-garden:not(.view-garden),
html[data-mode="phone"] .app-layout.cal-wide:not(.view-garden) {
  grid-template-columns: minmax(0, 1fr);
  grid-template-areas: "content";
}

/* Garden as a section of its own: it takes the whole width, and the other two
   stand down. */
html[data-mode="phone"] .app-layout.view-garden,
html[data-mode="phone"] .app-layout.no-garden.view-garden {
  grid-template-columns: minmax(0, 1fr);
  grid-template-areas: "garden";
}

html[data-mode="phone"] .app-layout.view-garden > .content-col { display: none; }

html[data-mode="phone"] .app-layout:not(.view-garden) > .garden-col { display: none; }

/* On the garden section the plot should sit at the top, not stick. */
html[data-mode="phone"] .app-layout.view-garden > .garden-col {
  position: static;
  max-width: 560px;
  margin: 0 auto;
  width: 100%;
}

/* The plot is a fixed 272px of pixel art plus its fences, and a phone has
   little enough width as it is - so the panel around it gives up half its
   padding, which is the difference between the plot fitting and having to be
   scrolled sideways on a 360px screen. */
html[data-mode="phone"] .garden-col .panel {
  padding-left: 10px;
  padding-right: 10px;
}


/* Layout picker in Settings. */
.mode-list {
  display: grid;
  gap: 8px;
  margin-top: 4px;
}

.mode-tile {
  display: block;
  text-align: left;
  border: 2px solid var(--border);
  border-radius: var(--radius);
  background: var(--panel);
  padding: 9px 12px;
  cursor: pointer;
  font: inherit;
}

.mode-tile:hover { border-color: var(--accent); }
.mode-tile.on { border-color: var(--accent); background: var(--accent-soft); }

.mode-name {
  display: block;
  font-weight: 600;
  font-size: 0.86rem;
  color: var(--text);
}

.mode-tile.on .mode-name::after {
  content: ' \2713';
  color: var(--accent);
}

.mode-hint {
  display: block;
  font-size: 0.76rem;
  color: var(--muted);
  margin-top: 2px;
}

/* What's new. */
.update-block { margin-bottom: 16px; }
.update-block:last-child { margin-bottom: 0; }

.update-date {
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  padding-bottom: 5px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 7px;
}

.update-items {
  margin: 0;
  padding-left: 18px;
  font-size: 0.85rem;
  line-height: 1.55;
}

.update-items li { margin-bottom: 3px; }


/* ---------------------------------------------------------------------------
   Overview section: the stats, the category manager, and every task filed
   under the category it belongs to.
   --------------------------------------------------------------------------- */

/* Outside the task list - the overview, the calendar - the content takes the
   width the sidebar column used to hold.

   Every rule here excludes the garden section. The garden is laid out by its
   own rules further up, and because these came later in the file with equal
   specificity they were overriding its `grid-template-areas` while leaving its
   columns alone - which left the garden placed in a phantom third column and
   pushed to the right. */
.app-layout.no-sidebar:not(.view-garden) {
  grid-template-columns: 324px minmax(0, 1fr);
  grid-template-areas: "garden content";
}

.app-layout.no-sidebar.no-garden:not(.view-garden) {
  grid-template-columns: minmax(0, 1fr);
  grid-template-areas: "content";
}

@media (max-width: 1275px) {
  .app-layout.no-sidebar:not(.view-garden) {
    grid-template-columns: 324px minmax(0, 1fr);
    grid-template-areas: "garden content";
  }
}

@media (max-width: 900px) {
  .app-layout.no-sidebar:not(.view-garden),
  .app-layout.no-sidebar.no-garden:not(.view-garden) {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "content" "garden";
  }
}

html[data-mode="phone"] .app-layout.no-sidebar:not(.view-garden),
html[data-mode="phone"] .app-layout.no-sidebar.no-garden:not(.view-garden) {
  grid-template-columns: minmax(0, 1fr);
  grid-template-areas: "content";
}

.cat-toggle {
  display: inline-flex;
  border: 1px solid var(--border);
  border-radius: 999px;
  overflow: hidden;
  margin-bottom: 14px;
}

/* Four options do not fit a phone's width, and wrapping turned each one into
   two stacked words. It scrolls sideways instead, labels intact. */
html[data-mode="phone"] .cat-toggle {
  display: flex;
  max-width: 100%;
  overflow-x: auto;
  scrollbar-width: none;
}

html[data-mode="phone"] .cat-toggle::-webkit-scrollbar { display: none; }
html[data-mode="phone"] .cat-toggle button { white-space: nowrap; flex: none; }

.cat-toggle button {
  border: none;
  background: var(--panel);
  color: var(--muted);
  font: inherit;
  font-size: 0.76rem;
  font-weight: 600;
  padding: 5px 14px;
  cursor: pointer;
}

.cat-toggle button.on { background: var(--accent-soft); color: var(--accent); }

/* The button row above the task list: by status, or by category. */
.list-toolbar {
  display: flex;
  align-items: center;
  margin-bottom: 4px;
}
.list-toolbar .cat-toggle { margin-bottom: 10px; }
/* display: flex above beats the [hidden] attribute on its own. */
.list-toolbar[hidden] { display: none; }

/* A category heading on the task list, standing in for the usual h2. */
.cat-section-head {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 10px;
}

#category-sections { display: block; }

/* Packed columns, not a grid and no longer CSS multi-column.

   A grid lines every category up on a row, so one tall category leaves a hole
   beside it the height of the difference. CSS columns were the next answer and
   left a worse one: a break can only fall between two categories, so the column
   is at least as tall as the tallest single category, and anything that will not
   fit in what is left of a column jumps to the next one - a one-task category
   ranked first sat alone at the top of a 640px column.

   So these are real columns, each a flex child of the row, and
   `packCategoryColumns()` puts each category into whichever is shortest at the
   time. flex: 1 1 0 with min-width: 0 means every column is exactly its share
   of the row whatever is in it, which is what makes measuring a block in one
   column tell you its height in any of them. The gaps here have twins in that
   function - CAT_COL_GAP and CAT_BLOCK_GAP - so change them together.

   The full-width blocks - the date buckets, the empty state - are rendered in
   .cat-wide above this rather than inside it. */
.cat-columns {
  display: flex;
  align-items: flex-start;
  gap: 24px;
}

.cat-col {
  flex: 1 1 0;
  min-width: 0;
}

.cat-col > .list-section { margin: 0 0 20px; }
.cat-col > .list-section:last-child { margin-bottom: 0; }

.cat-wide > .list-section { margin-bottom: 20px; }

/* Kept for the full-width blocks, which are still laid out as blocks. */
.cat-section-wide { width: 100%; }

/* Each category on the overview reads as one block: a tinted spine down its
   left in the category's own colour, so the eye can follow a group without a
   heading rule fighting the rows. */
.cat-group {
  margin-bottom: 14px;
  border-left: 3px solid var(--cat-color, var(--border));
  padding-left: 12px;
}
.cat-group:last-child { margin-bottom: 0; }

.cat-group-head {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 4px;
}

.cat-tasks {
  list-style: none;
  margin: 0;
  padding: 0;
}

.cat-task {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 5px 8px;
  border-radius: 6px;
  cursor: pointer;
  font-size: 0.86rem;
  line-height: 1.4;
}

.cat-task + .cat-task { border-top: 1px solid var(--border); }
.cat-task:hover { background: var(--bg); }

.cat-task-star {
  flex: none;
  color: var(--warn);
  font-size: 0.76rem;
}

.cat-task-title {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.cat-task.done .cat-task-title {
  text-decoration: line-through;
  color: var(--muted);
}

.cat-task-due {
  flex: none;
  font-size: 0.74rem;
  color: var(--muted);
}

.cat-task-due.overdue { color: var(--danger); font-weight: 600; }

.cat-empty {
  margin: 0;
  font-size: 0.82rem;
  color: var(--muted);
}


/* Days borrowed from the month either side, so every row is a whole week.
   Present and clickable, but clearly not this month. */
.cal-cell.outside {
  background: var(--bg);
  background: color-mix(in srgb, var(--bg) 65%, transparent);
  border-style: dashed;
}

.cal-cell.outside .cal-daynum { opacity: 0.45; }
.cal-cell.outside .cal-events { opacity: 0.6; }




/* Transient effects sit above the scene and are never rebuilt with it. */
.garden-effects {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 30;
}


/* ---------------------------------------------------------------------------
   Ground dressing. Nothing here reacts to anything - it is scenery, sitting
   under the plants and above the checker.
   --------------------------------------------------------------------------- */

.ground-patch {
  position: absolute;
  opacity: 0.45;
  border-radius: 42% 58% 46% 54%;
  z-index: 0;
  pointer-events: none;
}

.ground-tuft {
  position: absolute;
  width: 7px;
  height: 8px;
  z-index: 1;
  pointer-events: none;
}

.ground-tuft i {
  position: absolute;
  bottom: 0;
  width: 2px;
  border-radius: 1px 1px 0 0;
}

.ground-pebble {
  position: absolute;
  width: 3px;
  height: 2px;
  border-radius: 50%;
  opacity: 0.85;
  z-index: 1;
  pointer-events: none;
}

.ground-edging {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 5px;
  z-index: 2;
  pointer-events: none;
}

.ground-edging i {
  position: absolute;
  bottom: 0;
  border-radius: 2px 2px 0 0;
}


/* ---------------------------------------------------------------------------
   Plants: a slight sway, a half-grown stage, and enough variation between two
   of the same variety that a bed reads as a clump rather than a repeated
   stamp.
   --------------------------------------------------------------------------- */

.plant-art {
  display: block;
  position: relative;
  z-index: 1;
  transform-origin: 50% 100%;
}

/* Only what is in the ground moves - a plant in a pot has nothing to sway in. */
.garden-cell.sways .plant-art {
  animation: plant-sway 4.6s ease-in-out infinite;
  animation-delay: var(--sway-delay, 0s);
}

/* The plant's own nudge and size come through as custom properties so the
   animation can keep them - a keyframe that set only a rotation would throw
   both away and make every plant jump on the first frame. */
@keyframes plant-sway {
  0%, 100% { transform: translateX(var(--nudge, 0px)) scale(var(--scale, 1)) rotate(-1.1deg); }
  50%      { transform: translateX(var(--nudge, 0px)) scale(var(--scale, 1)) rotate(1.1deg); }
}

/* Halfway to grown: the real plant, smaller and not yet in full colour. */
.half-grown {
  display: block;
  transform: scale(0.72);
  transform-origin: 50% 92%;
  filter: saturate(0.7) brightness(1.05);
}

@media (prefers-reduced-motion: reduce) {
  .garden-cell.sways .plant-art { animation: none; }
}

/* ---------------------------------------------------------------------------
   Ambient life drifting across the plot.
   --------------------------------------------------------------------------- */

/* Position and wing-flap both come from JavaScript, one frame at a time. A CSS
   animation restarts whenever its element is moved in the DOM, and the effects
   layer is re-appended on every render - which is why picking a plant up used
   to fling every butterfly back to the edge of the plot. */
.garden-ambient {
  position: absolute;
  left: 0;
  top: 0;
  opacity: 0.9;
  z-index: 25;
  pointer-events: none;
  will-change: transform;
}

@media (prefers-reduced-motion: reduce) {
  .garden-ambient { display: none; }
}

/* ---------------------------------------------------------------------------
   The mouse pointer.

   Chrome draws the text I-beam white wherever the pixels under it are dark, so
   over the app's dark header ribbon - and over a stretch of selected text - the
   pointer all but vanished. None of the furniture here is text anybody needs to
   select: the header, the tabs, headings, pills, counts and empty notes now get
   the ordinary arrow instead. Anything worth copying out, and every field you
   type into, keeps its I-beam.

   This block sits last on purpose. `cursor` inherits, so a container set to
   `default` covers everything inside it that does not say otherwise, and the
   re-assertions below have to come after it.

   The arrow is the default for the whole page, not just the list below: any
   dark panel or theme the list missed would bring the white I-beam back. Only
   the fields you type into keep it.
   --------------------------------------------------------------------------- */

html { cursor: default; }

header,
.auth-screen,
nav.tabs,
h1, h2, h3,
.subtitle,
.section-header,
.shop-section-title,
.shop-info,
.list-toolbar,
.cat-toggle,
.cal-legend-row,
.cal-toggle-group,
.cat-group-head,
.cat-section-head,
.completed-day-heading,
.category-hint,
.empty-note,
.key-item,
.tag {
  cursor: default;
  -webkit-user-select: none;
  user-select: none;
}

/* Selected text keeps a light highlight, so the I-beam over it stays dark
   rather than inverting to white against the browser's own dark blue. */
::selection {
  background: var(--accent-soft);
  color: var(--text);
}

/* Fields, and the places worth copying from, keep the I-beam and stay
   selectable. */
input, textarea, select,
.task-notes,
.detail-value,
.modal-body {
  -webkit-user-select: text;
  user-select: text;
}

input[type="text"], input[type="search"], input[type="email"],
input[type="password"], input[type="number"], input[type="date"],
textarea {
  cursor: text;
}

/* Anything that reacts to a click says so, and anything that cannot be clicked
   says that too. */
button,
select,
label,
input[type="checkbox"],
input[type="color"],
[onclick] {
  cursor: pointer;
}

button:disabled,
select:disabled,
input:disabled,
.shop-tile-action:disabled,
.cat-toggle button:disabled {
  cursor: not-allowed;
}

/* Rows only offer the hand where dragging actually reorders something: the
   Priority / Active lists. The grouped views take their order from the
   categories, so their rows are marked undraggable and stay a plain pointer. */
li.task[draggable="false"] { cursor: pointer; }

/* The drag handles keep their own. */
.key-grip,
.key-grip:active,
.cat-sortable-item.dragging { cursor: move; }

/* ---------------------------------------------------------------------------
   Friends: everybody's garden, read-only.
   --------------------------------------------------------------------------- */

.friend-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.friend-row {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 10px 12px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 8px;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.friend-row:hover { border-color: var(--accent); }
.friend-row.on { border-color: var(--accent); background: var(--accent-soft); }

.friend-avatar {
  flex: none;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--accent);
  color: #fff;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.02em;
}

.friend-name {
  flex: 1;
  min-width: 0;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.friend-you {
  font-weight: 600;
  font-size: 0.66rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 1px 6px;
  margin-left: 6px;
}

/* Garden or Reef, on the row. */
.friend-world {
  flex: none;
  font-size: 0.62rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: 2px 8px;
  border-radius: 999px;
  border: 1px solid transparent;
}

.friend-world.garden { background: rgba(63, 145, 66, 0.14); border-color: rgba(63, 145, 66, 0.4); color: #2c6b30; }
.friend-world.ocean { background: rgba(58, 140, 178, 0.16); border-color: rgba(58, 140, 178, 0.45); color: #1f5d7a; }

@media (max-width: 560px) {
  .friend-world { display: none; }
}

.friend-counts {
  flex: none;
  font-size: 0.8rem;
  color: var(--muted);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.friend-counts b { color: var(--text); }

@media (max-width: 560px) {
  .friend-kinds { display: none; }
}

/* A garden opens where the name is, folded in under the row that was clicked,
   so nobody has to go looking for it at the foot of the page. */
.friend-item {
  display: flex;
  flex-direction: column;
}

.friend-item.open .friend-row {
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
}

.friend-garden-inline {
  border: 1px solid var(--accent);
  border-top: 0;
  border-radius: 0 0 8px 8px;
  background: var(--panel);
  padding: 10px 12px 12px;
}

.friend-garden-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.friend-garden-head h2 { margin: 0; }

.friend-garden-sub {
  margin: 4px 0 12px;
  font-size: 0.8rem;
  color: var(--muted);
}

/* The plot can be taller than the panel, so it scrolls inside its own box
   rather than stretching the page. */
.friend-garden-plot {
  overflow: auto;
  max-height: 60vh;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 8px;
  background: var(--bg);
}

.garden-preview {
  position: relative;
  border-radius: 6px;
  overflow: hidden;
}

.preview-band { position: absolute; left: 0; right: 0; }

/* The preview is a real plot in every respect but one: nothing in it can be
   clicked, walked on or dragged, so it takes the page's normal touch rules
   back rather than the plot's own. */
.garden-preview {
  touch-action: auto;
  pointer-events: none;
  cursor: default;
}

/* ============================ FOCUS ============================
   Almost every control in Tend is a custom-styled button, several of them
   setting their own background and border, so the browser's own focus ring
   was either invisible against them or suppressed outright: tabbing the
   header, the tab row, the shop and the help icons showed nothing at all.
   One ring, in whichever accent the current theme sets, drawn only for
   keyboard focus. Declared last in the file so it wins the ties.        */

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 4px;
}

/* A mouse click should not leave the ring sitting there afterwards. */
:focus:not(:focus-visible) { outline: none; }

/* The accent has nothing to sit against on the dark header ribbon or the
   bottom bar, so controls there take the ribbon's own text colour. */
header :focus-visible,
.bottom-nav :focus-visible {
  outline-color: var(--header-text);
}

/* The search box already answers focus by growing, so the ring goes round
   the whole control rather than the bare input inside it. */
.nav-search:focus-within {
  outline: 2px solid var(--header-text);
  outline-offset: 3px;
  border-radius: 6px;
}
.search-input:focus-visible { outline: none; }

/* Fields that draw their own focus state keep it and skip the ring. */
.auth-form input:focus-visible { outline: none; }

/* The garden plot is the one thing here that is neither button nor field:
   a focusable grid where every key press depends on knowing it has the
   focus. It already shows a ring on click - which is how you focus it -
   so keyboard focus just draws that ring heavier. */
.garden-plot:focus-visible {
  box-shadow: inset 0 0 0 3px var(--accent);
  outline: none;
}

/* ===================== GARDEN HELP GROUPS =====================
   These eleven buttons sat in one flat row, labelled only by a title
   attribute - which a phone never shows, so the only way to learn what one
   did was to tap all eleven. Three named groups now, and under the phone
   breakpoint each button shows its name instead of a bare icon.        */

.garden-help-icons {
  gap: 14px;
  align-items: flex-start;
}

.help-group {
  display: flex;
  flex-direction: column;
  gap: 5px;
}

.help-group-label {
  font-size: 0.64rem;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--muted);
}

.help-group-btns {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.help-icon-glyph {
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
}

.help-icon-word { display: none; }

@media (max-width: 560px) {
  .garden-help-icons { gap: 12px; }
  .help-group-btns {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 5px;
  }
  .help-icon-btn {
    width: auto;
    height: auto;
    min-height: 32px;
    justify-content: flex-start;
    gap: 8px;
    padding: 6px 9px;
  }
  .help-icon-word {
    display: inline;
    font-size: 0.76rem;
    font-weight: 500;
    color: var(--text);
  }
}

/* ===================== REVEAL TOGGLES =====================
   Show recurring / archived / completed. These used to sit in the Completed
   section's header, which filed the recurring control under a heading
   saying "Completed" and opened sections that render above the button just
   pressed. Their own line, ahead of everything they reveal.           */

.reveal-toggles {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 28px 0 6px;
}

/* ===================== UNDO BUTTON =====================
   The label names what it will undo, so it can run long. Trimmed with an
   ellipsis; the full text is on aria-label. */

#undo-btn { max-width: 16rem; }

#undo-btn-label {
  display: inline-block;
  max-width: 11.5rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: bottom;
}

@media (max-width: 720px) {
  #undo-btn { max-width: 9.5rem; }
  #undo-btn-label { max-width: 5.5rem; }
}
/* ================= the garden as a screen, not a page =================

   On a phone the garden is a section of its own, and a section you have to
   scroll is a picture of a garden rather than a place you are standing in.
   So in that one case the page stops being a page: the header holds the top,
   the bottom bar holds the bottom, and the plot takes everything between
   them. Nothing scrolls. The garden is usually taller than what is left, so
   the plot slides under its window to follow the gardener instead - see
   followHero() in js/garden.js.

   The two blocks that used to sit below the plot, the shop and the help, have
   nowhere to be in a screen that does not scroll. They are sheets now, opened
   from the row of buttons under the plot.                                  */

html.garden-full,
html.garden-full body { height: 100%; overflow: hidden; }

html.garden-full #app-shell {
  height: 100%;
  display: flex;
  flex-direction: column;
}

html.garden-full header { flex: none; }

html.garden-full main {
  flex: 1;
  /* Without this a flex item refuses to be shorter than its contents, and the
     garden pushes the bottom of the screen off the bottom of the screen. */
  min-height: 0;
  display: flex;
  /* No side padding: the garden goes edge to edge. The lines of text above
     and the buttons below put their own back, so only the plot is full
     width. */
  padding: 4px 0 var(--bnav-h, calc(68px + env(safe-area-inset-bottom, 0px)));
}

html.garden-full .app-layout,
html.garden-full .garden-col,
html.garden-full .garden-col .panel,
html.garden-full .garden-stage {
  min-height: 0;
  display: flex;
  flex-direction: column;
  flex: 1;
}

/* The panel was a card on a page. On its own screen it is the screen, so all
   of the card goes: the border it was drawn with, the rounding, the shadow
   and the padding that held the plot away from the edges. */
html.garden-full .garden-col .panel {
  margin: 0;
  padding: 0;
  border: none;
  border-radius: 0;
  box-shadow: none;
  background: none;
}

/* Everything that is not the plot puts its own gutter back. */
html.garden-full .garden-gardener-row,
html.garden-full .garden-controls { padding-left: 12px; padding-right: 12px; }

/* Same for the frame around the plot - the fence posts are the garden's own
   edge and are all the edge it needs. */
html.garden-full .garden-plot-wrap {
  border: none;
  border-radius: 0;
  padding: 0;
}

/* The window on the garden: whatever height is left after the status line and
   the buttons have taken theirs. fitPlot() caps it at the garden's own height
   so a single unlocked section does not leave the locked band showing. */
html.garden-full .garden-plot-wrap { flex: 1; min-height: 0; }

/* The ground slides rather than jumping, which is the difference between a
   camera and a cut. Short enough to keep up with a held-down walk. */
html.garden-full .garden-plot { transition: transform 0.12s linear; }


/* The h2 said "My Garden" above an h1 that already says it. */
html.garden-full #garden-panel-title { display: none; }

/* Sticky was for a page that scrolled. This one does not. */
/* Four buttons and the Holding chip do not fit across a 360px screen, so
   the chip takes a line of its own and the buttons get the full width -
   paid for by the heading, the hints and the help strip, which the sheets
   took away. */
html.garden-full .garden-controls {
  flex-direction: column;
  align-items: stretch;
  position: static;
  bottom: auto;
  flex: none;
  box-shadow: none;
  background: none;
  padding: 0;
  margin-top: 8px;
}

/* Four across on a 360px screen: the glyph and the word stack, and Help keeps
   the glyph alone. */
html[data-mode="phone"] .garden-ctl-btn {
  padding: 10px 2px;
  gap: 4px;
  min-height: 44px;
}

html[data-mode="phone"] .garden-ctl-btn span { font-size: 0.68rem; }

html.garden-full .garden-holding { justify-content: center; }

html[data-mode="phone"] .garden-ctl-btn.garden-ctl-narrow {
  flex: 0 0 44px;
  min-width: 44px;
}

/* The coin count already has the auto margin that pushes this end of the row
   right; a second one here would split the gap and strand the coins in the
   middle instead. */
html[data-mode="phone"] .garden-top-btns {
  display: flex;
  gap: 8px;
  flex: none;
}

/* The status line gives up whatever the buttons need rather than pushing
   them off. */
html[data-mode="phone"] .garden-gardener-row .garden-status { min-width: 0; }

/* Up here they are reached deliberately, not in the middle of a walk, so they
   are the smaller pair - the thumb row keeps the full-height buttons. */
html[data-mode="phone"] .garden-top-btns .garden-ctl-btn {
  flex: none;
  width: auto;
  min-height: 38px;
  padding: 7px 11px;
  font-size: 1.05rem;
  border-radius: 11px;
}

html[data-mode="phone"] .garden-top-btns .garden-ctl-btn.garden-ctl-narrow {
  flex: none;
  width: 38px;
  min-width: 38px;
  padding: 7px 0;
}

/* ===================== the thumb row =====================

   Four flat grey rectangles under a garden read as a form, not a game. On a
   phone they get a painted face, a darker lip under it and a press that moves
   the whole button down onto the lip - the arcade button in two box-shadows.

   The colours are the same in light and dark on purpose, like the garden's own
   daylight: these are toys sitting on the page, not part of its chrome. Each
   button gets its own, so the thumb learns the position by colour rather than
   by reading four words at a glance. */
html[data-mode="phone"] .garden-ctl-btn {
  --gbtn: #2fa36b;
  --gbtn-lit: #46bd82;
  --gbtn-lip: #1d7a4e;
  background: linear-gradient(180deg, var(--gbtn-lit) 0%, var(--gbtn) 62%);
  border: none;
  border-radius: 13px;
  min-height: 48px;
  font-size: 1.3rem;
  color: #fff;
  text-shadow: 0 1px 0 rgba(0, 0, 0, 0.22);
  /* The lip, then the shadow it casts on the page. */
  box-shadow: 0 4px 0 var(--gbtn-lip), 0 5px 10px rgba(20, 24, 40, 0.22),
              inset 0 1px 0 rgba(255, 255, 255, 0.35);
  transition: transform 0.06s ease-out, box-shadow 0.06s ease-out;
}

html[data-mode="phone"] #garden-use-btn { --gbtn: #4a7fa8; --gbtn-lit: #6b9cc2; --gbtn-lip: #2f5877; }
html[data-mode="phone"] #garden-putdown-btn { --gbtn: #7f56f0; --gbtn-lit: #9b7bf7; --gbtn-lip: #5a34c0; }
html[data-mode="phone"] #garden-shop-btn { --gbtn: #e0ab24; --gbtn-lit: #f0c649; --gbtn-lip: #a8770c; }
html[data-mode="phone"] #garden-help-btn { --gbtn: #6b7280; --gbtn-lit: #858c99; --gbtn-lip: #474d57; }

html[data-mode="phone"] .garden-ctl-btn span {
  color: #fff;
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.02em;
}

/* Down onto the lip: move by exactly what the lip is worth and take it off the
   shadow, or the button appears to sink into the page instead of being held. */
html[data-mode="phone"] .garden-ctl-btn:active:not(:disabled) {
  transform: translateY(3px);
  background: linear-gradient(180deg, var(--gbtn) 0%, var(--gbtn-lit) 100%);
  box-shadow: 0 1px 0 var(--gbtn-lip), 0 1px 3px rgba(20, 24, 40, 0.24),
              inset 0 1px 3px rgba(0, 0, 0, 0.22);
}

/* Nothing to press means nothing sticking up to press. */
html[data-mode="phone"] .garden-ctl-btn:disabled {
  opacity: 0.45;
  transform: translateY(3px);
  box-shadow: 0 1px 0 var(--gbtn-lip);
}

/* A sheet is a modal that has a whole panel inside it, so it gives up the
   padding the panel brings with it and takes the width it can get. */
.garden-sheet { width: 420px; max-height: 80vh; }
.garden-sheet > div > .panel,
.garden-sheet #shop-panel { margin: 0; border: none; padding: 0; background: none; box-shadow: none; }
.garden-sheet .garden-hint { display: block; margin-top: 0; }


/* The iPhone's borrowed haptic - see index.html. Out of sight and out of the
   way of every pointer, but NOT display:none or visibility:hidden: a control
   the browser considers unrendered does not tick. */
#ios-haptic {
  position: fixed;
  top: 0;
  left: 0;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
  z-index: -1;
  overflow: hidden;
}

/* ===================== the loading garden =====================

   Shown in the auth card while the sign-in library loads and the session is
   checked. Pure CSS on static markup: nothing starts it and nothing stops it,
   because .auth-screen[hidden] is display:none and a hidden element does not
   animate. One 3s loop - the can tips in, three drops fall, the plant goes up
   through its three stages, the can tips back.                              */

.tend-loading {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 10px 0 2px;
}

.tend-loading svg {
  width: 168px;
  height: 116px;
  image-rendering: pixelated;
  overflow: visible;
}

/* The gardener is a sprite nested inside the picture; keep it at its own size. */
.tend-loading svg svg { width: 19px; height: 20px; overflow: visible; }

.tend-loading-word {
  margin: 0;
  font-size: 0.82rem;
  color: rgba(255, 255, 255, 0.72);
}

/* Each stage holds, then cuts to the next. Cuts, not fades: the garden grows
   in stages everywhere else in the app and this matches it. */
.tend-stage { opacity: 0; animation: tend-stage-hold 3s infinite; }
.tend-s1 { animation-name: tend-s1; }
.tend-s2 { animation-name: tend-s2; }
.tend-s3 { animation-name: tend-s3; }

@keyframes tend-stage-hold { 0%, 100% { opacity: 1; } }
@keyframes tend-s1 { 0%, 37.9% { opacity: 1; } 38%, 100% { opacity: 0; } }
@keyframes tend-s2 { 0%, 37.9% { opacity: 0; } 38%, 57.9% { opacity: 1; } 58%, 100% { opacity: 0; } }
@keyframes tend-s3 { 0%, 57.9% { opacity: 0; } 58%, 100% { opacity: 1; } }

/* Held in the gardener's hand and tipped at the spout end, so it pours rather than swings. */
.tend-can {
  transform-box: fill-box;
  transform-origin: 90% 80%;
  animation: tend-can 3s infinite;
}

@keyframes tend-can {
  0%, 20% { transform: rotate(0deg); }
  30%, 74% { transform: rotate(26deg); }
  86%, 100% { transform: rotate(0deg); }
}

/* The drops fall on their own short cycle; this only decides when there is
   any water at all, so none of it appears before the can has tipped. */
.tend-drops { opacity: 0; animation: tend-pour 3s infinite; }

@keyframes tend-pour {
  0%, 31.9% { opacity: 0; }
  32%, 73.9% { opacity: 1; }
  74%, 100% { opacity: 0; }
}

.tend-drop {
  transform-box: fill-box;
  animation: tend-drop 0.62s linear infinite;
}

.tend-drop:nth-child(2) { animation-delay: 0.2s; }
.tend-drop:nth-child(3) { animation-delay: 0.4s; }

@keyframes tend-drop {
  0%   { opacity: 0; transform: translate(0, 0); }
  15%  { opacity: 1; }
  80%  { opacity: 1; }
  100% { opacity: 0; transform: translate(2px, 7px); }
}

/* Asked for stillness, it is a picture of a grown plant and nothing moves. */
@media (prefers-reduced-motion: reduce) {
  .tend-can, .tend-drops, .tend-drop, .tend-stage { animation: none; }
  .tend-s1, .tend-s2, .tend-drops { opacity: 0; }
  .tend-s3 { opacity: 1; }
}

/* Coins on a friend's row, beside how much they have grown. */
.friend-coins {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  margin-left: 8px;
  white-space: nowrap;
}

.friend-coins svg { width: 12px; height: 12px; }

/* An account with nothing planted: shown only behind Others, and dimmed, so
   the list still reads as "gardens first". */
.friend-bare .friend-row { opacity: 0.66; }

.friends-others-btn { margin-top: 12px; }

/* The phone's garden section is its own screen with its own layout - the
   desktop pane's height and scrolling have no business there. */
html[data-mode="phone"] .garden-col { max-height: none; overflow-y: visible; }

/* ===================== more garden on the Garden screen =====================

   The garden screen spent more than half its height on things that were not
   the garden: the page title and a fun fact, a three-line status, the Holding
   chip on a line of its own and a row of tall buttons under it. The title
   and fact go (the bottom bar already says Garden), the status keeps to two
   lines, and the chip and the three buttons share one short row. */
html.garden-full #page-title,
html.garden-full #page-subtitle { display: none; }
#page-subtitle.off-tasks { display: none; }   /* the fun fact is the Tasks page's alone */

html.garden-full .garden-gardener-row .garden-status {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-size: 0.72rem;
}

html.garden-full .garden-controls {
  flex-direction: row;
  align-items: stretch;
  gap: 6px;
  margin-top: 6px;
}

/* What is in your hands: a small tile at the start of the row, the thing
   itself with its name under it. */
html.garden-full .garden-holding {
  flex: 0 0 64px;
  flex-direction: column;
  justify-content: center;
  gap: 0;
  padding: 3px 4px;
  min-width: 0;
  border-radius: 12px;
}
html.garden-full .garden-holding-label { display: none; }
html.garden-full .garden-holding-name {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  font-size: 0.6rem;
}
html.garden-full .garden-holding-art svg { max-height: 26px; width: auto; }

html.garden-full .garden-ctl-btns { gap: 6px; }
html.garden-full .garden-controls .garden-ctl-btn {
  min-height: 44px;
  padding: 6px 2px;
  font-size: 1.1rem;
  gap: 2px;
  flex-direction: column;
}
html.garden-full .garden-controls .garden-ctl-btn span { font-size: 0.62rem; }

/* The look strips down the sides of the plot are drag areas, not the page. */
html.garden-full .garden-plot-wrap { touch-action: none; }

/* ===================== the shop, on a phone =====================

   On a computer the shop is two columns of small rows, which suits a panel
   beside the garden. In a phone sheet the same rows read like a web form.
   There it gets a tab per kind of thing and a grid of big tiles, each with
   its price on a button you can hit with a thumb. */
.shop-tabs { display: none; }
.shop-column .shop-sec:first-child .shop-section-title:first-child { margin-top: 0; }

html[data-mode="phone"] .shop-tabs {
  display: flex;
  gap: 6px;
  margin: 4px 0 12px;
}
html[data-mode="phone"] .shop-tab {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 8px 2px 7px;
  font: inherit;
  font-size: 0.72rem;
  font-weight: 700;
  color: var(--muted);
  background: var(--bg);
  border: 2px solid transparent;
  border-radius: 12px;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
html[data-mode="phone"] .shop-tab span { font-size: 1.35rem; line-height: 1.1; }
html[data-mode="phone"] .shop-tab.on {
  color: var(--text);
  background: var(--panel);
  border-color: #e0ab24;
  box-shadow: 0 3px 0 #a8770c;
}
html[data-mode="phone"] .shop-tab:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }

html[data-mode="phone"] .shop-columns { display: block; }
html[data-mode="phone"] .shop-sec:not(.on) { display: none; }
/* The tab already names the section; Food keeps its heading inside Pets. */
html[data-mode="phone"] .shop-sec > .shop-section-title:first-child { display: none; }
html[data-mode="phone"] .shop-subtitle { font-size: 0.72rem; margin: 16px 0 8px; }
html[data-mode="phone"] .shop-info { font-size: 0.8rem; margin-bottom: 8px; }
html[data-mode="phone"] .shop-desc { font-size: 0.8rem; margin: 6px 0 8px; }
html[data-mode="phone"] .shop-sell-btn { font-size: 0.95rem; min-height: 40px; box-shadow: 0 3px 0 #1d7a4e; }

html[data-mode="phone"] .shop-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}
html[data-mode="phone"] .shop-tile {
  flex-direction: column;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
  padding: 12px 8px 8px;
  border-radius: 14px;
  border-width: 2px;
  background: var(--panel);
  text-align: center;
  min-height: 132px;
}
html[data-mode="phone"] .shop-tile-icon { font-size: 2rem; }
html[data-mode="phone"] .shop-tile-icon svg { width: 40px; height: 40px; }
html[data-mode="phone"] .shop-tile-info { flex-direction: column; justify-content: space-between; align-self: stretch; gap: 6px; }
html[data-mode="phone"] .shop-tile-label { font-size: 0.8rem; font-weight: 600; flex: 1; display: flex; align-items: center; }
/* The price is the button. */
html[data-mode="phone"] .shop-tile-action {
  align-self: stretch;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 3px;
  min-height: 38px;
  font-size: 0.95rem;
  border-radius: 10px;
  color: #fff;
  background: linear-gradient(180deg, #f0c649, #e0ab24 62%);
  box-shadow: 0 3px 0 #a8770c;
  text-shadow: 0 1px 0 rgba(0, 0, 0, 0.2);
}
html[data-mode="phone"] .shop-tile-action:active:not(:disabled) { transform: translateY(2px); box-shadow: 0 1px 0 #a8770c; }
html[data-mode="phone"] .shop-tile.equipped .shop-tile-action { background: var(--green); box-shadow: none; color: #fff; }
html[data-mode="phone"] .shop-tile.owned .shop-tile-action { background: var(--bg); color: var(--text); box-shadow: inset 0 0 0 2px var(--border); text-shadow: none; }
html[data-mode="phone"] .shop-tile-action:disabled { background: var(--border); color: var(--muted); box-shadow: none; text-shadow: none; }
html[data-mode="phone"] .shop-coins { font-size: 1.1rem; font-weight: 700; }

/* The gardener on the Overview, reading the numbers out. */
.overview-guide { display: flex; align-items: flex-end; gap: 12px; margin: 0 0 16px; }
.overview-guide .gn-sprite { flex: none; width: 48px; display: flex; justify-content: center; }
.overview-guide .gn-sprite svg { display: block; image-rendering: pixelated; transform: scale(1.5); transform-origin: bottom center; }
.overview-bubble {
  position: relative;
  margin: 0;
  padding: 9px 12px;
  border-radius: 10px;
  background: var(--accent-soft);
  font-size: 0.88rem;
  line-height: 1.4;
}
.overview-bubble::before {
  content: "";
  position: absolute;
  left: -6px;
  bottom: 10px;
  border: 6px solid transparent;
  border-left: 0;
  border-right-color: var(--accent-soft);
}

/* Only the Tasks page and Plan My Day offer + New Task. */
html[data-view]:not([data-view="list"]):not([data-view="plan"]) .new-task-btn-row { display: none; }

/* The garden fills a tablet's width on the Garden screen: the phone column
   cap left a 560px strip in the middle of an iPad. */
html.garden-full .app-layout.view-garden > .garden-col { max-width: none; }

/* The help gardener: sprite in the corner, a thought bubble with a question
   mark over their head. The whole thing is the button. */
.panel-head-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.panel-head-row h2 { margin-bottom: 0; }
.help-gardener-slot { flex: none; margin-left: auto; }
.help-gardener {
  position: relative;
  display: block;
  width: 46px;
  height: 58px;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.help-gardener:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; border-radius: 8px; }
.help-gardener .hg-sprite { position: absolute; left: 8px; bottom: 0; }
.help-gardener .hg-sprite svg { display: block; image-rendering: pixelated; }
.help-gardener .hg-thought {
  position: absolute;
  right: 0;
  top: 0;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--panel);
  border: 2px solid var(--accent);
  color: var(--accent);
  font-weight: 800;
  font-size: 0.8rem;
  line-height: 18px;
  text-align: center;
  animation: hg-bob 2.4s ease-in-out infinite;
}
.help-gardener .hg-thought::after {
  content: "";
  position: absolute;
  left: -3px;
  bottom: -5px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--panel);
  border: 2px solid var(--accent);
}
.help-gardener:active .hg-thought { background: var(--accent-soft); }
@keyframes hg-bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-2px); } }
@media (prefers-reduced-motion: reduce) { .help-gardener .hg-thought { animation: none; } }

.explain { display: flex; gap: 14px; align-items: flex-start; }
.explain .gn-sprite { flex: none; padding-top: 4px; }
.explain .gn-sprite svg { display: block; image-rendering: pixelated; }
.explain p { margin: 0 0 10px; line-height: 1.5; }
/* On a phone the plan's toggle takes a line of its own; the gardener stays
   up beside the title. */
html[data-mode="phone"] .plan-head .plan-head-text { order: 1; flex: 1; min-width: 0; }
html[data-mode="phone"] .plan-head .help-gardener-slot { order: 2; }
html[data-mode="phone"] .plan-head .cal-view-toggle { order: 3; }

/* ===================== the loading scene =====================

   Plays once, in this order, then only the dance repeats:
     0.0-1.2s  walks in from the left, side-on, bobbing
     1.2-1.8s  stands and looks at the seedling
     1.8-3.3s  tips the can (it pivots at their hand, so it never leaves it)
     2.7 / 3.2 the seedling grows, then comes up as the flower
     3.5-4.9s  hearts float up
     4.4s on   turns to face you and dances until the app has loaded
   Everything but the dance holds its last frame (forwards), so nothing
   restarts. The loading screen is replaced the moment sign-in renders. */
.lg-stage, .lg-drops, .lg-heart, .lg-front { opacity: 0; }
.lg-s1 { opacity: 1; animation: lg-hide 0.01s 2.7s forwards; }
.lg-s2 { animation: lg-show 0.01s 2.7s forwards, lg-hide 0.01s 3.2s forwards; }
.lg-s3 { animation: lg-show 0.01s 3.2s forwards; }
.lg-pop { transform-box: fill-box; transform-origin: 50% 100%; animation: lg-pop 0.35s 3.2s ease-out backwards; }

.lg-walker { animation: lg-walk 1.2s steps(12) backwards; }
.lg-bob { animation: lg-bob 0.3s steps(2) 4; }
.lg-side { animation: lg-hide 0.01s 4.4s forwards; }
.lg-front {
  transform-box: fill-box;
  transform-origin: 50% 100%;
  animation: lg-show 0.01s 4.4s forwards, lg-dance 0.7s 4.4s ease-in-out infinite alternate;
}

.lg-can {
  transform-box: fill-box;
  transform-origin: 5% 50%;
  animation: lg-tip 1.5s 1.8s ease-in-out;
}
.lg-drops { animation: lg-show 0.01s 2.1s forwards, lg-hide 0.01s 3s forwards; }
.lg-drop { animation: lg-fall 0.5s linear infinite; }
.lg-drop:nth-child(2) { animation-delay: 0.17s; }
.lg-drop:nth-child(3) { animation-delay: 0.34s; }

.lg-heart { animation: lg-heart 1s ease-out forwards; }
.lg-h1 { animation-delay: 3.5s; }
.lg-h2 { animation-delay: 3.7s; }
.lg-h3 { animation-delay: 3.9s; }

@keyframes lg-show { to { opacity: 1; } }
@keyframes lg-hide { to { opacity: 0; } }
@keyframes lg-walk { from { transform: translateX(-22px); } to { transform: translateX(0); } }
@keyframes lg-bob { 50% { transform: translateY(-1px); } }
@keyframes lg-tip { 0% { transform: rotate(0deg); } 20%, 80% { transform: rotate(32deg); } 100% { transform: rotate(0deg); } }
@keyframes lg-fall { 0% { opacity: 0; transform: translate(0, 0); } 20% { opacity: 1; } 100% { opacity: 0; transform: translate(2px, 8px); } }
@keyframes lg-pop { from { transform: scale(0.6); } to { transform: scale(1); } }
@keyframes lg-heart { 0% { opacity: 0; transform: translateY(0); } 20% { opacity: 1; } 100% { opacity: 0; transform: translateY(-9px); } }
@keyframes lg-dance {
  0%   { transform: translateY(0) rotate(-8deg); }
  50%  { transform: translateY(-2px) rotate(0deg); }
  100% { transform: translateY(0) rotate(8deg); }
}

/* Asked for stillness: the ending, held - the flower grown and the
   gardener facing you. */
@media (prefers-reduced-motion: reduce) {
  .lg-stage, .lg-walker, .lg-bob, .lg-side, .lg-front, .lg-can, .lg-drops, .lg-drop, .lg-heart, .lg-pop { animation: none; }
  .lg-s1, .lg-s2, .lg-side, .lg-drops, .lg-heart { opacity: 0; }
  .lg-s3, .lg-front { opacity: 1; }
}

/* The thumb row sits on the bottom bar, whatever height the garden is. With
   only a section or two open the plot is shorter than the screen, and the
   buttons used to follow it up into the middle. */
html.garden-full .garden-controls { margin-top: auto; padding-top: 6px; padding-bottom: 5px; }
html.garden-full .garden-plot { will-change: transform; }

/* ===================== shop sheet, second pass =====================

   One fixed size whichever tab is open, so the sheet does not jump when you
   switch; the tabs along the bottom under the thumb, like the app's own bar;
   and smaller tiles, three across, so a whole section mostly fits without
   scrolling. Only the tiles scroll, inside the sheet. */
html[data-mode="phone"] .garden-sheet:has(#shop-panel) { overflow: hidden; display: flex; flex-direction: column; }
html[data-mode="phone"] .garden-sheet #shop-modal-slot { flex: 1; min-height: 0; display: flex; flex-direction: column; }
html[data-mode="phone"] .garden-sheet #shop-panel {
  display: flex;
  flex-direction: column;
  height: min(64vh, 540px);
  min-height: 0;
}
html[data-mode="phone"] .garden-sheet #shop-panel .shop-columns {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding-bottom: 4px;
}
html[data-mode="phone"] .garden-sheet #shop-panel .shop-tabs {
  order: 3;
  flex: none;
  margin: 10px 0 0;
  padding-top: 10px;
  border-top: 1px solid var(--border);
}
html[data-mode="phone"] .shop-tab.on { box-shadow: 0 -3px 0 #a8770c inset; }

html[data-mode="phone"] .shop-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
html[data-mode="phone"] .shop-tile { min-height: 0; padding: 8px 5px 6px; gap: 4px; border-radius: 12px; }
html[data-mode="phone"] .shop-tile-icon { font-size: 1.4rem; }
html[data-mode="phone"] .shop-tile-icon svg { width: 28px; height: 28px; }
html[data-mode="phone"] .shop-tile-label { font-size: 0.68rem; line-height: 1.2; }
html[data-mode="phone"] .shop-tile-action { min-height: 30px; font-size: 0.8rem; border-radius: 8px; }
html[data-mode="phone"] .shop-tab { padding: 6px 2px 5px; }
html[data-mode="phone"] .shop-tab span { font-size: 1.15rem; }
/* The sheet is a flex column now; the close button keeps to its corner. */
html[data-mode="phone"] .garden-sheet:has(#shop-panel) > .modal-close { align-self: flex-end; }
/* "seedling 1, grown 2" is a note, not a price: let it wrap in the button. */
html[data-mode="phone"] .shop-tile-action { white-space: normal; text-align: center; line-height: 1.1; padding: 2px 4px; font-size: 0.78rem; }

/* Help gardener, smaller, with a warm garden-gold thought bubble instead of
   the app's purple. */
.help-gardener { width: 36px; height: 44px; }
.help-gardener .hg-sprite { left: 5px; }
.help-gardener .hg-sprite svg { width: 21px; height: 22px; }
.help-gardener .hg-thought,
.help-gardener .hg-thought::after {
  background: #fff8e6;
  border-color: #d9a520;
}
.help-gardener .hg-thought {
  width: 17px;
  height: 17px;
  font-size: 0.66rem;
  line-height: 13px;
  color: #8a5f00;
}
.help-gardener .hg-thought::after { width: 5px; height: 5px; left: -2px; bottom: -4px; }
.help-gardener:active .hg-thought { background: #fdecb8; }
:root[data-dark] .help-gardener .hg-thought,
:root[data-dark] .help-gardener .hg-thought::after { background: #3a2f12; }
:root[data-dark] .help-gardener .hg-thought { color: #f2cf7c; }

/* The dance is the Macarena now, stepped pose by pose in boot.js, so the
   front view only needs showing - no sway of its own. */
.lg-front { animation: lg-show 0.01s 4.4s forwards; }

/* ---------------------------------------------------------------------------
   Ponds, and what lives in and around them.
   --------------------------------------------------------------------------- */

/* The rim and the rounded corners come from garden.js, square by square, so a
   run of water reads as one pond. A slow shimmer of light across it. */
.garden-water {
  background-color: #4fa3d1;
  /* 17px divides the 34px square, so the glints line up across the join
     between one square of pond and the next. */
  background-image: radial-gradient(circle at 30% 35%, rgba(255, 255, 255, 0.2) 0 1.5px, transparent 2.5px);
  background-size: 17px 17px;
  animation: pond-shimmer 7s linear infinite;
}

@keyframes pond-shimmer {
  from { background-position: 0 0; }
  to   { background-position: 17px 0; }
}

/* Over the water and the soil, under the plants and the scenery. */
.pond-layer {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 1;
}

.pond-creature {
  position: absolute;
  left: 0;
  top: 0;
  will-change: transform;
}

.pond-creature svg { display: block; image-rendering: pixelated; }

/* Seen through the water. */
.pond-creature.koi { opacity: 0.92; }
.pond-creature.tadpole { opacity: 0.85; }

/* A frog is moved a whole square at a time; the transition is the hop across
   and the keyframes lift it on the way. */
.pond-creature.frog { transition: transform 0.38s ease-out; }
.pond-creature.frog .frog-swim { display: none; }
.pond-creature.frog.in-water .frog-sit { display: none; }
.pond-creature.frog.in-water .frog-swim { display: block; }
.pond-creature.frog.hop-a > span { animation: frog-hop-a 0.38s ease-out; }
.pond-creature.frog.hop-b > span { animation: frog-hop-b 0.38s ease-out; }
@keyframes frog-hop-a { 50% { transform: translateY(-9px); } }
@keyframes frog-hop-b { 50% { transform: translateY(-9px); } }
.pond-creature.frog > span { display: block; }

/* Birds. Where they are comes from garden.js; what they look like doing it -
   wings going, sitting, splashing, pecking - is the class. */
.garden-bird { opacity: 1; z-index: 26; }
.garden-bird > span { display: none; }
.garden-bird svg { display: block; image-rendering: pixelated; }
.garden-bird.flying .bird-fly-a { display: block; animation: bird-flap-a 0.24s steps(1) infinite; }
.garden-bird.flying .bird-fly-b { display: block; position: absolute; top: 0; left: 0; animation: bird-flap-b 0.24s steps(1) infinite; }
.garden-bird:not(.flying) .bird-sit { display: block; }
@keyframes bird-flap-a { 0% { opacity: 1; } 50% { opacity: 0; } }
@keyframes bird-flap-b { 0% { opacity: 0; } 50% { opacity: 1; } }

/* In the bath: sunk to its middle in the water and bobbing. */
.garden-bird.bathe .bird-sit {
  clip-path: inset(0 0 45% 0);
  animation: bird-bathe 0.36s ease-in-out infinite alternate;
}
@keyframes bird-bathe {
  from { transform: translateY(0) rotate(-6deg); }
  to   { transform: translateY(1px) rotate(8deg); }
}

/* Head down to the seed, and up again. */
.garden-bird.eat .bird-sit {
  transform-origin: 40% 90%;
  animation: bird-peck 1.1s steps(1) infinite;
}
@keyframes bird-peck {
  0%   { transform: rotate(0); }
  55%  { transform: rotate(22deg); }
  70%  { transform: rotate(0); }
  80%  { transform: rotate(22deg); }
  90%  { transform: rotate(0); }
}

.garden-splash {
  position: absolute;
  width: 2px;
  height: 2px;
  background: #c8ecfb;
  box-shadow: 3px -1px 0 #9bd8f3, -3px 0 0 #e6f6fd;
  pointer-events: none;
  z-index: 27;
  animation: garden-splash 0.6s ease-out forwards;
}
@keyframes garden-splash {
  0%   { transform: translate(0, 0); opacity: 1; }
  60%  { transform: translate(var(--dx), -7px); opacity: 1; }
  100% { transform: translate(var(--dx), 0); opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .garden-water { animation: none; }
  .pond-creature.frog { transition: none; }
  .pond-creature.frog > span { animation: none !important; }
}
